Skip to main content

ruvector_robotics/perception/
config.rs

1//! Configuration types for the perception pipeline.
2//!
3//! Provides tuning knobs for scene-graph construction, obstacle detection,
4//! and the top-level perception configuration that bundles them together.
5
6use serde::{Deserialize, Serialize};
7
8// ---------------------------------------------------------------------------
9// Scene-graph configuration
10// ---------------------------------------------------------------------------
11
12/// Tuning parameters for scene-graph construction from point clouds.
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14pub struct SceneGraphConfig {
15    /// Maximum distance between two points to be considered part of the same
16    /// cluster (metres).
17    pub cluster_radius: f64,
18    /// Minimum number of points required to form a valid cluster / object.
19    pub min_cluster_size: usize,
20    /// Hard cap on the number of objects the builder will emit.
21    pub max_objects: usize,
22    /// Maximum centre-to-centre distance for two objects to be connected by an
23    /// edge in the scene graph (metres).
24    pub edge_distance_threshold: f64,
25}
26
27impl Default for SceneGraphConfig {
28    fn default() -> Self {
29        Self {
30            cluster_radius: 0.5,
31            min_cluster_size: 3,
32            max_objects: 256,
33            edge_distance_threshold: 5.0,
34        }
35    }
36}
37
38// ---------------------------------------------------------------------------
39// Obstacle configuration
40// ---------------------------------------------------------------------------
41
42/// Tuning parameters for the obstacle detector.
43#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
44pub struct ObstacleConfig {
45    /// Minimum number of points that must fall inside a cluster to be
46    /// considered an obstacle.
47    pub min_obstacle_size: usize,
48    /// Maximum range from the robot within which obstacles are detected
49    /// (metres).
50    pub max_detection_range: f64,
51    /// Extra padding added around every detected obstacle (metres).
52    pub safety_margin: f64,
53}
54
55impl Default for ObstacleConfig {
56    fn default() -> Self {
57        Self {
58            min_obstacle_size: 3,
59            max_detection_range: 20.0,
60            safety_margin: 0.2,
61        }
62    }
63}
64
65// ---------------------------------------------------------------------------
66// Top-level perception configuration
67// ---------------------------------------------------------------------------
68
69/// Aggregated configuration for the full perception pipeline.
70#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
71pub struct PerceptionConfig {
72    pub scene_graph: SceneGraphConfig,
73    pub obstacle: ObstacleConfig,
74}
75
76// ---------------------------------------------------------------------------
77// Tests
78// ---------------------------------------------------------------------------
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn test_scene_graph_config_defaults() {
86        let cfg = SceneGraphConfig::default();
87        assert!((cfg.cluster_radius - 0.5).abs() < f64::EPSILON);
88        assert_eq!(cfg.min_cluster_size, 3);
89        assert_eq!(cfg.max_objects, 256);
90        assert!((cfg.edge_distance_threshold - 5.0).abs() < f64::EPSILON);
91    }
92
93    #[test]
94    fn test_obstacle_config_defaults() {
95        let cfg = ObstacleConfig::default();
96        assert_eq!(cfg.min_obstacle_size, 3);
97        assert!((cfg.max_detection_range - 20.0).abs() < f64::EPSILON);
98        assert!((cfg.safety_margin - 0.2).abs() < f64::EPSILON);
99    }
100
101    #[test]
102    fn test_perception_config_defaults() {
103        let cfg = PerceptionConfig::default();
104        assert_eq!(cfg.scene_graph, SceneGraphConfig::default());
105        assert_eq!(cfg.obstacle, ObstacleConfig::default());
106    }
107
108    #[test]
109    fn test_scene_graph_config_serde_roundtrip() {
110        let cfg = SceneGraphConfig {
111            cluster_radius: 1.0,
112            min_cluster_size: 5,
113            max_objects: 128,
114            edge_distance_threshold: 3.0,
115        };
116        let json = serde_json::to_string(&cfg).unwrap();
117        let restored: SceneGraphConfig = serde_json::from_str(&json).unwrap();
118        assert_eq!(cfg, restored);
119    }
120
121    #[test]
122    fn test_obstacle_config_serde_roundtrip() {
123        let cfg = ObstacleConfig {
124            min_obstacle_size: 10,
125            max_detection_range: 50.0,
126            safety_margin: 0.5,
127        };
128        let json = serde_json::to_string(&cfg).unwrap();
129        let restored: ObstacleConfig = serde_json::from_str(&json).unwrap();
130        assert_eq!(cfg, restored);
131    }
132
133    #[test]
134    fn test_perception_config_serde_roundtrip() {
135        let cfg = PerceptionConfig::default();
136        let json = serde_json::to_string_pretty(&cfg).unwrap();
137        let restored: PerceptionConfig = serde_json::from_str(&json).unwrap();
138        assert_eq!(cfg, restored);
139    }
140
141    #[test]
142    fn test_config_clone_equality() {
143        let a = PerceptionConfig::default();
144        let b = a.clone();
145        assert_eq!(a, b);
146    }
147
148    #[test]
149    fn test_config_debug_format() {
150        let cfg = PerceptionConfig::default();
151        let dbg = format!("{:?}", cfg);
152        assert!(dbg.contains("PerceptionConfig"));
153        assert!(dbg.contains("SceneGraphConfig"));
154        assert!(dbg.contains("ObstacleConfig"));
155    }
156
157    #[test]
158    fn test_custom_perception_config() {
159        let cfg = PerceptionConfig {
160            scene_graph: SceneGraphConfig {
161                cluster_radius: 2.0,
162                min_cluster_size: 10,
163                max_objects: 64,
164                edge_distance_threshold: 10.0,
165            },
166            obstacle: ObstacleConfig {
167                min_obstacle_size: 5,
168                max_detection_range: 100.0,
169                safety_margin: 1.0,
170            },
171        };
172        assert!((cfg.scene_graph.cluster_radius - 2.0).abs() < f64::EPSILON);
173        assert_eq!(cfg.obstacle.min_obstacle_size, 5);
174    }
175}