Skip to main content

spatialrust_io/pcd/
schema.rs

1use spatialrust_core::{DType, FieldSemantic, PointField, PointSchema};
2
3use crate::error::{pcd_format, IoError};
4
5/// PCD scalar type token.
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub enum PcdType {
8    /// Signed integer.
9    I,
10    /// Unsigned integer.
11    U,
12    /// Floating point.
13    F,
14}
15
16/// One PCD field specification from the header.
17#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct PcdFieldSpec {
19    /// Field name.
20    pub name: String,
21    /// Size in bytes of one scalar component.
22    pub size: usize,
23    /// Scalar type token.
24    pub kind: PcdType,
25    /// Number of scalar components.
26    pub count: usize,
27}
28
29impl PcdFieldSpec {
30    /// Returns the total byte size of this field for one point.
31    #[must_use]
32    pub fn byte_size(&self) -> usize {
33        self.size * self.count
34    }
35
36    /// Maps this field to a SpatialRust dtype.
37    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/// Maps a PCD field name to a SpatialRust semantic.
60#[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
81/// Builds a SpatialRust schema from PCD field specs.
82pub 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}