1use super::PointFieldView;
20
21pub const MAX_FIELDS: usize = 16;
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum PointFieldType {
32 Int8 = 1,
33 Uint8 = 2,
34 Int16 = 3,
35 Uint16 = 4,
36 Int32 = 5,
37 Uint32 = 6,
38 Float32 = 7,
39 Float64 = 8,
40}
41
42impl PointFieldType {
43 pub fn from_datatype(dt: u8) -> Option<Self> {
45 match dt {
46 1 => Some(Self::Int8),
47 2 => Some(Self::Uint8),
48 3 => Some(Self::Int16),
49 4 => Some(Self::Uint16),
50 5 => Some(Self::Int32),
51 6 => Some(Self::Uint32),
52 7 => Some(Self::Float32),
53 8 => Some(Self::Float64),
54 _ => None,
55 }
56 }
57
58 pub fn size_bytes(self) -> usize {
60 match self {
61 Self::Int8 | Self::Uint8 => 1,
62 Self::Int16 | Self::Uint16 => 2,
63 Self::Int32 | Self::Uint32 | Self::Float32 => 4,
64 Self::Float64 => 8,
65 }
66 }
67
68 pub fn read_as_f64(self, data: &[u8], off: usize) -> Option<f64> {
79 match self {
80 Self::Float64 => Some(f64::from_le_bytes(data.get(off..off + 8)?.try_into().ok()?)),
81 Self::Float32 => {
82 Some(f32::from_le_bytes(data.get(off..off + 4)?.try_into().ok()?) as f64)
83 }
84 Self::Uint32 => {
85 Some(u32::from_le_bytes(data.get(off..off + 4)?.try_into().ok()?) as f64)
86 }
87 Self::Int32 => {
88 Some(i32::from_le_bytes(data.get(off..off + 4)?.try_into().ok()?) as f64)
89 }
90 Self::Uint16 => {
91 Some(u16::from_le_bytes(data.get(off..off + 2)?.try_into().ok()?) as f64)
92 }
93 Self::Int16 => {
94 Some(i16::from_le_bytes(data.get(off..off + 2)?.try_into().ok()?) as f64)
95 }
96 Self::Uint8 => Some(*data.get(off)? as f64),
97 Self::Int8 => Some(*data.get(off)? as i8 as f64),
98 }
99 }
100}
101
102#[derive(Debug, Clone, Copy)]
106pub struct FieldDesc<'a> {
107 pub name: &'a str,
108 pub byte_offset: u32,
109 pub field_type: PointFieldType,
110 pub count: u32,
111}
112
113impl<'a> FieldDesc<'a> {
114 pub fn from_view(view: &PointFieldView<'a>) -> Option<Self> {
116 Some(FieldDesc {
117 name: view.name,
118 byte_offset: view.offset,
119 field_type: PointFieldType::from_datatype(view.datatype)?,
120 count: view.count,
121 })
122 }
123
124 pub fn read_as_f64(&self, point_data: &[u8]) -> Option<f64> {
134 self.field_type
135 .read_as_f64(point_data, self.byte_offset as usize)
136 }
137
138 pub fn read_as_f32(&self, point_data: &[u8]) -> Option<f32> {
148 self.field_type
149 .read_as_f64(point_data, self.byte_offset as usize)
150 .map(|v| v as f32)
151 }
152}
153
154#[derive(Debug)]
158pub enum PointCloudError {
159 FieldNotFound { name: &'static str },
161 FieldMismatch {
163 name: &'static str,
164 reason: &'static str,
165 },
166 TooManyFields { found: usize },
168 UnknownDatatype { field_name: String, datatype: u8 },
170 BigEndianNotSupported,
172 InvalidLayout { reason: &'static str },
174 FieldAccessOutOfBounds { byte_offset: u32 },
176}
177
178impl core::fmt::Display for PointCloudError {
179 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
180 match self {
181 Self::FieldNotFound { name } => write!(f, "field not found: {name}"),
182 Self::FieldMismatch { name, reason } => {
183 write!(f, "field mismatch for '{name}': {reason}")
184 }
185 Self::TooManyFields { found } => {
186 write!(f, "too many fields: {found} (max {MAX_FIELDS})")
187 }
188 Self::UnknownDatatype {
189 field_name,
190 datatype,
191 } => write!(f, "unknown datatype {datatype} for field '{field_name}'"),
192 Self::BigEndianNotSupported => write!(f, "big-endian point data not supported"),
193 Self::InvalidLayout { reason } => write!(f, "invalid layout: {reason}"),
194 Self::FieldAccessOutOfBounds { byte_offset } => {
195 write!(f, "field access out of bounds at byte offset {byte_offset}")
196 }
197 }
198 }
199}
200
201impl std::error::Error for PointCloudError {}
202
203pub trait PointScalar: Sized {
209 const FIELD_TYPE: PointFieldType;
210 fn read_le(data: &[u8], offset: usize) -> Self;
211}
212
213impl PointScalar for f32 {
214 const FIELD_TYPE: PointFieldType = PointFieldType::Float32;
215 #[inline(always)]
216 fn read_le(data: &[u8], offset: usize) -> Self {
217 let bytes: [u8; 4] = data[offset..offset + 4]
218 .try_into()
219 .expect("bounds checked by caller");
220 f32::from_le_bytes(bytes)
221 }
222}
223
224impl PointScalar for f64 {
225 const FIELD_TYPE: PointFieldType = PointFieldType::Float64;
226 #[inline(always)]
227 fn read_le(data: &[u8], offset: usize) -> Self {
228 let bytes: [u8; 8] = data[offset..offset + 8]
229 .try_into()
230 .expect("bounds checked by caller");
231 f64::from_le_bytes(bytes)
232 }
233}
234
235impl PointScalar for u8 {
236 const FIELD_TYPE: PointFieldType = PointFieldType::Uint8;
237 #[inline(always)]
238 fn read_le(data: &[u8], offset: usize) -> Self {
239 data[offset]
240 }
241}
242
243impl PointScalar for i8 {
244 const FIELD_TYPE: PointFieldType = PointFieldType::Int8;
245 #[inline(always)]
246 fn read_le(data: &[u8], offset: usize) -> Self {
247 data[offset] as i8
248 }
249}
250
251impl PointScalar for u16 {
252 const FIELD_TYPE: PointFieldType = PointFieldType::Uint16;
253 #[inline(always)]
254 fn read_le(data: &[u8], offset: usize) -> Self {
255 let bytes: [u8; 2] = data[offset..offset + 2]
256 .try_into()
257 .expect("bounds checked by caller");
258 u16::from_le_bytes(bytes)
259 }
260}
261
262impl PointScalar for i16 {
263 const FIELD_TYPE: PointFieldType = PointFieldType::Int16;
264 #[inline(always)]
265 fn read_le(data: &[u8], offset: usize) -> Self {
266 let bytes: [u8; 2] = data[offset..offset + 2]
267 .try_into()
268 .expect("bounds checked by caller");
269 i16::from_le_bytes(bytes)
270 }
271}
272
273impl PointScalar for u32 {
274 const FIELD_TYPE: PointFieldType = PointFieldType::Uint32;
275 #[inline(always)]
276 fn read_le(data: &[u8], offset: usize) -> Self {
277 let bytes: [u8; 4] = data[offset..offset + 4]
278 .try_into()
279 .expect("bounds checked by caller");
280 u32::from_le_bytes(bytes)
281 }
282}
283
284impl PointScalar for i32 {
285 const FIELD_TYPE: PointFieldType = PointFieldType::Int32;
286 #[inline(always)]
287 fn read_le(data: &[u8], offset: usize) -> Self {
288 let bytes: [u8; 4] = data[offset..offset + 4]
289 .try_into()
290 .expect("bounds checked by caller");
291 i32::from_le_bytes(bytes)
292 }
293}
294
295pub struct ExpectedField {
299 pub name: &'static str,
300 pub byte_offset: u32,
301 pub field_type: PointFieldType,
302}
303
304pub trait Point: Sized {
310 const FIELD_COUNT: usize;
312
313 fn expected_fields() -> &'static [ExpectedField];
315
316 fn point_size() -> u32;
318
319 fn read_from(data: &[u8], base: usize) -> Self;
324}
325
326#[macro_export]
344macro_rules! define_point {
345 (
346 $(#[$meta:meta])*
347 $vis:vis struct $Name:ident {
348 $( $field:ident : $ty:ty => $offset:expr ),+ $(,)?
349 }
350 ) => {
351 $(#[$meta])*
352 #[derive(Debug, Clone, Copy, PartialEq)]
353 $vis struct $Name {
354 $( pub $field: $ty ),+
355 }
356
357 impl $crate::sensor_msgs::pointcloud::Point for $Name {
358 const FIELD_COUNT: usize =
359 $crate::_point_field_count!($($field)+);
360
361 fn expected_fields()
362 -> &'static [$crate::sensor_msgs::pointcloud::ExpectedField]
363 {
364 &[
365 $(
366 $crate::sensor_msgs::pointcloud::ExpectedField {
367 name: stringify!($field),
368 byte_offset: $offset,
369 field_type:
370 <$ty as $crate::sensor_msgs::pointcloud::PointScalar>
371 ::FIELD_TYPE,
372 }
373 ),+
374 ]
375 }
376
377 fn point_size() -> u32 {
378 let mut size = 0u32;
379 $(
380 {
381 let end = $offset + (core::mem::size_of::<$ty>() as u32);
382 if end > size { size = end; }
383 }
384 )+
385 size
386 }
387
388 fn read_from(data: &[u8], base: usize) -> Self {
389 $Name {
390 $(
391 $field:
392 <$ty as $crate::sensor_msgs::pointcloud::PointScalar>
393 ::read_le(data, base + ($offset as usize))
394 ),+
395 }
396 }
397 }
398 };
399}
400
401#[macro_export]
403#[doc(hidden)]
404macro_rules! _point_field_count {
405 () => { 0usize };
406 ($x:ident $($rest:ident)*) => {
407 1usize + $crate::_point_field_count!($($rest)*)
408 };
409}
410
411pub use crate::_point_field_count;
413pub use crate::define_point;
414
415#[derive(Debug)]
437pub struct DynPointCloud<'a> {
438 data: &'a [u8],
439 point_step: usize,
440 row_step: usize,
441 num_points: usize,
442 fields: [Option<FieldDesc<'a>>; MAX_FIELDS],
443 field_count: usize,
444 height: u32,
445 width: u32,
446}
447
448impl<'a> DynPointCloud<'a> {
449 pub fn from_pointcloud2<B: AsRef<[u8]>>(
463 pc: &'a super::PointCloud2<B>,
464 ) -> Result<Self, PointCloudError> {
465 if pc.is_bigendian() {
466 return Err(PointCloudError::BigEndianNotSupported);
467 }
468
469 let point_step = pc.point_step() as usize;
470 if point_step == 0 {
471 return Err(PointCloudError::InvalidLayout {
472 reason: "point_step is zero",
473 });
474 }
475
476 let num_points = pc.point_count();
477 let data = pc.data();
478 let height = pc.height() as usize;
479 let width = pc.width() as usize;
480 let row_step = pc.row_step() as usize;
481
482 if num_points > 0 {
483 let min_row_step =
485 width
486 .checked_mul(point_step)
487 .ok_or(PointCloudError::InvalidLayout {
488 reason: "width × point_step overflows usize",
489 })?;
490 if row_step < min_row_step {
491 return Err(PointCloudError::InvalidLayout {
492 reason: "row_step smaller than width × point_step",
493 });
494 }
495
496 let required_len =
498 height
499 .checked_mul(row_step)
500 .ok_or(PointCloudError::InvalidLayout {
501 reason: "height × row_step overflows usize",
502 })?;
503 if data.len() < required_len {
504 return Err(PointCloudError::InvalidLayout {
505 reason: "data buffer shorter than height × row_step",
506 });
507 }
508 }
509
510 let mut fields = [const { None }; MAX_FIELDS];
511 let mut field_count = 0;
512
513 for view in pc.fields_iter() {
514 if field_count >= MAX_FIELDS {
515 return Err(PointCloudError::TooManyFields {
516 found: field_count + 1,
517 });
518 }
519 let desc =
520 FieldDesc::from_view(&view).ok_or_else(|| PointCloudError::UnknownDatatype {
521 field_name: view.name.to_string(),
522 datatype: view.datatype,
523 })?;
524 let field_size = desc
526 .field_type
527 .size_bytes()
528 .checked_mul(desc.count as usize)
529 .ok_or(PointCloudError::InvalidLayout {
530 reason: "field count × size overflows usize",
531 })?;
532 let field_end = (desc.byte_offset as usize).checked_add(field_size).ok_or(
533 PointCloudError::InvalidLayout {
534 reason: "field offset + size overflows usize",
535 },
536 )?;
537 if field_end > point_step {
538 return Err(PointCloudError::InvalidLayout {
539 reason: "field extends beyond point_step",
540 });
541 }
542 fields[field_count] = Some(desc);
543 field_count += 1;
544 }
545
546 Ok(DynPointCloud {
547 data,
548 point_step,
549 row_step: pc.row_step() as usize,
550 num_points,
551 fields,
552 field_count,
553 height: pc.height(),
554 width: pc.width(),
555 })
556 }
557
558 pub fn len(&self) -> usize {
560 self.num_points
561 }
562
563 pub fn is_empty(&self) -> bool {
565 self.num_points == 0
566 }
567
568 pub fn height(&self) -> u32 {
570 self.height
571 }
572
573 pub fn width(&self) -> u32 {
575 self.width
576 }
577
578 pub fn point_step(&self) -> usize {
580 self.point_step
581 }
582
583 pub fn field_count(&self) -> usize {
585 self.field_count
586 }
587
588 pub fn fields(&self) -> impl Iterator<Item = &FieldDesc<'a>> {
590 self.fields[..self.field_count]
591 .iter()
592 .filter_map(|f| f.as_ref())
593 }
594
595 pub fn field(&self, name: &str) -> Option<&FieldDesc<'a>> {
597 self.fields().find(|f| f.name == name)
598 }
599
600 #[inline]
603 fn point_offset(&self, i: usize) -> usize {
604 if self.row_step == (self.width as usize) * self.point_step {
605 i * self.point_step
606 } else {
607 let w = self.width as usize;
608 (i / w) * self.row_step + (i % w) * self.point_step
609 }
610 }
611
612 pub fn point(&self, index: usize) -> Option<DynPoint<'a, '_>> {
614 if index >= self.num_points {
615 return None;
616 }
617 let base = self.point_offset(index);
618 let end = base + self.point_step;
619 if end > self.data.len() {
620 return None;
621 }
622 Some(DynPoint {
623 data: &self.data[base..end],
624 cloud: self,
625 })
626 }
627
628 pub fn point_at(&self, row: u32, col: u32) -> Option<DynPoint<'a, '_>> {
632 if row >= self.height || col >= self.width {
633 return None;
634 }
635 let base = (row as usize) * self.row_step + (col as usize) * self.point_step;
636 let end = base + self.point_step;
637 if end > self.data.len() {
638 return None;
639 }
640 Some(DynPoint {
641 data: &self.data[base..end],
642 cloud: self,
643 })
644 }
645
646 pub fn iter(&self) -> DynPointIter<'a, '_> {
648 DynPointIter {
649 cloud: self,
650 index: 0,
651 }
652 }
653
654 pub fn gather_f32(&self, name: &str) -> Option<Vec<f32>> {
660 let desc = self.field(name)?;
661 if desc.field_type != PointFieldType::Float32 {
662 return None;
663 }
664 let off = desc.byte_offset as usize;
665 let mut out = Vec::with_capacity(self.num_points);
666 for i in 0..self.num_points {
667 let base = self.point_offset(i) + off;
668 let bytes: [u8; 4] = self.data[base..base + 4].try_into().ok()?;
669 out.push(f32::from_le_bytes(bytes));
670 }
671 Some(out)
672 }
673
674 pub fn gather_u32(&self, name: &str) -> Option<Vec<u32>> {
680 let desc = self.field(name)?;
681 if desc.field_type != PointFieldType::Uint32 {
682 return None;
683 }
684 let off = desc.byte_offset as usize;
685 let mut out = Vec::with_capacity(self.num_points);
686 for i in 0..self.num_points {
687 let base = self.point_offset(i) + off;
688 let bytes: [u8; 4] = self.data[base..base + 4].try_into().ok()?;
689 out.push(u32::from_le_bytes(bytes));
690 }
691 Some(out)
692 }
693
694 pub fn gather_u16(&self, name: &str) -> Option<Vec<u16>> {
700 let desc = self.field(name)?;
701 if desc.field_type != PointFieldType::Uint16 {
702 return None;
703 }
704 let off = desc.byte_offset as usize;
705 let mut out = Vec::with_capacity(self.num_points);
706 for i in 0..self.num_points {
707 let base = self.point_offset(i) + off;
708 let bytes: [u8; 2] = self.data[base..base + 2].try_into().ok()?;
709 out.push(u16::from_le_bytes(bytes));
710 }
711 Some(out)
712 }
713
714 pub fn gather_u8(&self, name: &str) -> Option<Vec<u8>> {
720 let desc = self.field(name)?;
721 if desc.field_type != PointFieldType::Uint8 {
722 return None;
723 }
724 let off = desc.byte_offset as usize;
725 let mut out = Vec::with_capacity(self.num_points);
726 for i in 0..self.num_points {
727 out.push(self.data[self.point_offset(i) + off]);
728 }
729 Some(out)
730 }
731
732 pub fn gather_i8(&self, name: &str) -> Option<Vec<i8>> {
738 let desc = self.field(name)?;
739 if desc.field_type != PointFieldType::Int8 {
740 return None;
741 }
742 let off = desc.byte_offset as usize;
743 let mut out = Vec::with_capacity(self.num_points);
744 for i in 0..self.num_points {
745 out.push(self.data[self.point_offset(i) + off] as i8);
746 }
747 Some(out)
748 }
749
750 pub fn gather_i16(&self, name: &str) -> Option<Vec<i16>> {
756 let desc = self.field(name)?;
757 if desc.field_type != PointFieldType::Int16 {
758 return None;
759 }
760 let off = desc.byte_offset as usize;
761 let mut out = Vec::with_capacity(self.num_points);
762 for i in 0..self.num_points {
763 let base = self.point_offset(i) + off;
764 let bytes: [u8; 2] = self.data[base..base + 2].try_into().ok()?;
765 out.push(i16::from_le_bytes(bytes));
766 }
767 Some(out)
768 }
769
770 pub fn gather_i32(&self, name: &str) -> Option<Vec<i32>> {
776 let desc = self.field(name)?;
777 if desc.field_type != PointFieldType::Int32 {
778 return None;
779 }
780 let off = desc.byte_offset as usize;
781 let mut out = Vec::with_capacity(self.num_points);
782 for i in 0..self.num_points {
783 let base = self.point_offset(i) + off;
784 let bytes: [u8; 4] = self.data[base..base + 4].try_into().ok()?;
785 out.push(i32::from_le_bytes(bytes));
786 }
787 Some(out)
788 }
789
790 pub fn gather_f64(&self, name: &str) -> Option<Vec<f64>> {
796 let desc = self.field(name)?;
797 if desc.field_type != PointFieldType::Float64 {
798 return None;
799 }
800 let off = desc.byte_offset as usize;
801 let mut out = Vec::with_capacity(self.num_points);
802 for i in 0..self.num_points {
803 let base = self.point_offset(i) + off;
804 let bytes: [u8; 8] = self.data[base..base + 8].try_into().ok()?;
805 out.push(f64::from_le_bytes(bytes));
806 }
807 Some(out)
808 }
809
810 pub fn gather_as_f64(&self, name: &str) -> Option<Vec<f64>> {
820 let desc = self.field(name)?;
821 let mut out = Vec::with_capacity(self.num_points);
822 for i in 0..self.num_points {
823 let base = self.point_offset(i);
824 let point_data = &self.data[base..base + self.point_step];
825 out.push(desc.read_as_f64(point_data)?);
826 }
827 Some(out)
828 }
829
830 pub fn gather_as_f32(&self, name: &str) -> Option<Vec<f32>> {
840 let desc = self.field(name)?;
841 let mut out = Vec::with_capacity(self.num_points);
842 for i in 0..self.num_points {
843 let base = self.point_offset(i);
844 let point_data = &self.data[base..base + self.point_step];
845 out.push(desc.read_as_f32(point_data)?);
846 }
847 Some(out)
848 }
849}
850
851pub struct DynPoint<'a, 'c> {
862 data: &'a [u8],
863 cloud: &'c DynPointCloud<'a>,
864}
865
866impl<'a, 'c> DynPoint<'a, 'c> {
867 pub fn read_f32(&self, name: &str) -> Option<f32> {
869 let desc = self.cloud.field(name)?;
870 if desc.field_type != PointFieldType::Float32 {
871 return None;
872 }
873 let off = desc.byte_offset as usize;
874 let bytes: [u8; 4] = self.data[off..off + 4].try_into().ok()?;
875 Some(f32::from_le_bytes(bytes))
876 }
877
878 pub fn read_u32(&self, name: &str) -> Option<u32> {
880 let desc = self.cloud.field(name)?;
881 if desc.field_type != PointFieldType::Uint32 {
882 return None;
883 }
884 let off = desc.byte_offset as usize;
885 let bytes: [u8; 4] = self.data[off..off + 4].try_into().ok()?;
886 Some(u32::from_le_bytes(bytes))
887 }
888
889 pub fn read_u16(&self, name: &str) -> Option<u16> {
891 let desc = self.cloud.field(name)?;
892 if desc.field_type != PointFieldType::Uint16 {
893 return None;
894 }
895 let off = desc.byte_offset as usize;
896 let bytes: [u8; 2] = self.data[off..off + 2].try_into().ok()?;
897 Some(u16::from_le_bytes(bytes))
898 }
899
900 pub fn read_u8(&self, name: &str) -> Option<u8> {
902 let desc = self.cloud.field(name)?;
903 if desc.field_type != PointFieldType::Uint8 {
904 return None;
905 }
906 Some(self.data[desc.byte_offset as usize])
907 }
908
909 pub fn read_i8(&self, name: &str) -> Option<i8> {
911 let desc = self.cloud.field(name)?;
912 if desc.field_type != PointFieldType::Int8 {
913 return None;
914 }
915 Some(self.data[desc.byte_offset as usize] as i8)
916 }
917
918 pub fn read_i16(&self, name: &str) -> Option<i16> {
920 let desc = self.cloud.field(name)?;
921 if desc.field_type != PointFieldType::Int16 {
922 return None;
923 }
924 let off = desc.byte_offset as usize;
925 let bytes: [u8; 2] = self.data[off..off + 2].try_into().ok()?;
926 Some(i16::from_le_bytes(bytes))
927 }
928
929 pub fn read_i32(&self, name: &str) -> Option<i32> {
931 let desc = self.cloud.field(name)?;
932 if desc.field_type != PointFieldType::Int32 {
933 return None;
934 }
935 let off = desc.byte_offset as usize;
936 let bytes: [u8; 4] = self.data[off..off + 4].try_into().ok()?;
937 Some(i32::from_le_bytes(bytes))
938 }
939
940 pub fn read_f64(&self, name: &str) -> Option<f64> {
942 let desc = self.cloud.field(name)?;
943 if desc.field_type != PointFieldType::Float64 {
944 return None;
945 }
946 let off = desc.byte_offset as usize;
947 let bytes: [u8; 8] = self.data[off..off + 8].try_into().ok()?;
948 Some(f64::from_le_bytes(bytes))
949 }
950
951 pub fn read_f32_at(&self, desc: &FieldDesc<'_>) -> Result<f32, PointCloudError> {
957 let off = desc.byte_offset as usize;
958 let bytes: [u8; 4] = self
959 .data
960 .get(off..off + 4)
961 .ok_or(PointCloudError::FieldAccessOutOfBounds {
962 byte_offset: desc.byte_offset,
963 })?
964 .try_into()
965 .map_err(|_| PointCloudError::FieldAccessOutOfBounds {
966 byte_offset: desc.byte_offset,
967 })?;
968 Ok(f32::from_le_bytes(bytes))
969 }
970
971 pub fn read_u32_at(&self, desc: &FieldDesc<'_>) -> Result<u32, PointCloudError> {
973 let off = desc.byte_offset as usize;
974 let bytes: [u8; 4] = self
975 .data
976 .get(off..off + 4)
977 .ok_or(PointCloudError::FieldAccessOutOfBounds {
978 byte_offset: desc.byte_offset,
979 })?
980 .try_into()
981 .map_err(|_| PointCloudError::FieldAccessOutOfBounds {
982 byte_offset: desc.byte_offset,
983 })?;
984 Ok(u32::from_le_bytes(bytes))
985 }
986
987 pub fn read_u16_at(&self, desc: &FieldDesc<'_>) -> Result<u16, PointCloudError> {
989 let off = desc.byte_offset as usize;
990 let bytes: [u8; 2] = self
991 .data
992 .get(off..off + 2)
993 .ok_or(PointCloudError::FieldAccessOutOfBounds {
994 byte_offset: desc.byte_offset,
995 })?
996 .try_into()
997 .map_err(|_| PointCloudError::FieldAccessOutOfBounds {
998 byte_offset: desc.byte_offset,
999 })?;
1000 Ok(u16::from_le_bytes(bytes))
1001 }
1002
1003 pub fn read_u8_at(&self, desc: &FieldDesc<'_>) -> Result<u8, PointCloudError> {
1005 self.data.get(desc.byte_offset as usize).copied().ok_or(
1006 PointCloudError::FieldAccessOutOfBounds {
1007 byte_offset: desc.byte_offset,
1008 },
1009 )
1010 }
1011
1012 pub fn read_i8_at(&self, desc: &FieldDesc<'_>) -> Result<i8, PointCloudError> {
1014 self.data
1015 .get(desc.byte_offset as usize)
1016 .map(|&b| b as i8)
1017 .ok_or(PointCloudError::FieldAccessOutOfBounds {
1018 byte_offset: desc.byte_offset,
1019 })
1020 }
1021
1022 pub fn read_i16_at(&self, desc: &FieldDesc<'_>) -> Result<i16, PointCloudError> {
1024 let off = desc.byte_offset as usize;
1025 let bytes: [u8; 2] = self
1026 .data
1027 .get(off..off + 2)
1028 .ok_or(PointCloudError::FieldAccessOutOfBounds {
1029 byte_offset: desc.byte_offset,
1030 })?
1031 .try_into()
1032 .map_err(|_| PointCloudError::FieldAccessOutOfBounds {
1033 byte_offset: desc.byte_offset,
1034 })?;
1035 Ok(i16::from_le_bytes(bytes))
1036 }
1037
1038 pub fn read_i32_at(&self, desc: &FieldDesc<'_>) -> Result<i32, PointCloudError> {
1040 let off = desc.byte_offset as usize;
1041 let bytes: [u8; 4] = self
1042 .data
1043 .get(off..off + 4)
1044 .ok_or(PointCloudError::FieldAccessOutOfBounds {
1045 byte_offset: desc.byte_offset,
1046 })?
1047 .try_into()
1048 .map_err(|_| PointCloudError::FieldAccessOutOfBounds {
1049 byte_offset: desc.byte_offset,
1050 })?;
1051 Ok(i32::from_le_bytes(bytes))
1052 }
1053
1054 pub fn read_f64_at(&self, desc: &FieldDesc<'_>) -> Result<f64, PointCloudError> {
1056 let off = desc.byte_offset as usize;
1057 let bytes: [u8; 8] = self
1058 .data
1059 .get(off..off + 8)
1060 .ok_or(PointCloudError::FieldAccessOutOfBounds {
1061 byte_offset: desc.byte_offset,
1062 })?
1063 .try_into()
1064 .map_err(|_| PointCloudError::FieldAccessOutOfBounds {
1065 byte_offset: desc.byte_offset,
1066 })?;
1067 Ok(f64::from_le_bytes(bytes))
1068 }
1069
1070 pub fn cloud(&self) -> &DynPointCloud<'a> {
1076 self.cloud
1077 }
1078
1079 pub fn data(&self) -> &'a [u8] {
1095 self.data
1096 }
1097
1098 pub fn read_as_f64(&self, name: &str) -> Option<f64> {
1108 self.cloud.field(name)?.read_as_f64(self.data)
1109 }
1110
1111 pub fn read_as_f32(&self, name: &str) -> Option<f32> {
1117 self.cloud.field(name)?.read_as_f32(self.data)
1118 }
1119}
1120
1121pub struct DynPointIter<'a, 'c> {
1125 cloud: &'c DynPointCloud<'a>,
1126 index: usize,
1127}
1128
1129impl<'a, 'c> Iterator for DynPointIter<'a, 'c> {
1130 type Item = DynPoint<'a, 'c>;
1131
1132 fn next(&mut self) -> Option<Self::Item> {
1133 let point = self.cloud.point(self.index)?;
1134 self.index += 1;
1135 Some(point)
1136 }
1137
1138 fn size_hint(&self) -> (usize, Option<usize>) {
1139 let remaining = self.cloud.num_points.saturating_sub(self.index);
1140 (remaining, Some(remaining))
1141 }
1142}
1143
1144impl ExactSizeIterator for DynPointIter<'_, '_> {}
1145
1146#[derive(Debug)]
1164pub struct PointCloud<'a, P: Point> {
1165 data: &'a [u8],
1166 point_step: usize,
1167 row_step: usize,
1168 num_points: usize,
1169 height: u32,
1170 width: u32,
1171 _marker: core::marker::PhantomData<P>,
1172}
1173
1174impl<'a, P: Point> PointCloud<'a, P> {
1175 pub fn from_pointcloud2<B: AsRef<[u8]>>(
1190 pc: &'a super::PointCloud2<B>,
1191 ) -> Result<Self, PointCloudError> {
1192 if pc.is_bigendian() {
1193 return Err(PointCloudError::BigEndianNotSupported);
1194 }
1195
1196 Self::validate(pc)?;
1197
1198 let point_step = pc.point_step() as usize;
1199 let num_points = pc.point_count();
1200 let height = pc.height() as usize;
1201 let width = pc.width() as usize;
1202 let row_step = pc.row_step() as usize;
1203
1204 if num_points > 0 {
1206 let min_row_step =
1207 width
1208 .checked_mul(point_step)
1209 .ok_or(PointCloudError::InvalidLayout {
1210 reason: "width × point_step overflows usize",
1211 })?;
1212 if row_step < min_row_step {
1213 return Err(PointCloudError::InvalidLayout {
1214 reason: "row_step smaller than width × point_step",
1215 });
1216 }
1217
1218 let required_len =
1219 height
1220 .checked_mul(row_step)
1221 .ok_or(PointCloudError::InvalidLayout {
1222 reason: "height × row_step overflows usize",
1223 })?;
1224 if pc.data().len() < required_len {
1225 return Err(PointCloudError::InvalidLayout {
1226 reason: "data buffer shorter than height × row_step",
1227 });
1228 }
1229 }
1230
1231 Ok(PointCloud {
1232 data: pc.data(),
1233 point_step,
1234 row_step,
1235 num_points,
1236 height: pc.height(),
1237 width: pc.width(),
1238 _marker: core::marker::PhantomData,
1239 })
1240 }
1241
1242 pub fn validate<B: AsRef<[u8]>>(pc: &super::PointCloud2<B>) -> Result<(), PointCloudError> {
1244 let expected = P::expected_fields();
1245
1246 for exp in expected {
1249 let mut found = None;
1250 for f in pc.fields_iter() {
1251 if f.name == exp.name {
1252 found = Some((f.offset, f.datatype));
1253 break;
1254 }
1255 }
1256 match found {
1257 None => {
1258 return Err(PointCloudError::FieldNotFound { name: exp.name });
1259 }
1260 Some((offset, datatype)) => {
1261 if offset != exp.byte_offset {
1262 return Err(PointCloudError::FieldMismatch {
1263 name: exp.name,
1264 reason: "byte offset mismatch",
1265 });
1266 }
1267 let actual_type = PointFieldType::from_datatype(datatype);
1268 if actual_type != Some(exp.field_type) {
1269 return Err(PointCloudError::FieldMismatch {
1270 name: exp.name,
1271 reason: "datatype mismatch",
1272 });
1273 }
1274 }
1275 }
1276 }
1277
1278 let point_step = pc.point_step();
1279 if point_step < P::point_size() {
1280 return Err(PointCloudError::InvalidLayout {
1281 reason: "point_step smaller than Point type size",
1282 });
1283 }
1284
1285 Ok(())
1286 }
1287
1288 pub fn len(&self) -> usize {
1290 self.num_points
1291 }
1292
1293 pub fn is_empty(&self) -> bool {
1295 self.num_points == 0
1296 }
1297
1298 pub fn height(&self) -> u32 {
1300 self.height
1301 }
1302
1303 pub fn width(&self) -> u32 {
1305 self.width
1306 }
1307
1308 #[inline]
1311 fn point_offset(&self, i: usize) -> usize {
1312 if self.row_step == (self.width as usize) * self.point_step {
1313 i * self.point_step
1314 } else {
1315 let w = self.width as usize;
1316 (i / w) * self.row_step + (i % w) * self.point_step
1317 }
1318 }
1319
1320 pub fn get(&self, index: usize) -> Option<P> {
1322 if index >= self.num_points {
1323 return None;
1324 }
1325 let base = self.point_offset(index);
1326 if base + P::point_size() as usize > self.data.len() {
1327 return None;
1328 }
1329 Some(P::read_from(self.data, base))
1330 }
1331
1332 pub fn get_at(&self, row: u32, col: u32) -> Option<P> {
1336 if row >= self.height || col >= self.width {
1337 return None;
1338 }
1339 let base = (row as usize) * self.row_step + (col as usize) * self.point_step;
1340 if base + P::point_size() as usize > self.data.len() {
1341 return None;
1342 }
1343 Some(P::read_from(self.data, base))
1344 }
1345
1346 pub fn iter(&self) -> PointIter<'a, P> {
1348 PointIter {
1349 data: self.data,
1350 point_step: self.point_step,
1351 row_step: self.row_step,
1352 width: self.width as usize,
1353 num_points: self.num_points,
1354 index: 0,
1355 _marker: core::marker::PhantomData,
1356 }
1357 }
1358}
1359
1360pub struct PointIter<'a, P: Point> {
1362 data: &'a [u8],
1363 point_step: usize,
1364 row_step: usize,
1365 width: usize,
1366 num_points: usize,
1367 index: usize,
1368 _marker: core::marker::PhantomData<P>,
1369}
1370
1371impl<P: Point> PointIter<'_, P> {
1372 #[inline]
1373 fn point_offset(&self, i: usize) -> usize {
1374 if self.row_step == self.width * self.point_step {
1375 i * self.point_step
1376 } else {
1377 (i / self.width) * self.row_step + (i % self.width) * self.point_step
1378 }
1379 }
1380}
1381
1382impl<P: Point> Iterator for PointIter<'_, P> {
1383 type Item = P;
1384
1385 fn next(&mut self) -> Option<P> {
1386 if self.index >= self.num_points {
1387 return None;
1388 }
1389 let base = self.point_offset(self.index);
1390 if base + P::point_size() as usize > self.data.len() {
1391 return None;
1392 }
1393 self.index += 1;
1394 Some(P::read_from(self.data, base))
1395 }
1396
1397 fn size_hint(&self) -> (usize, Option<usize>) {
1398 let remaining = self.num_points.saturating_sub(self.index);
1399 (remaining, Some(remaining))
1400 }
1401}
1402
1403impl<P: Point> ExactSizeIterator for PointIter<'_, P> {}
1404
1405#[cfg(test)]
1408#[allow(deprecated)] mod tests {
1410 use super::*;
1411 use crate::builtin_interfaces::Time;
1412 use crate::sensor_msgs::{PointCloud2, PointFieldView};
1413
1414 fn make_test_cloud() -> PointCloud2<Vec<u8>> {
1416 let fields = [
1417 PointFieldView {
1418 name: "x",
1419 offset: 0,
1420 datatype: 7,
1421 count: 1,
1422 },
1423 PointFieldView {
1424 name: "y",
1425 offset: 4,
1426 datatype: 7,
1427 count: 1,
1428 },
1429 PointFieldView {
1430 name: "z",
1431 offset: 8,
1432 datatype: 7,
1433 count: 1,
1434 },
1435 PointFieldView {
1436 name: "intensity",
1437 offset: 12,
1438 datatype: 7,
1439 count: 1,
1440 },
1441 ];
1442 let point_step = 16u32;
1443 let num_points = 4u32;
1444 let mut data = vec![0u8; (point_step * num_points) as usize];
1445
1446 for i in 0..4u32 {
1448 let base = (i * point_step) as usize;
1449 let x = (i * 3 + 1) as f32;
1450 let y = (i * 3 + 2) as f32;
1451 let z = (i * 3 + 3) as f32;
1452 let intensity = ((i + 1) * 10) as f32;
1453 data[base..base + 4].copy_from_slice(&x.to_le_bytes());
1454 data[base + 4..base + 8].copy_from_slice(&y.to_le_bytes());
1455 data[base + 8..base + 12].copy_from_slice(&z.to_le_bytes());
1456 data[base + 12..base + 16].copy_from_slice(&intensity.to_le_bytes());
1457 }
1458
1459 PointCloud2::new(
1460 Time::new(100, 0),
1461 "lidar",
1462 1,
1463 num_points,
1464 &fields,
1465 false,
1466 point_step,
1467 point_step * num_points,
1468 &data,
1469 true,
1470 )
1471 .unwrap()
1472 }
1473
1474 #[test]
1477 fn dyn_cloud_from_pointcloud2() {
1478 let pc = make_test_cloud();
1479 let cdr = pc.to_cdr();
1480 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
1481 let cloud = DynPointCloud::from_pointcloud2(&decoded).unwrap();
1482
1483 assert_eq!(cloud.len(), 4);
1484 assert_eq!(cloud.field_count(), 4);
1485 assert!(cloud.field("x").is_some());
1486 assert!(cloud.field("intensity").is_some());
1487 assert!(cloud.field("nonexistent").is_none());
1488 }
1489
1490 #[test]
1491 fn dyn_cloud_point_access() {
1492 let pc = make_test_cloud();
1493 let cdr = pc.to_cdr();
1494 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
1495 let cloud = DynPointCloud::from_pointcloud2(&decoded).unwrap();
1496
1497 let p0 = cloud.point(0).unwrap();
1498 assert_eq!(p0.read_f32("x"), Some(1.0));
1499 assert_eq!(p0.read_f32("y"), Some(2.0));
1500 assert_eq!(p0.read_f32("z"), Some(3.0));
1501 assert_eq!(p0.read_f32("intensity"), Some(10.0));
1502
1503 let p3 = cloud.point(3).unwrap();
1504 assert_eq!(p3.read_f32("x"), Some(10.0));
1505 assert_eq!(p3.read_f32("z"), Some(12.0));
1506
1507 assert!(cloud.point(4).is_none());
1509 }
1510
1511 #[test]
1512 fn dyn_cloud_descriptor_access() {
1513 let pc = make_test_cloud();
1514 let cdr = pc.to_cdr();
1515 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
1516 let cloud = DynPointCloud::from_pointcloud2(&decoded).unwrap();
1517
1518 let x_desc = cloud.field("x").unwrap();
1520 let z_desc = cloud.field("z").unwrap();
1521
1522 for (i, point) in cloud.iter().enumerate() {
1523 let expected_x = (i as f32) * 3.0 + 1.0;
1524 let expected_z = (i as f32) * 3.0 + 3.0;
1525 assert_eq!(point.read_f32_at(x_desc).unwrap(), expected_x);
1526 assert_eq!(point.read_f32_at(z_desc).unwrap(), expected_z);
1527 }
1528 }
1529
1530 #[test]
1531 fn dyn_cloud_gather() {
1532 let pc = make_test_cloud();
1533 let cdr = pc.to_cdr();
1534 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
1535 let cloud = DynPointCloud::from_pointcloud2(&decoded).unwrap();
1536
1537 let xs = cloud.gather_f32("x").unwrap();
1538 assert_eq!(xs, vec![1.0, 4.0, 7.0, 10.0]);
1539
1540 let zs = cloud.gather_f32("z").unwrap();
1541 assert_eq!(zs, vec![3.0, 6.0, 9.0, 12.0]);
1542
1543 assert!(cloud.gather_u32("x").is_none());
1545 assert!(cloud.gather_f32("nonexistent").is_none());
1547 }
1548
1549 #[test]
1550 fn dyn_cloud_iterator_count() {
1551 let pc = make_test_cloud();
1552 let cdr = pc.to_cdr();
1553 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
1554 let cloud = DynPointCloud::from_pointcloud2(&decoded).unwrap();
1555
1556 assert_eq!(cloud.iter().count(), 4);
1557 assert_eq!(cloud.iter().len(), 4);
1558 }
1559
1560 #[test]
1561 fn dyn_cloud_organized() {
1562 let fields = [
1564 PointFieldView {
1565 name: "x",
1566 offset: 0,
1567 datatype: 7,
1568 count: 1,
1569 },
1570 PointFieldView {
1571 name: "y",
1572 offset: 4,
1573 datatype: 7,
1574 count: 1,
1575 },
1576 PointFieldView {
1577 name: "z",
1578 offset: 8,
1579 datatype: 7,
1580 count: 1,
1581 },
1582 ];
1583 let point_step = 12u32;
1584 let mut data = vec![0u8; 48]; for i in 0..4u32 {
1586 let base = (i * point_step) as usize;
1587 let val = (i + 1) as f32;
1588 data[base..base + 4].copy_from_slice(&val.to_le_bytes());
1589 }
1590 let pc = PointCloud2::new(
1591 Time::new(0, 0),
1592 "cam",
1593 2,
1594 2,
1595 &fields,
1596 false,
1597 point_step,
1598 24,
1599 &data,
1600 true,
1601 )
1602 .unwrap();
1603 let cdr = pc.to_cdr();
1604 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
1605 let cloud = DynPointCloud::from_pointcloud2(&decoded).unwrap();
1606
1607 assert_eq!(cloud.height(), 2);
1608 assert_eq!(cloud.width(), 2);
1609 assert_eq!(cloud.point_at(0, 0).unwrap().read_f32("x"), Some(1.0));
1610 assert_eq!(cloud.point_at(0, 1).unwrap().read_f32("x"), Some(2.0));
1611 assert_eq!(cloud.point_at(1, 0).unwrap().read_f32("x"), Some(3.0));
1612 assert_eq!(cloud.point_at(1, 1).unwrap().read_f32("x"), Some(4.0));
1613 assert!(cloud.point_at(2, 0).is_none());
1614 }
1615
1616 #[test]
1617 fn dyn_cloud_rejects_bigendian() {
1618 let fields = [PointFieldView {
1619 name: "x",
1620 offset: 0,
1621 datatype: 7,
1622 count: 1,
1623 }];
1624 let data = vec![0u8; 4];
1625 let pc =
1626 PointCloud2::new(Time::new(0, 0), "f", 1, 1, &fields, true, 4, 4, &data, true).unwrap();
1627 let cdr = pc.to_cdr();
1628 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
1629 let err = DynPointCloud::from_pointcloud2(&decoded).unwrap_err();
1630 assert!(matches!(err, PointCloudError::BigEndianNotSupported));
1631 }
1632
1633 #[test]
1634 fn dyn_cloud_mixed_types() {
1635 let fields = [
1637 PointFieldView {
1638 name: "x",
1639 offset: 0,
1640 datatype: 7,
1641 count: 1,
1642 },
1643 PointFieldView {
1644 name: "y",
1645 offset: 4,
1646 datatype: 7,
1647 count: 1,
1648 },
1649 PointFieldView {
1650 name: "z",
1651 offset: 8,
1652 datatype: 7,
1653 count: 1,
1654 },
1655 PointFieldView {
1656 name: "class",
1657 offset: 12,
1658 datatype: 4,
1659 count: 1,
1660 },
1661 PointFieldView {
1662 name: "flags",
1663 offset: 14,
1664 datatype: 2,
1665 count: 1,
1666 },
1667 ];
1668 let point_step = 16u32;
1669 let mut data = vec![0u8; 16];
1670 data[0..4].copy_from_slice(&1.0f32.to_le_bytes());
1671 data[4..8].copy_from_slice(&2.0f32.to_le_bytes());
1672 data[8..12].copy_from_slice(&3.0f32.to_le_bytes());
1673 data[12..14].copy_from_slice(&42u16.to_le_bytes());
1674 data[14] = 0xFF;
1675
1676 let pc = PointCloud2::new(
1677 Time::new(0, 0),
1678 "f",
1679 1,
1680 1,
1681 &fields,
1682 false,
1683 point_step,
1684 16,
1685 &data,
1686 true,
1687 )
1688 .unwrap();
1689 let cdr = pc.to_cdr();
1690 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
1691 let cloud = DynPointCloud::from_pointcloud2(&decoded).unwrap();
1692 let p = cloud.point(0).unwrap();
1693
1694 assert_eq!(p.read_f32("x"), Some(1.0));
1695 assert_eq!(p.read_u16("class"), Some(42));
1696 assert_eq!(p.read_u8("flags"), Some(0xFF));
1697 assert_eq!(p.read_f32("class"), None);
1699 assert_eq!(p.read_u32("flags"), None);
1700
1701 assert_eq!(cloud.gather_u16("class"), Some(vec![42]));
1703 assert_eq!(cloud.gather_u8("flags"), Some(vec![0xFF]));
1704 }
1705
1706 #[test]
1707 fn dyn_cloud_empty() {
1708 let fields = [PointFieldView {
1709 name: "x",
1710 offset: 0,
1711 datatype: 7,
1712 count: 1,
1713 }];
1714 let pc =
1715 PointCloud2::new(Time::new(0, 0), "f", 0, 0, &fields, false, 4, 0, &[], true).unwrap();
1716 let cdr = pc.to_cdr();
1717 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
1718 let cloud = DynPointCloud::from_pointcloud2(&decoded).unwrap();
1719
1720 assert!(cloud.is_empty());
1721 assert_eq!(cloud.len(), 0);
1722 assert_eq!(cloud.iter().count(), 0);
1723 assert!(cloud.gather_f32("x").unwrap().is_empty());
1724 }
1725
1726 define_point! {
1729 struct TestXyzPoint {
1730 x: f32 => 0,
1731 y: f32 => 4,
1732 z: f32 => 8,
1733 }
1734 }
1735
1736 define_point! {
1737 struct TestXyzClassPoint {
1738 x: f32 => 0,
1739 y: f32 => 4,
1740 z: f32 => 8,
1741 class_id: u16 => 12,
1742 instance_id: u16 => 14,
1743 }
1744 }
1745
1746 #[test]
1747 fn static_cloud_xyz() {
1748 let fields = [
1749 PointFieldView {
1750 name: "x",
1751 offset: 0,
1752 datatype: 7,
1753 count: 1,
1754 },
1755 PointFieldView {
1756 name: "y",
1757 offset: 4,
1758 datatype: 7,
1759 count: 1,
1760 },
1761 PointFieldView {
1762 name: "z",
1763 offset: 8,
1764 datatype: 7,
1765 count: 1,
1766 },
1767 ];
1768 let point_step = 12u32;
1769 let mut data = vec![0u8; 36];
1770 for i in 0..3u32 {
1771 let base = (i * point_step) as usize;
1772 data[base..base + 4].copy_from_slice(&(i as f32 + 1.0).to_le_bytes());
1773 data[base + 4..base + 8].copy_from_slice(&(i as f32 + 10.0).to_le_bytes());
1774 data[base + 8..base + 12].copy_from_slice(&(i as f32 + 100.0).to_le_bytes());
1775 }
1776
1777 let pc = PointCloud2::new(
1778 Time::new(0, 0),
1779 "lidar",
1780 1,
1781 3,
1782 &fields,
1783 false,
1784 point_step,
1785 36,
1786 &data,
1787 true,
1788 )
1789 .unwrap();
1790 let cdr = pc.to_cdr();
1791 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
1792 let cloud = PointCloud::<TestXyzPoint>::from_pointcloud2(&decoded).unwrap();
1793
1794 assert_eq!(cloud.len(), 3);
1795 let p0 = cloud.get(0).unwrap();
1796 assert_eq!(p0.x, 1.0);
1797 assert_eq!(p0.y, 10.0);
1798 assert_eq!(p0.z, 100.0);
1799
1800 let p2 = cloud.get(2).unwrap();
1801 assert_eq!(p2.x, 3.0);
1802 assert_eq!(p2.y, 12.0);
1803 assert_eq!(p2.z, 102.0);
1804 }
1805
1806 #[test]
1807 fn static_cloud_mixed_types() {
1808 let fields = [
1809 PointFieldView {
1810 name: "x",
1811 offset: 0,
1812 datatype: 7,
1813 count: 1,
1814 },
1815 PointFieldView {
1816 name: "y",
1817 offset: 4,
1818 datatype: 7,
1819 count: 1,
1820 },
1821 PointFieldView {
1822 name: "z",
1823 offset: 8,
1824 datatype: 7,
1825 count: 1,
1826 },
1827 PointFieldView {
1828 name: "class_id",
1829 offset: 12,
1830 datatype: 4,
1831 count: 1,
1832 },
1833 PointFieldView {
1834 name: "instance_id",
1835 offset: 14,
1836 datatype: 4,
1837 count: 1,
1838 },
1839 ];
1840 let point_step = 16u32;
1841 let mut data = vec![0u8; 16];
1842 data[0..4].copy_from_slice(&1.5f32.to_le_bytes());
1843 data[4..8].copy_from_slice(&2.5f32.to_le_bytes());
1844 data[8..12].copy_from_slice(&3.5f32.to_le_bytes());
1845 data[12..14].copy_from_slice(&7u16.to_le_bytes());
1846 data[14..16].copy_from_slice(&42u16.to_le_bytes());
1847
1848 let pc = PointCloud2::new(
1849 Time::new(0, 0),
1850 "f",
1851 1,
1852 1,
1853 &fields,
1854 false,
1855 point_step,
1856 16,
1857 &data,
1858 true,
1859 )
1860 .unwrap();
1861 let cdr = pc.to_cdr();
1862 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
1863 let cloud = PointCloud::<TestXyzClassPoint>::from_pointcloud2(&decoded).unwrap();
1864
1865 let p = cloud.get(0).unwrap();
1866 assert_eq!(p.x, 1.5);
1867 assert_eq!(p.y, 2.5);
1868 assert_eq!(p.z, 3.5);
1869 assert_eq!(p.class_id, 7);
1870 assert_eq!(p.instance_id, 42);
1871 }
1872
1873 #[test]
1874 fn static_cloud_validation_field_missing() {
1875 let fields = [
1876 PointFieldView {
1877 name: "x",
1878 offset: 0,
1879 datatype: 7,
1880 count: 1,
1881 },
1882 PointFieldView {
1883 name: "y",
1884 offset: 4,
1885 datatype: 7,
1886 count: 1,
1887 },
1888 ];
1889 let data = vec![0u8; 8];
1890 let pc = PointCloud2::new(
1891 Time::new(0, 0),
1892 "f",
1893 1,
1894 1,
1895 &fields,
1896 false,
1897 8,
1898 8,
1899 &data,
1900 true,
1901 )
1902 .unwrap();
1903 let cdr = pc.to_cdr();
1904 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
1905 let err = PointCloud::<TestXyzPoint>::from_pointcloud2(&decoded).unwrap_err();
1906 assert!(matches!(err, PointCloudError::FieldNotFound { name: "z" }));
1907 }
1908
1909 #[test]
1910 fn static_cloud_validation_type_mismatch() {
1911 let fields = [
1912 PointFieldView {
1913 name: "x",
1914 offset: 0,
1915 datatype: 7,
1916 count: 1,
1917 },
1918 PointFieldView {
1919 name: "y",
1920 offset: 4,
1921 datatype: 7,
1922 count: 1,
1923 },
1924 PointFieldView {
1925 name: "z",
1926 offset: 8,
1927 datatype: 6, count: 1,
1929 },
1930 ];
1931 let data = vec![0u8; 12];
1932 let pc = PointCloud2::new(
1933 Time::new(0, 0),
1934 "f",
1935 1,
1936 1,
1937 &fields,
1938 false,
1939 12,
1940 12,
1941 &data,
1942 true,
1943 )
1944 .unwrap();
1945 let cdr = pc.to_cdr();
1946 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
1947 let err = PointCloud::<TestXyzPoint>::from_pointcloud2(&decoded).unwrap_err();
1948 assert!(matches!(
1949 err,
1950 PointCloudError::FieldMismatch { name: "z", .. }
1951 ));
1952 }
1953
1954 #[test]
1955 fn static_cloud_extra_fields_ok() {
1956 let fields = [
1959 PointFieldView {
1960 name: "x",
1961 offset: 0,
1962 datatype: 7,
1963 count: 1,
1964 },
1965 PointFieldView {
1966 name: "y",
1967 offset: 4,
1968 datatype: 7,
1969 count: 1,
1970 },
1971 PointFieldView {
1972 name: "z",
1973 offset: 8,
1974 datatype: 7,
1975 count: 1,
1976 },
1977 PointFieldView {
1978 name: "intensity",
1979 offset: 12,
1980 datatype: 7,
1981 count: 1,
1982 },
1983 ];
1984 let data = vec![0u8; 16];
1985 let pc = PointCloud2::new(
1986 Time::new(0, 0),
1987 "f",
1988 1,
1989 1,
1990 &fields,
1991 false,
1992 16,
1993 16,
1994 &data,
1995 true,
1996 )
1997 .unwrap();
1998 let cdr = pc.to_cdr();
1999 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
2000 let cloud = PointCloud::<TestXyzPoint>::from_pointcloud2(&decoded).unwrap();
2001 assert_eq!(cloud.len(), 1);
2002 }
2003
2004 #[test]
2005 fn static_cloud_iterator() {
2006 let fields = [
2007 PointFieldView {
2008 name: "x",
2009 offset: 0,
2010 datatype: 7,
2011 count: 1,
2012 },
2013 PointFieldView {
2014 name: "y",
2015 offset: 4,
2016 datatype: 7,
2017 count: 1,
2018 },
2019 PointFieldView {
2020 name: "z",
2021 offset: 8,
2022 datatype: 7,
2023 count: 1,
2024 },
2025 ];
2026 let point_step = 12u32;
2027 let n = 100u32;
2028 let mut data = vec![0u8; (point_step * n) as usize];
2029 for i in 0..n {
2030 let base = (i * point_step) as usize;
2031 data[base..base + 4].copy_from_slice(&(i as f32).to_le_bytes());
2032 data[base + 4..base + 8].copy_from_slice(&(i as f32 * 2.0).to_le_bytes());
2033 data[base + 8..base + 12].copy_from_slice(&(i as f32 * 3.0).to_le_bytes());
2034 }
2035
2036 let pc = PointCloud2::new(
2037 Time::new(0, 0),
2038 "f",
2039 1,
2040 n,
2041 &fields,
2042 false,
2043 point_step,
2044 point_step * n,
2045 &data,
2046 true,
2047 )
2048 .unwrap();
2049 let cdr = pc.to_cdr();
2050 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
2051 let cloud = PointCloud::<TestXyzPoint>::from_pointcloud2(&decoded).unwrap();
2052
2053 assert_eq!(cloud.iter().len(), 100);
2054 let points: Vec<_> = cloud.iter().collect();
2055 assert_eq!(
2056 points[0],
2057 TestXyzPoint {
2058 x: 0.0,
2059 y: 0.0,
2060 z: 0.0
2061 }
2062 );
2063 assert_eq!(
2064 points[99],
2065 TestXyzPoint {
2066 x: 99.0,
2067 y: 198.0,
2068 z: 297.0
2069 }
2070 );
2071 }
2072
2073 #[test]
2076 fn convenience_methods() {
2077 let pc = make_test_cloud();
2078 let cdr = pc.to_cdr();
2079 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
2080
2081 let dyn_cloud = decoded.as_dyn_cloud().unwrap();
2083 assert_eq!(dyn_cloud.len(), 4);
2084
2085 let typed_cloud = decoded.as_typed_cloud::<TestXyzPoint>().unwrap();
2087 assert_eq!(typed_cloud.len(), 4);
2088 assert_eq!(typed_cloud.get(0).unwrap().x, 1.0);
2089 }
2090
2091 #[test]
2094 fn point_field_type_from_datatype() {
2095 assert_eq!(PointFieldType::from_datatype(1), Some(PointFieldType::Int8));
2096 assert_eq!(
2097 PointFieldType::from_datatype(2),
2098 Some(PointFieldType::Uint8)
2099 );
2100 assert_eq!(
2101 PointFieldType::from_datatype(3),
2102 Some(PointFieldType::Int16)
2103 );
2104 assert_eq!(
2105 PointFieldType::from_datatype(4),
2106 Some(PointFieldType::Uint16)
2107 );
2108 assert_eq!(
2109 PointFieldType::from_datatype(5),
2110 Some(PointFieldType::Int32)
2111 );
2112 assert_eq!(
2113 PointFieldType::from_datatype(6),
2114 Some(PointFieldType::Uint32)
2115 );
2116 assert_eq!(
2117 PointFieldType::from_datatype(7),
2118 Some(PointFieldType::Float32)
2119 );
2120 assert_eq!(
2121 PointFieldType::from_datatype(8),
2122 Some(PointFieldType::Float64)
2123 );
2124 assert_eq!(PointFieldType::from_datatype(0), None);
2126 assert_eq!(PointFieldType::from_datatype(9), None);
2127 assert_eq!(PointFieldType::from_datatype(255), None);
2128 }
2129
2130 #[test]
2131 fn point_field_type_size_bytes() {
2132 assert_eq!(PointFieldType::Int8.size_bytes(), 1);
2133 assert_eq!(PointFieldType::Uint8.size_bytes(), 1);
2134 assert_eq!(PointFieldType::Int16.size_bytes(), 2);
2135 assert_eq!(PointFieldType::Uint16.size_bytes(), 2);
2136 assert_eq!(PointFieldType::Int32.size_bytes(), 4);
2137 assert_eq!(PointFieldType::Uint32.size_bytes(), 4);
2138 assert_eq!(PointFieldType::Float32.size_bytes(), 4);
2139 assert_eq!(PointFieldType::Float64.size_bytes(), 8);
2140 }
2141
2142 #[test]
2143 fn field_desc_from_view_unknown_datatype() {
2144 let view = PointFieldView {
2145 name: "bad",
2146 offset: 0,
2147 datatype: 99,
2148 count: 1,
2149 };
2150 assert!(FieldDesc::from_view(&view).is_none());
2151 }
2152
2153 #[test]
2156 fn dyn_cloud_signed_and_f64_types() {
2157 let fields = [
2158 PointFieldView {
2159 name: "i8_field",
2160 offset: 0,
2161 datatype: 1,
2162 count: 1,
2163 }, PointFieldView {
2165 name: "u8_field",
2166 offset: 1,
2167 datatype: 2,
2168 count: 1,
2169 }, PointFieldView {
2171 name: "i16_field",
2172 offset: 2,
2173 datatype: 3,
2174 count: 1,
2175 }, PointFieldView {
2177 name: "u16_field",
2178 offset: 4,
2179 datatype: 4,
2180 count: 1,
2181 }, PointFieldView {
2183 name: "i32_field",
2184 offset: 6,
2185 datatype: 5,
2186 count: 1,
2187 }, PointFieldView {
2189 name: "u32_field",
2190 offset: 10,
2191 datatype: 6,
2192 count: 1,
2193 }, PointFieldView {
2195 name: "f32_field",
2196 offset: 14,
2197 datatype: 7,
2198 count: 1,
2199 }, PointFieldView {
2201 name: "f64_field",
2202 offset: 18,
2203 datatype: 8,
2204 count: 1,
2205 }, ];
2207 let point_step = 26u32; let mut data = vec![0u8; point_step as usize];
2209 data[0] = 0xFE_u8; data[1] = 42; data[2..4].copy_from_slice(&(-300i16).to_le_bytes()); data[4..6].copy_from_slice(&1000u16.to_le_bytes()); data[6..10].copy_from_slice(&(-100_000i32).to_le_bytes()); data[10..14].copy_from_slice(&3_000_000u32.to_le_bytes()); data[14..18].copy_from_slice(&std::f32::consts::PI.to_le_bytes()); data[18..26].copy_from_slice(&std::f64::consts::E.to_le_bytes()); let pc = PointCloud2::new(
2219 Time::new(0, 0),
2220 "test",
2221 1,
2222 1,
2223 &fields,
2224 false,
2225 point_step,
2226 point_step,
2227 &data,
2228 true,
2229 )
2230 .unwrap();
2231 let cdr = pc.to_cdr();
2232 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
2233 let cloud = DynPointCloud::from_pointcloud2(&decoded).unwrap();
2234
2235 assert_eq!(cloud.field_count(), 8);
2236
2237 let i8_desc = cloud.field("i8_field").unwrap();
2239 assert_eq!(i8_desc.field_type, PointFieldType::Int8);
2240 let f64_desc = cloud.field("f64_field").unwrap();
2241 assert_eq!(f64_desc.field_type, PointFieldType::Float64);
2242
2243 assert_eq!(cloud.gather_u8("u8_field"), Some(vec![42]));
2245 assert_eq!(cloud.gather_u16("u16_field"), Some(vec![1000]));
2246 assert_eq!(cloud.gather_u32("u32_field"), Some(vec![3_000_000]));
2247
2248 let p = cloud.point(0).unwrap();
2250 assert_eq!(p.read_u8("u8_field"), Some(42));
2251 assert_eq!(p.read_u16("u16_field"), Some(1000));
2252 assert_eq!(p.read_u32("u32_field"), Some(3_000_000));
2253 assert_eq!(p.read_f32("f32_field"), Some(std::f32::consts::PI));
2254
2255 let u16_desc = cloud.field("u16_field").unwrap();
2257 assert_eq!(p.read_u16_at(u16_desc).unwrap(), 1000);
2258 let u8_desc = cloud.field("u8_field").unwrap();
2259 assert_eq!(p.read_u8_at(u8_desc).unwrap(), 42);
2260 }
2261
2262 #[test]
2265 fn define_point_metadata() {
2266 assert_eq!(TestXyzPoint::FIELD_COUNT, 3);
2267 assert_eq!(TestXyzPoint::point_size(), 12);
2268
2269 let fields = TestXyzPoint::expected_fields();
2270 assert_eq!(fields.len(), 3);
2271 assert_eq!(fields[0].name, "x");
2272 assert_eq!(fields[0].byte_offset, 0);
2273 assert_eq!(fields[0].field_type, PointFieldType::Float32);
2274 assert_eq!(fields[1].name, "y");
2275 assert_eq!(fields[2].name, "z");
2276 assert_eq!(fields[2].byte_offset, 8);
2277 }
2278
2279 #[test]
2280 fn define_point_mixed_metadata() {
2281 assert_eq!(TestXyzClassPoint::FIELD_COUNT, 5);
2282 assert_eq!(TestXyzClassPoint::point_size(), 16); let fields = TestXyzClassPoint::expected_fields();
2285 assert_eq!(fields[3].name, "class_id");
2286 assert_eq!(fields[3].field_type, PointFieldType::Uint16);
2287 assert_eq!(fields[3].byte_offset, 12);
2288 assert_eq!(fields[4].name, "instance_id");
2289 assert_eq!(fields[4].byte_offset, 14);
2290 }
2291
2292 #[test]
2293 fn define_point_read_from() {
2294 let mut data = vec![0u8; 12];
2295 data[0..4].copy_from_slice(&1.5f32.to_le_bytes());
2296 data[4..8].copy_from_slice(&2.5f32.to_le_bytes());
2297 data[8..12].copy_from_slice(&3.5f32.to_le_bytes());
2298
2299 let p = TestXyzPoint::read_from(&data, 0);
2300 assert_eq!(p.x, 1.5);
2301 assert_eq!(p.y, 2.5);
2302 assert_eq!(p.z, 3.5);
2303 }
2304
2305 #[test]
2308 fn static_cloud_validation_offset_mismatch() {
2309 let fields = [
2311 PointFieldView {
2312 name: "x",
2313 offset: 0,
2314 datatype: 7,
2315 count: 1,
2316 },
2317 PointFieldView {
2318 name: "y",
2319 offset: 8, datatype: 7,
2321 count: 1,
2322 },
2323 PointFieldView {
2324 name: "z",
2325 offset: 12,
2326 datatype: 7,
2327 count: 1,
2328 },
2329 ];
2330 let data = vec![0u8; 16];
2331 let pc = PointCloud2::new(
2332 Time::new(0, 0),
2333 "f",
2334 1,
2335 1,
2336 &fields,
2337 false,
2338 16,
2339 16,
2340 &data,
2341 true,
2342 )
2343 .unwrap();
2344 let cdr = pc.to_cdr();
2345 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
2346 let err = PointCloud::<TestXyzPoint>::from_pointcloud2(&decoded).unwrap_err();
2347 assert!(matches!(
2348 err,
2349 PointCloudError::FieldMismatch {
2350 name: "y",
2351 reason: "byte offset mismatch"
2352 }
2353 ));
2354 }
2355
2356 #[test]
2357 fn static_cloud_point_step_too_small() {
2358 let fields = [
2359 PointFieldView {
2360 name: "x",
2361 offset: 0,
2362 datatype: 7,
2363 count: 1,
2364 },
2365 PointFieldView {
2366 name: "y",
2367 offset: 4,
2368 datatype: 7,
2369 count: 1,
2370 },
2371 PointFieldView {
2372 name: "z",
2373 offset: 8,
2374 datatype: 7,
2375 count: 1,
2376 },
2377 ];
2378 let data = vec![0u8; 8];
2380 let pc = PointCloud2::new(
2381 Time::new(0, 0),
2382 "f",
2383 1,
2384 1,
2385 &fields,
2386 false,
2387 8,
2388 8,
2389 &data,
2390 true,
2391 )
2392 .unwrap();
2393 let cdr = pc.to_cdr();
2394 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
2395 let err = PointCloud::<TestXyzPoint>::from_pointcloud2(&decoded).unwrap_err();
2396 assert!(matches!(err, PointCloudError::InvalidLayout { .. }));
2397 }
2398
2399 define_point! {
2402 struct TestOusterPoint {
2404 x: f32 => 0,
2405 y: f32 => 4,
2406 z: f32 => 8,
2407 }
2408 }
2409
2410 #[test]
2411 fn static_cloud_with_padding() {
2412 let fields = [
2414 PointFieldView {
2415 name: "x",
2416 offset: 0,
2417 datatype: 7,
2418 count: 1,
2419 },
2420 PointFieldView {
2421 name: "y",
2422 offset: 4,
2423 datatype: 7,
2424 count: 1,
2425 },
2426 PointFieldView {
2427 name: "z",
2428 offset: 8,
2429 datatype: 7,
2430 count: 1,
2431 },
2432 PointFieldView {
2433 name: "intensity",
2434 offset: 12,
2435 datatype: 7,
2436 count: 1,
2437 },
2438 PointFieldView {
2439 name: "ring",
2440 offset: 16,
2441 datatype: 4,
2442 count: 1,
2443 },
2444 PointFieldView {
2445 name: "timestamp",
2446 offset: 24,
2447 datatype: 8,
2448 count: 1,
2449 },
2450 ];
2451 let point_step = 32u32;
2452 let n = 3u32;
2453 let mut data = vec![0u8; (point_step * n) as usize];
2454 for i in 0..n {
2455 let base = (i * point_step) as usize;
2456 data[base..base + 4].copy_from_slice(&(i as f32 * 10.0).to_le_bytes());
2457 data[base + 4..base + 8].copy_from_slice(&(i as f32 * 20.0).to_le_bytes());
2458 data[base + 8..base + 12].copy_from_slice(&(i as f32 * 30.0).to_le_bytes());
2459 }
2460
2461 let pc = PointCloud2::new(
2462 Time::new(0, 0),
2463 "os1",
2464 1,
2465 n,
2466 &fields,
2467 false,
2468 point_step,
2469 point_step * n,
2470 &data,
2471 true,
2472 )
2473 .unwrap();
2474 let cdr = pc.to_cdr();
2475 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
2476
2477 let cloud = PointCloud::<TestOusterPoint>::from_pointcloud2(&decoded).unwrap();
2479 assert_eq!(cloud.len(), 3);
2480 let p1 = cloud.get(1).unwrap();
2481 assert_eq!(p1.x, 10.0);
2482 assert_eq!(p1.y, 20.0);
2483 assert_eq!(p1.z, 30.0);
2484
2485 let dyn_cloud = decoded.as_dyn_cloud().unwrap();
2487 assert_eq!(dyn_cloud.field_count(), 6);
2488 assert_eq!(dyn_cloud.point_step(), 32);
2489
2490 let xs = dyn_cloud.gather_f32("x").unwrap();
2492 assert_eq!(xs, vec![0.0, 10.0, 20.0]);
2493 }
2494
2495 #[test]
2498 fn dyn_cloud_unknown_datatype_rejected() {
2499 let view = PointFieldView {
2502 name: "bad",
2503 offset: 0,
2504 datatype: 99,
2505 count: 1,
2506 };
2507 assert!(FieldDesc::from_view(&view).is_none());
2508 }
2509
2510 #[test]
2511 fn dyn_cloud_gather_wrong_type_returns_none() {
2512 let pc = make_test_cloud();
2513 let cdr = pc.to_cdr();
2514 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
2515 let cloud = DynPointCloud::from_pointcloud2(&decoded).unwrap();
2516
2517 assert!(cloud.gather_u32("x").is_none());
2519 assert!(cloud.gather_u16("x").is_none());
2520 assert!(cloud.gather_u8("x").is_none());
2521
2522 assert!(cloud.gather_f32("nonexistent").is_none());
2524 assert!(cloud.gather_u32("nonexistent").is_none());
2525 assert!(cloud.gather_u16("nonexistent").is_none());
2526 assert!(cloud.gather_u8("nonexistent").is_none());
2527 }
2528
2529 #[test]
2530 fn dyn_point_wrong_type_returns_none() {
2531 let pc = make_test_cloud();
2532 let cdr = pc.to_cdr();
2533 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
2534 let cloud = DynPointCloud::from_pointcloud2(&decoded).unwrap();
2535 let p = cloud.point(0).unwrap();
2536
2537 assert!(p.read_u32("x").is_none());
2539 assert!(p.read_u16("x").is_none());
2540 assert!(p.read_u8("x").is_none());
2541
2542 assert!(p.read_f32("nope").is_none());
2544 assert!(p.read_u32("nope").is_none());
2545 assert!(p.read_u16("nope").is_none());
2546 assert!(p.read_u8("nope").is_none());
2547 }
2548
2549 #[test]
2552 fn point_cloud_error_display() {
2553 let e = PointCloudError::FieldNotFound { name: "x" };
2554 assert_eq!(format!("{e}"), "field not found: x");
2555
2556 let e = PointCloudError::FieldMismatch {
2557 name: "y",
2558 reason: "byte offset mismatch",
2559 };
2560 assert_eq!(
2561 format!("{e}"),
2562 "field mismatch for 'y': byte offset mismatch"
2563 );
2564
2565 let e = PointCloudError::TooManyFields { found: 20 };
2566 assert_eq!(format!("{e}"), "too many fields: 20 (max 16)");
2567
2568 let e = PointCloudError::UnknownDatatype {
2569 field_name: "bad".into(),
2570 datatype: 99,
2571 };
2572 assert_eq!(format!("{e}"), "unknown datatype 99 for field 'bad'");
2573
2574 let e = PointCloudError::BigEndianNotSupported;
2575 assert_eq!(format!("{e}"), "big-endian point data not supported");
2576
2577 let e = PointCloudError::InvalidLayout {
2578 reason: "point_step is zero",
2579 };
2580 assert_eq!(format!("{e}"), "invalid layout: point_step is zero");
2581 }
2582
2583 #[test]
2586 fn static_cloud_organized_access() {
2587 let fields = [
2588 PointFieldView {
2589 name: "x",
2590 offset: 0,
2591 datatype: 7,
2592 count: 1,
2593 },
2594 PointFieldView {
2595 name: "y",
2596 offset: 4,
2597 datatype: 7,
2598 count: 1,
2599 },
2600 PointFieldView {
2601 name: "z",
2602 offset: 8,
2603 datatype: 7,
2604 count: 1,
2605 },
2606 ];
2607 let point_step = 12u32;
2608 let n = 6u32;
2610 let mut data = vec![0u8; (point_step * n) as usize];
2611 for i in 0..n {
2612 let base = (i * point_step) as usize;
2613 data[base..base + 4].copy_from_slice(&(i as f32).to_le_bytes());
2614 }
2615
2616 let pc = PointCloud2::new(
2617 Time::new(0, 0),
2618 "depth",
2619 3,
2620 2,
2621 &fields,
2622 false,
2623 point_step,
2624 point_step * 2,
2625 &data,
2626 true,
2627 )
2628 .unwrap();
2629 let cdr = pc.to_cdr();
2630 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
2631 let cloud = PointCloud::<TestXyzPoint>::from_pointcloud2(&decoded).unwrap();
2632
2633 assert_eq!(cloud.height(), 3);
2634 assert_eq!(cloud.width(), 2);
2635
2636 assert_eq!(cloud.get_at(0, 0).unwrap().x, 0.0);
2638 assert_eq!(cloud.get_at(0, 1).unwrap().x, 1.0);
2639 assert_eq!(cloud.get_at(1, 0).unwrap().x, 2.0);
2640 assert_eq!(cloud.get_at(1, 1).unwrap().x, 3.0);
2641 assert_eq!(cloud.get_at(2, 0).unwrap().x, 4.0);
2642 assert_eq!(cloud.get_at(2, 1).unwrap().x, 5.0);
2643
2644 assert!(cloud.get_at(3, 0).is_none());
2646 assert!(cloud.get_at(0, 2).is_none());
2647 }
2648
2649 #[test]
2650 fn static_cloud_organized_with_row_padding() {
2651 let fields = [
2653 PointFieldView {
2654 name: "x",
2655 offset: 0,
2656 datatype: 7,
2657 count: 1,
2658 },
2659 PointFieldView {
2660 name: "y",
2661 offset: 4,
2662 datatype: 7,
2663 count: 1,
2664 },
2665 PointFieldView {
2666 name: "z",
2667 offset: 8,
2668 datatype: 7,
2669 count: 1,
2670 },
2671 ];
2672 let point_step = 12u32;
2673 let width = 2u32;
2674 let height = 3u32;
2675 let row_step = 32u32;
2677 let total_bytes = (row_step * height) as usize;
2678 let mut data = vec![0xFFu8; total_bytes]; for row in 0..height {
2681 for col in 0..width {
2682 let offset = (row * row_step + col * point_step) as usize;
2683 let val = (row * width + col) as f32;
2684 data[offset..offset + 4].copy_from_slice(&val.to_le_bytes());
2685 }
2686 }
2687
2688 let pc = PointCloud2::new(
2689 Time::new(0, 0),
2690 "padded",
2691 height,
2692 width,
2693 &fields,
2694 false,
2695 point_step,
2696 row_step,
2697 &data,
2698 true,
2699 )
2700 .unwrap();
2701 let cdr = pc.to_cdr();
2702 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
2703
2704 let dyn_cloud = DynPointCloud::from_pointcloud2(&decoded).unwrap();
2706 let typed_cloud = PointCloud::<TestXyzPoint>::from_pointcloud2(&decoded).unwrap();
2707
2708 for row in 0..height {
2709 for col in 0..width {
2710 let expected = (row * width + col) as f32;
2711 let dp = dyn_cloud.point_at(row, col).unwrap();
2712 assert_eq!(dp.read_f32("x"), Some(expected), "dyn row={row} col={col}");
2713 let tp = typed_cloud.get_at(row, col).unwrap();
2714 assert_eq!(tp.x, expected, "typed row={row} col={col}");
2715 }
2716 }
2717 }
2718
2719 define_point! {
2722 struct TestAllTypesPoint {
2724 a: i8 => 0,
2725 b: u8 => 1,
2726 c: i16 => 2,
2727 d: u16 => 4,
2728 e: i32 => 6,
2729 f: u32 => 10,
2730 g: f32 => 14,
2731 h: f64 => 18,
2732 }
2733 }
2734
2735 #[test]
2736 fn static_cloud_all_scalar_types() {
2737 use crate::sensor_msgs::pointcloud::Point;
2738
2739 assert_eq!(TestAllTypesPoint::FIELD_COUNT, 8);
2740 assert_eq!(TestAllTypesPoint::point_size(), 26); let fields = TestAllTypesPoint::expected_fields();
2743 assert_eq!(fields[0].field_type, PointFieldType::Int8);
2744 assert_eq!(fields[1].field_type, PointFieldType::Uint8);
2745 assert_eq!(fields[2].field_type, PointFieldType::Int16);
2746 assert_eq!(fields[3].field_type, PointFieldType::Uint16);
2747 assert_eq!(fields[4].field_type, PointFieldType::Int32);
2748 assert_eq!(fields[5].field_type, PointFieldType::Uint32);
2749 assert_eq!(fields[6].field_type, PointFieldType::Float32);
2750 assert_eq!(fields[7].field_type, PointFieldType::Float64);
2751
2752 let mut data = vec![0u8; 26];
2754 data[0] = 0xFE; data[1] = 200; data[2..4].copy_from_slice(&(-500i16).to_le_bytes());
2757 data[4..6].copy_from_slice(&60000u16.to_le_bytes());
2758 data[6..10].copy_from_slice(&(-1_000_000i32).to_le_bytes());
2759 data[10..14].copy_from_slice(&4_000_000u32.to_le_bytes());
2760 data[14..18].copy_from_slice(&std::f32::consts::E.to_le_bytes());
2761 data[18..26].copy_from_slice(&std::f64::consts::PI.to_le_bytes());
2762
2763 let p = TestAllTypesPoint::read_from(&data, 0);
2764 assert_eq!(p.a, -2);
2765 assert_eq!(p.b, 200);
2766 assert_eq!(p.c, -500);
2767 assert_eq!(p.d, 60000);
2768 assert_eq!(p.e, -1_000_000);
2769 assert_eq!(p.f, 4_000_000);
2770 assert_eq!(p.g, std::f32::consts::E);
2771 assert_eq!(p.h, std::f64::consts::PI);
2772 }
2773
2774 fn make_all_types_cloud_cdr() -> Vec<u8> {
2778 let fields = [
2779 PointFieldView {
2780 name: "i8_field",
2781 offset: 0,
2782 datatype: 1,
2783 count: 1,
2784 },
2785 PointFieldView {
2786 name: "u8_field",
2787 offset: 1,
2788 datatype: 2,
2789 count: 1,
2790 },
2791 PointFieldView {
2792 name: "i16_field",
2793 offset: 2,
2794 datatype: 3,
2795 count: 1,
2796 },
2797 PointFieldView {
2798 name: "u16_field",
2799 offset: 4,
2800 datatype: 4,
2801 count: 1,
2802 },
2803 PointFieldView {
2804 name: "i32_field",
2805 offset: 6,
2806 datatype: 5,
2807 count: 1,
2808 },
2809 PointFieldView {
2810 name: "u32_field",
2811 offset: 10,
2812 datatype: 6,
2813 count: 1,
2814 },
2815 PointFieldView {
2816 name: "f32_field",
2817 offset: 14,
2818 datatype: 7,
2819 count: 1,
2820 },
2821 PointFieldView {
2822 name: "f64_field",
2823 offset: 18,
2824 datatype: 8,
2825 count: 1,
2826 },
2827 ];
2828 let point_step = 26u32;
2829 let mut data = vec![0u8; point_step as usize];
2830 data[0] = 0xFE_u8; data[1] = 42;
2832 data[2..4].copy_from_slice(&(-300i16).to_le_bytes());
2833 data[4..6].copy_from_slice(&1000u16.to_le_bytes());
2834 data[6..10].copy_from_slice(&(-100_000i32).to_le_bytes());
2835 data[10..14].copy_from_slice(&3_000_000u32.to_le_bytes());
2836 data[14..18].copy_from_slice(&std::f32::consts::PI.to_le_bytes());
2837 data[18..26].copy_from_slice(&std::f64::consts::E.to_le_bytes());
2838
2839 PointCloud2::new(
2840 Time::new(0, 0),
2841 "test",
2842 1,
2843 1,
2844 &fields,
2845 false,
2846 point_step,
2847 point_step,
2848 &data,
2849 true,
2850 )
2851 .unwrap()
2852 .to_cdr()
2853 }
2854
2855 #[test]
2856 fn dyn_point_signed_and_f64_by_name() {
2857 let cdr = make_all_types_cloud_cdr();
2858 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
2859 let cloud = DynPointCloud::from_pointcloud2(&decoded).unwrap();
2860 let p = cloud.point(0).unwrap();
2861
2862 assert_eq!(p.read_i8("i8_field"), Some(-2));
2863 assert_eq!(p.read_i16("i16_field"), Some(-300));
2864 assert_eq!(p.read_i32("i32_field"), Some(-100_000));
2865 assert_eq!(p.read_f64("f64_field"), Some(std::f64::consts::E));
2866 }
2867
2868 #[test]
2869 fn dyn_point_signed_and_f64_by_descriptor() {
2870 let cdr = make_all_types_cloud_cdr();
2871 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
2872 let cloud = DynPointCloud::from_pointcloud2(&decoded).unwrap();
2873 let p = cloud.point(0).unwrap();
2874
2875 let i8_desc = cloud.field("i8_field").unwrap();
2876 let i16_desc = cloud.field("i16_field").unwrap();
2877 let i32_desc = cloud.field("i32_field").unwrap();
2878 let f64_desc = cloud.field("f64_field").unwrap();
2879
2880 assert_eq!(p.read_i8_at(i8_desc).unwrap(), -2);
2881 assert_eq!(p.read_i16_at(i16_desc).unwrap(), -300);
2882 assert_eq!(p.read_i32_at(i32_desc).unwrap(), -100_000);
2883 assert_eq!(p.read_f64_at(f64_desc).unwrap(), std::f64::consts::E);
2884 }
2885
2886 #[test]
2887 fn dyn_cloud_gather_signed_and_f64() {
2888 let cdr = make_all_types_cloud_cdr();
2889 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
2890 let cloud = DynPointCloud::from_pointcloud2(&decoded).unwrap();
2891
2892 assert_eq!(cloud.gather_i8("i8_field"), Some(vec![-2]));
2893 assert_eq!(cloud.gather_i16("i16_field"), Some(vec![-300]));
2894 assert_eq!(cloud.gather_i32("i32_field"), Some(vec![-100_000]));
2895 assert_eq!(
2896 cloud.gather_f64("f64_field"),
2897 Some(vec![std::f64::consts::E])
2898 );
2899 }
2900
2901 #[test]
2902 fn dyn_point_signed_f64_wrong_type_returns_none() {
2903 let cdr = make_all_types_cloud_cdr();
2904 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
2905 let cloud = DynPointCloud::from_pointcloud2(&decoded).unwrap();
2906 let p = cloud.point(0).unwrap();
2907
2908 assert!(p.read_i8("f32_field").is_none());
2910 assert!(p.read_i16("i8_field").is_none());
2911 assert!(p.read_i32("i16_field").is_none());
2912 assert!(p.read_f64("f32_field").is_none());
2913 assert!(p.read_f32("f64_field").is_none());
2914
2915 assert!(cloud.gather_i8("f64_field").is_none());
2917 assert!(cloud.gather_f64("i8_field").is_none());
2918 assert!(cloud.gather_i16("i32_field").is_none());
2919 assert!(cloud.gather_i32("i16_field").is_none());
2920 }
2921
2922 #[test]
2923 fn dyn_point_at_invalid_descriptor_returns_error() {
2924 let cdr = make_all_types_cloud_cdr();
2925 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
2926 let cloud = DynPointCloud::from_pointcloud2(&decoded).unwrap();
2927 let p = cloud.point(0).unwrap();
2928
2929 let bad = FieldDesc {
2930 name: "fake",
2931 byte_offset: 9999,
2932 field_type: PointFieldType::Float32,
2933 count: 1,
2934 };
2935 assert!(matches!(
2936 p.read_f32_at(&bad),
2937 Err(PointCloudError::FieldAccessOutOfBounds { byte_offset: 9999 })
2938 ));
2939 assert!(p.read_u8_at(&bad).is_err());
2940 assert!(p.read_i8_at(&bad).is_err());
2941 assert!(p.read_u16_at(&bad).is_err());
2942 assert!(p.read_i16_at(&bad).is_err());
2943 assert!(p.read_u32_at(&bad).is_err());
2944 assert!(p.read_i32_at(&bad).is_err());
2945 assert!(p.read_f64_at(&bad).is_err());
2946 }
2947
2948 #[test]
2949 fn dyn_cloud_gather_with_row_padding() {
2950 let fields = [
2951 PointFieldView {
2952 name: "x",
2953 offset: 0,
2954 datatype: 7,
2955 count: 1,
2956 },
2957 PointFieldView {
2958 name: "y",
2959 offset: 4,
2960 datatype: 7,
2961 count: 1,
2962 },
2963 PointFieldView {
2964 name: "z",
2965 offset: 8,
2966 datatype: 7,
2967 count: 1,
2968 },
2969 ];
2970 let point_step = 12u32;
2971 let width = 2u32;
2972 let height = 2u32;
2973 let row_step = 32u32; let total = (row_step * height) as usize;
2975 let mut data = vec![0xFFu8; total];
2976
2977 for row in 0..height {
2979 for col in 0..width {
2980 let off = (row * row_step + col * point_step) as usize;
2981 let val = (row * width + col) as f32;
2982 data[off..off + 4].copy_from_slice(&val.to_le_bytes());
2983 }
2984 }
2985
2986 let pc = PointCloud2::new(
2987 Time::new(0, 0),
2988 "pad",
2989 height,
2990 width,
2991 &fields,
2992 false,
2993 point_step,
2994 row_step,
2995 &data,
2996 true,
2997 )
2998 .unwrap();
2999 let cdr = pc.to_cdr();
3000 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
3001 let cloud = DynPointCloud::from_pointcloud2(&decoded).unwrap();
3002
3003 let gathered = cloud.gather_f32("x").unwrap();
3004 assert_eq!(gathered, vec![0.0, 1.0, 2.0, 3.0]);
3005 }
3006
3007 #[test]
3008 fn dyn_cloud_max_fields_boundary() {
3009 let names: Vec<String> = (0..17).map(|i| format!("f{i}")).collect();
3011 let fields_16: Vec<PointFieldView<'_>> = (0..16)
3012 .map(|i| PointFieldView {
3013 name: &names[i],
3014 offset: i as u32,
3015 datatype: 2, count: 1,
3017 })
3018 .collect();
3019 let data = vec![0u8; 16];
3020 let pc = PointCloud2::new(
3021 Time::new(0, 0),
3022 "max",
3023 1,
3024 1,
3025 &fields_16,
3026 false,
3027 16,
3028 16,
3029 &data,
3030 true,
3031 )
3032 .unwrap();
3033 let cdr = pc.to_cdr();
3034 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
3035 assert!(DynPointCloud::from_pointcloud2(&decoded).is_ok());
3036
3037 let fields_17: Vec<PointFieldView<'_>> = (0..17)
3039 .map(|i| PointFieldView {
3040 name: &names[i],
3041 offset: i as u32,
3042 datatype: 2,
3043 count: 1,
3044 })
3045 .collect();
3046 let data = vec![0u8; 17];
3047 let pc = PointCloud2::new(
3048 Time::new(0, 0),
3049 "max",
3050 1,
3051 1,
3052 &fields_17,
3053 false,
3054 17,
3055 17,
3056 &data,
3057 true,
3058 )
3059 .unwrap();
3060 let cdr = pc.to_cdr();
3061 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
3062 assert!(matches!(
3063 DynPointCloud::from_pointcloud2(&decoded),
3064 Err(PointCloudError::TooManyFields { found: 17 })
3065 ));
3066 }
3067
3068 #[test]
3069 fn dyn_cloud_rejects_row_step_too_small() {
3070 let fields = [PointFieldView {
3071 name: "x",
3072 offset: 0,
3073 datatype: 7,
3074 count: 1,
3075 }];
3076 let data = vec![0u8; 48];
3077 let pc = PointCloud2::new(
3079 Time::new(0, 0),
3080 "bad",
3081 3,
3082 2,
3083 &fields,
3084 false,
3085 4,
3086 2,
3087 &data,
3088 true,
3089 )
3090 .unwrap();
3091 let cdr = pc.to_cdr();
3092 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
3093 assert!(matches!(
3094 DynPointCloud::from_pointcloud2(&decoded),
3095 Err(PointCloudError::InvalidLayout { .. })
3096 ));
3097 }
3098
3099 #[test]
3100 fn static_cloud_rejects_row_step_too_small() {
3101 let fields = [
3102 PointFieldView {
3103 name: "x",
3104 offset: 0,
3105 datatype: 7,
3106 count: 1,
3107 },
3108 PointFieldView {
3109 name: "y",
3110 offset: 4,
3111 datatype: 7,
3112 count: 1,
3113 },
3114 PointFieldView {
3115 name: "z",
3116 offset: 8,
3117 datatype: 7,
3118 count: 1,
3119 },
3120 ];
3121 let data = vec![0u8; 48];
3122 let pc = PointCloud2::new(
3123 Time::new(0, 0),
3124 "bad",
3125 2,
3126 2,
3127 &fields,
3128 false,
3129 12,
3130 4,
3131 &data,
3132 true,
3133 )
3134 .unwrap();
3135 let cdr = pc.to_cdr();
3136 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
3137 assert!(matches!(
3138 PointCloud::<TestXyzPoint>::from_pointcloud2(&decoded),
3139 Err(PointCloudError::InvalidLayout { .. })
3140 ));
3141 }
3142
3143 #[test]
3144 fn static_cloud_iter_with_row_padding() {
3145 let fields = [
3146 PointFieldView {
3147 name: "x",
3148 offset: 0,
3149 datatype: 7,
3150 count: 1,
3151 },
3152 PointFieldView {
3153 name: "y",
3154 offset: 4,
3155 datatype: 7,
3156 count: 1,
3157 },
3158 PointFieldView {
3159 name: "z",
3160 offset: 8,
3161 datatype: 7,
3162 count: 1,
3163 },
3164 ];
3165 let point_step = 12u32;
3166 let width = 2u32;
3167 let height = 2u32;
3168 let row_step = 32u32; let total = (row_step * height) as usize;
3170 let mut data = vec![0xFFu8; total];
3171
3172 for row in 0..height {
3173 for col in 0..width {
3174 let off = (row * row_step + col * point_step) as usize;
3175 let val = (row * width + col) as f32;
3176 data[off..off + 4].copy_from_slice(&val.to_le_bytes());
3177 }
3178 }
3179
3180 let pc = PointCloud2::new(
3181 Time::new(0, 0),
3182 "pad",
3183 height,
3184 width,
3185 &fields,
3186 false,
3187 point_step,
3188 row_step,
3189 &data,
3190 true,
3191 )
3192 .unwrap();
3193 let cdr = pc.to_cdr();
3194 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
3195 let cloud = PointCloud::<TestXyzPoint>::from_pointcloud2(&decoded).unwrap();
3196
3197 let xs: Vec<f32> = cloud.iter().map(|p| p.x).collect();
3199 assert_eq!(xs, vec![0.0, 1.0, 2.0, 3.0]);
3200
3201 assert_eq!(cloud.get(2).unwrap().x, 2.0);
3203 assert_eq!(cloud.get(3).unwrap().x, 3.0);
3204 }
3205
3206 fn make_coercion_cloud_cdr() -> Vec<u8> {
3210 let fields = [
3211 PointFieldView {
3212 name: "fi8",
3213 offset: 0,
3214 datatype: 1,
3215 count: 1,
3216 },
3217 PointFieldView {
3218 name: "fu8",
3219 offset: 1,
3220 datatype: 2,
3221 count: 1,
3222 },
3223 PointFieldView {
3224 name: "fi16",
3225 offset: 2,
3226 datatype: 3,
3227 count: 1,
3228 },
3229 PointFieldView {
3230 name: "fu16",
3231 offset: 4,
3232 datatype: 4,
3233 count: 1,
3234 },
3235 PointFieldView {
3236 name: "fi32",
3237 offset: 6,
3238 datatype: 5,
3239 count: 1,
3240 },
3241 PointFieldView {
3242 name: "fu32",
3243 offset: 10,
3244 datatype: 6,
3245 count: 1,
3246 },
3247 PointFieldView {
3248 name: "ff32",
3249 offset: 14,
3250 datatype: 7,
3251 count: 1,
3252 },
3253 PointFieldView {
3254 name: "ff64",
3255 offset: 18,
3256 datatype: 8,
3257 count: 1,
3258 },
3259 ];
3260 let point_step = 26u32;
3261 let mut data = vec![0u8; point_step as usize];
3262 data[0] = 0xFF_u8; data[1] = 200; data[2..4].copy_from_slice(&(-1000i16).to_le_bytes());
3265 data[4..6].copy_from_slice(&60000u16.to_le_bytes());
3266 data[6..10].copy_from_slice(&(-1_000_000i32).to_le_bytes());
3267 data[10..14].copy_from_slice(&3_000_000_000u32.to_le_bytes());
3268 data[14..18].copy_from_slice(&1.5f32.to_le_bytes());
3269 data[18..26].copy_from_slice(&2.5f64.to_le_bytes());
3270
3271 PointCloud2::new(
3272 Time::new(0, 0),
3273 "coerce",
3274 1,
3275 1,
3276 &fields,
3277 false,
3278 point_step,
3279 point_step,
3280 &data,
3281 true,
3282 )
3283 .unwrap()
3284 .to_cdr()
3285 }
3286
3287 #[test]
3288 fn field_desc_read_as_f64_all_types() {
3289 let cdr = make_coercion_cloud_cdr();
3290 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
3291 let cloud = DynPointCloud::from_pointcloud2(&decoded).unwrap();
3292 let p = cloud.point(0).unwrap();
3293 let data = p.data();
3294
3295 assert_eq!(cloud.field("fi8").unwrap().read_as_f64(data), Some(-1.0));
3296 assert_eq!(cloud.field("fu8").unwrap().read_as_f64(data), Some(200.0));
3297 assert_eq!(
3298 cloud.field("fi16").unwrap().read_as_f64(data),
3299 Some(-1000.0)
3300 );
3301 assert_eq!(
3302 cloud.field("fu16").unwrap().read_as_f64(data),
3303 Some(60000.0)
3304 );
3305 assert_eq!(
3306 cloud.field("fi32").unwrap().read_as_f64(data),
3307 Some(-1_000_000.0)
3308 );
3309 assert_eq!(
3310 cloud.field("fu32").unwrap().read_as_f64(data),
3311 Some(3_000_000_000.0)
3312 );
3313 assert_eq!(
3314 cloud.field("ff32").unwrap().read_as_f64(data),
3315 Some(1.5f32 as f64)
3316 );
3317 assert_eq!(cloud.field("ff64").unwrap().read_as_f64(data), Some(2.5));
3318 }
3319
3320 #[test]
3321 fn field_desc_read_as_f32_all_types() {
3322 let cdr = make_coercion_cloud_cdr();
3323 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
3324 let cloud = DynPointCloud::from_pointcloud2(&decoded).unwrap();
3325 let p = cloud.point(0).unwrap();
3326 let data = p.data();
3327
3328 assert_eq!(cloud.field("fi8").unwrap().read_as_f32(data), Some(-1.0f32));
3329 assert_eq!(
3330 cloud.field("fu8").unwrap().read_as_f32(data),
3331 Some(200.0f32)
3332 );
3333 assert_eq!(
3334 cloud.field("fi16").unwrap().read_as_f32(data),
3335 Some(-1000.0f32)
3336 );
3337 assert_eq!(
3338 cloud.field("fu16").unwrap().read_as_f32(data),
3339 Some(60000.0f32)
3340 );
3341 assert_eq!(
3342 cloud.field("fi32").unwrap().read_as_f32(data),
3343 Some(-1_000_000.0f32)
3344 );
3345 assert_eq!(
3347 cloud.field("fu32").unwrap().read_as_f32(data),
3348 Some(3_000_000_000u32 as f32)
3349 );
3350 assert_eq!(cloud.field("ff32").unwrap().read_as_f32(data), Some(1.5f32));
3351 assert_eq!(cloud.field("ff64").unwrap().read_as_f32(data), Some(2.5f32));
3352 }
3353
3354 #[test]
3355 fn field_desc_read_as_out_of_bounds() {
3356 let bad = FieldDesc {
3357 name: "fake",
3358 byte_offset: 100,
3359 field_type: PointFieldType::Float32,
3360 count: 1,
3361 };
3362 let short_data = [0u8; 4];
3363 assert!(bad.read_as_f64(&short_data).is_none());
3364 assert!(bad.read_as_f32(&short_data).is_none());
3365
3366 let bad_f64 = FieldDesc {
3367 name: "fake64",
3368 byte_offset: 100,
3369 field_type: PointFieldType::Float64,
3370 count: 1,
3371 };
3372 assert!(bad_f64.read_as_f64(&short_data).is_none());
3373 assert!(bad_f64.read_as_f32(&short_data).is_none());
3374
3375 let bad_u8 = FieldDesc {
3377 name: "oob_u8",
3378 byte_offset: 100,
3379 field_type: PointFieldType::Uint8,
3380 count: 1,
3381 };
3382 assert!(bad_u8.read_as_f64(&short_data).is_none());
3383 assert!(bad_u8.read_as_f32(&short_data).is_none());
3384
3385 let bad_i16 = FieldDesc {
3386 name: "oob_i16",
3387 byte_offset: 100,
3388 field_type: PointFieldType::Int16,
3389 count: 1,
3390 };
3391 assert!(bad_i16.read_as_f64(&short_data).is_none());
3392 assert!(bad_i16.read_as_f32(&short_data).is_none());
3393 }
3394
3395 #[test]
3396 fn dynpoint_read_as_convenience() {
3397 let cdr = make_coercion_cloud_cdr();
3398 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
3399 let cloud = DynPointCloud::from_pointcloud2(&decoded).unwrap();
3400 let p = cloud.point(0).unwrap();
3401
3402 assert_eq!(p.read_as_f64("fu16"), Some(60000.0));
3404 assert_eq!(p.read_as_f64("fi32"), Some(-1_000_000.0));
3405 assert_eq!(p.read_as_f64("ff32"), Some(1.5f32 as f64));
3406
3407 assert_eq!(p.read_as_f32("fu8"), Some(200.0f32));
3409 assert_eq!(p.read_as_f32("fi16"), Some(-1000.0f32));
3410 assert_eq!(p.read_as_f32("ff64"), Some(2.5f32));
3411 }
3412
3413 #[test]
3414 fn dynpoint_read_as_missing_field() {
3415 let cdr = make_coercion_cloud_cdr();
3416 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
3417 let cloud = DynPointCloud::from_pointcloud2(&decoded).unwrap();
3418 let p = cloud.point(0).unwrap();
3419
3420 assert!(p.read_as_f64("nonexistent").is_none());
3421 assert!(p.read_as_f32("nonexistent").is_none());
3422 }
3423
3424 #[test]
3425 fn dynpoint_data_and_cloud_accessors() {
3426 let cdr = make_coercion_cloud_cdr();
3427 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
3428 let cloud = DynPointCloud::from_pointcloud2(&decoded).unwrap();
3429 let p = cloud.point(0).unwrap();
3430
3431 assert_eq!(p.data().len(), cloud.point_step());
3432 assert!(p.cloud().field("fi8").is_some());
3433 assert!(p.cloud().field("nonexistent").is_none());
3434 }
3435
3436 #[test]
3437 fn dynpoint_hot_loop_pattern() {
3438 let cdr = make_coercion_cloud_cdr();
3439 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
3440 let cloud = DynPointCloud::from_pointcloud2(&decoded).unwrap();
3441
3442 let f32_desc = cloud.field("ff32").unwrap();
3444 let u16_desc = cloud.field("fu16").unwrap();
3445
3446 for point in cloud.iter() {
3448 assert_eq!(f32_desc.read_as_f64(point.data()), Some(1.5f32 as f64));
3449 assert_eq!(u16_desc.read_as_f32(point.data()), Some(60000.0f32));
3450 }
3451 }
3452
3453 #[test]
3454 fn dyn_cloud_gather_as_f64() {
3455 let cdr = make_coercion_cloud_cdr();
3456 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
3457 let cloud = DynPointCloud::from_pointcloud2(&decoded).unwrap();
3458
3459 assert_eq!(cloud.gather_as_f64("fu16"), Some(vec![60000.0f64]));
3461 assert_eq!(cloud.gather_as_f64("fi32"), Some(vec![-1_000_000.0f64]));
3463 }
3464
3465 #[test]
3466 fn dyn_cloud_gather_as_f32() {
3467 let cdr = make_coercion_cloud_cdr();
3468 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
3469 let cloud = DynPointCloud::from_pointcloud2(&decoded).unwrap();
3470
3471 assert_eq!(
3473 cloud.gather_as_f32("fu32"),
3474 Some(vec![3_000_000_000u32 as f32])
3475 );
3476 assert_eq!(cloud.gather_as_f32("ff64"), Some(vec![2.5f32]));
3478 }
3479
3480 #[test]
3481 fn dyn_cloud_gather_as_missing() {
3482 let cdr = make_coercion_cloud_cdr();
3483 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
3484 let cloud = DynPointCloud::from_pointcloud2(&decoded).unwrap();
3485
3486 assert!(cloud.gather_as_f64("nonexistent").is_none());
3487 assert!(cloud.gather_as_f32("nonexistent").is_none());
3488 }
3489
3490 #[test]
3491 fn dyn_cloud_gather_as_multipoint_with_row_padding() {
3492 let fields = [
3494 PointFieldView {
3495 name: "val",
3496 offset: 0,
3497 datatype: 4,
3498 count: 1,
3499 }, ];
3501 let point_step = 4u32; let width = 2u32;
3503 let height = 2u32;
3504 let row_step = 16u32; let total = (row_step * height) as usize;
3506 let mut data = vec![0xFFu8; total];
3507
3508 let values: [u16; 4] = [100, 200, 300, 400];
3509 for row in 0..height {
3510 for col in 0..width {
3511 let off = (row * row_step + col * point_step) as usize;
3512 let idx = (row * width + col) as usize;
3513 data[off..off + 2].copy_from_slice(&values[idx].to_le_bytes());
3514 }
3515 }
3516
3517 let pc = PointCloud2::new(
3518 Time::new(0, 0),
3519 "multi",
3520 height,
3521 width,
3522 &fields,
3523 false,
3524 point_step,
3525 row_step,
3526 &data,
3527 true,
3528 )
3529 .unwrap();
3530 let cdr = pc.to_cdr();
3531 let decoded = PointCloud2::from_cdr(&cdr).unwrap();
3532 let cloud = DynPointCloud::from_pointcloud2(&decoded).unwrap();
3533
3534 assert_eq!(
3536 cloud.gather_as_f64("val"),
3537 Some(vec![100.0, 200.0, 300.0, 400.0])
3538 );
3539
3540 assert_eq!(
3542 cloud.gather_as_f32("val"),
3543 Some(vec![100.0f32, 200.0f32, 300.0f32, 400.0f32])
3544 );
3545 }
3546
3547 #[test]
3548 fn field_desc_read_as_f32_infinity_narrowing() {
3549 let desc = FieldDesc {
3551 name: "big",
3552 byte_offset: 0,
3553 field_type: PointFieldType::Float64,
3554 count: 1,
3555 };
3556 let big = 1e40_f64.to_le_bytes();
3557 assert_eq!(desc.read_as_f32(&big), Some(f32::INFINITY));
3558
3559 let neg_big = (-1e40_f64).to_le_bytes();
3560 assert_eq!(desc.read_as_f32(&neg_big), Some(f32::NEG_INFINITY));
3561 }
3562}