spatialrust_io/pcd/
schema.rs1use spatialrust_core::{DType, FieldSemantic, PointField, PointSchema};
2
3use crate::error::{pcd_format, IoError};
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub enum PcdType {
8 I,
10 U,
12 F,
14}
15
16#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct PcdFieldSpec {
19 pub name: String,
21 pub size: usize,
23 pub kind: PcdType,
25 pub count: usize,
27}
28
29impl PcdFieldSpec {
30 #[must_use]
32 pub fn byte_size(&self) -> usize {
33 self.size * self.count
34 }
35
36 pub fn dtype(&self) -> Result<DType, IoError> {
38 if self.count != 1 {
39 return Err(pcd_format(format!(
40 "field `{}` has unsupported COUNT {}",
41 self.name, self.count
42 )));
43 }
44 match (self.kind, self.size) {
45 (PcdType::F, 4) => Ok(DType::F32),
46 (PcdType::F, 8) => Ok(DType::F64),
47 (PcdType::I, 4) => Ok(DType::I32),
48 (PcdType::U, 1) => Ok(DType::U8),
49 (PcdType::U, 2) => Ok(DType::U16),
50 (PcdType::U, 4) => Ok(DType::U32),
51 _ => Err(pcd_format(format!(
52 "unsupported PCD field `{}` with TYPE {:?} and SIZE {}",
53 self.name, self.kind, self.size
54 ))),
55 }
56 }
57}
58
59#[must_use]
61pub fn infer_field_semantic(name: &str) -> FieldSemantic {
62 match name.to_ascii_lowercase().as_str() {
63 "x" => FieldSemantic::PositionX,
64 "y" => FieldSemantic::PositionY,
65 "z" => FieldSemantic::PositionZ,
66 "normal_x" | "nx" => FieldSemantic::NormalX,
67 "normal_y" | "ny" => FieldSemantic::NormalY,
68 "normal_z" | "nz" => FieldSemantic::NormalZ,
69 "intensity" | "i" => FieldSemantic::Intensity,
70 "curvature" => FieldSemantic::Curvature,
71 "ring" => FieldSemantic::Ring,
72 "timestamp" | "t" | "time_offset" => FieldSemantic::TimeOffset,
73 "label" => FieldSemantic::Label,
74 "r" | "red" => FieldSemantic::ColorR,
75 "g" | "green" => FieldSemantic::ColorG,
76 "b" | "blue" => FieldSemantic::ColorB,
77 _ => FieldSemantic::Unknown,
78 }
79}
80
81pub fn schema_from_pcd_fields(fields: &[PcdFieldSpec]) -> Result<PointSchema, IoError> {
83 let mut schema = PointSchema::new();
84 for field in fields {
85 if field.name.eq_ignore_ascii_case("rgb") {
86 schema = schema
87 .with_field(PointField::scalar("r", FieldSemantic::ColorR, DType::U8))
88 .with_field(PointField::scalar("g", FieldSemantic::ColorG, DType::U8))
89 .with_field(PointField::scalar("b", FieldSemantic::ColorB, DType::U8));
90 continue;
91 }
92 let dtype = field.dtype()?;
93 let semantic = infer_field_semantic(&field.name);
94 schema = schema.with_field(PointField::scalar(field.name.clone(), semantic, dtype));
95 }
96 Ok(schema)
97}
98
99#[cfg(test)]
100mod tests {
101 use super::{infer_field_semantic, schema_from_pcd_fields, PcdFieldSpec, PcdType};
102 use spatialrust_core::FieldSemantic;
103
104 #[test]
105 fn maps_xyz_semantics() {
106 assert_eq!(infer_field_semantic("x"), FieldSemantic::PositionX);
107 assert_eq!(infer_field_semantic("intensity"), FieldSemantic::Intensity);
108 }
109
110 #[test]
111 fn expands_rgb_field() {
112 let fields = vec![PcdFieldSpec { name: "rgb".into(), size: 4, kind: PcdType::F, count: 1 }];
113 let schema = schema_from_pcd_fields(&fields).unwrap();
114 assert_eq!(schema.len(), 3);
115 }
116}