1#![allow(dead_code)]
8pub mod distance;
9pub mod interpolation;
10pub mod matching;
11pub mod structures;
12pub mod transforms;
13
14use crate::{Result, VisionError};
15use scirs2_core::ndarray::{Array1, Array2, ArrayView2};
16use scirs2_spatial::distance::{cosine, euclidean, manhattan, EuclideanDistance};
17use scirs2_spatial::kdtree::KDTree;
18use scirs2_spatial::transform::{RigidTransform, Rotation};
19use torsh_tensor::Tensor;
20
21#[derive(Debug, Clone)]
23pub struct SpatialConfig {
24 pub distance_metric: DistanceMetric,
26 pub k_neighbors: usize,
28 pub use_simd: bool,
30 pub tolerance: f64,
32}
33
34impl Default for SpatialConfig {
35 fn default() -> Self {
36 Self {
37 distance_metric: DistanceMetric::Euclidean,
38 k_neighbors: 5,
39 use_simd: true,
40 tolerance: 1e-8,
41 }
42 }
43}
44
45#[derive(Debug, Clone, Copy)]
47pub enum DistanceMetric {
48 Euclidean,
49 Manhattan,
50 Cosine,
51 Chebyshev,
52 Minkowski(f64),
53}
54
55impl DistanceMetric {
56 pub fn compute(&self, a: &ArrayView2<f64>, b: &ArrayView2<f64>) -> Result<f64> {
58 let distance = match self {
59 DistanceMetric::Euclidean => euclidean(
60 a.as_slice().expect("slice conversion should succeed"),
61 b.as_slice().expect("slice conversion should succeed"),
62 ),
63 DistanceMetric::Manhattan => manhattan(
64 a.as_slice().expect("slice conversion should succeed"),
65 b.as_slice().expect("slice conversion should succeed"),
66 ),
67 DistanceMetric::Cosine => cosine(
68 a.as_slice().expect("slice conversion should succeed"),
69 b.as_slice().expect("slice conversion should succeed"),
70 ),
71 _ => euclidean(
72 a.as_slice().expect("slice conversion should succeed"),
73 b.as_slice().expect("slice conversion should succeed"),
74 ), };
76
77 Ok(distance)
78 }
79}
80
81#[derive(Debug, Clone)]
83pub struct SpatialPoint {
84 pub coordinates: Array1<f64>,
85 pub data: Option<Tensor>,
86 pub id: Option<usize>,
87}
88
89impl SpatialPoint {
90 pub fn new(coordinates: Array1<f64>) -> Self {
92 Self {
93 coordinates,
94 data: None,
95 id: None,
96 }
97 }
98
99 pub fn with_data(coordinates: Array1<f64>, data: Tensor) -> Self {
101 Self {
102 coordinates,
103 data: Some(data),
104 id: None,
105 }
106 }
107
108 pub fn dimension(&self) -> usize {
110 self.coordinates.len()
111 }
112}
113
114#[derive(Debug, Clone)]
116pub struct FeatureMatch {
117 pub query_idx: usize,
118 pub target_idx: usize,
119 pub distance: f64,
120 pub confidence: f64,
121}
122
123impl FeatureMatch {
124 pub fn new(query_idx: usize, target_idx: usize, distance: f64) -> Self {
126 Self {
127 query_idx,
128 target_idx,
129 distance,
130 confidence: 1.0 / (1.0 + distance),
131 }
132 }
133}
134
135#[derive(Debug, Clone)]
137pub struct TransformResult {
138 pub rotation: Rotation,
139 pub translation: Array1<f64>,
140 pub scale: f64,
141 pub error: f64,
142}
143
144pub struct SpatialProcessor {
146 config: SpatialConfig,
147 kdtree: Option<KDTree<f32, EuclideanDistance<f32>>>,
148}
149
150impl SpatialProcessor {
151 pub fn new(config: SpatialConfig) -> Self {
153 Self {
154 config,
155 kdtree: None,
156 }
157 }
158
159 pub fn build_index(&mut self, points: &Array2<f64>) -> Result<()> {
161 let points_f32 = points.mapv(|x| x as f32);
163
164 let kdtree = KDTree::new(&points_f32)
165 .map_err(|e| VisionError::Other(anyhow::anyhow!("Failed to build KDTree: {}", e)))?;
166
167 self.kdtree = Some(kdtree);
168 Ok(())
169 }
170
171 pub fn find_neighbors(&self, query: &ArrayView2<f64>) -> Result<Vec<Vec<usize>>> {
173 let kdtree = self
174 .kdtree
175 .as_ref()
176 .ok_or_else(|| VisionError::InvalidInput("Spatial index not built".to_string()))?;
177
178 let mut all_neighbors = Vec::new();
179
180 for query_point in query.outer_iter() {
181 let (indices, _distances) = kdtree
182 .query(
183 &query_point
184 .as_slice()
185 .expect("slice conversion should succeed")
186 .iter()
187 .map(|&x| x as f32)
188 .collect::<Vec<f32>>(),
189 self.config.k_neighbors,
190 )
191 .map_err(|e| {
192 VisionError::Other(anyhow::anyhow!("Neighbor search failed: {}", e))
193 })?;
194 all_neighbors.push(indices);
195 }
196
197 Ok(all_neighbors)
198 }
199
200 pub fn match_features(
202 &self,
203 descriptors1: &Array2<f64>,
204 descriptors2: &Array2<f64>,
205 ) -> Result<Vec<FeatureMatch>> {
206 let descriptors2_f32 = descriptors2.mapv(|x| x as f32);
208 let kdtree = KDTree::new(&descriptors2_f32)
209 .map_err(|e| VisionError::Other(anyhow::anyhow!("Failed to build KDTree: {}", e)))?;
210
211 let mut matches = Vec::new();
212
213 for (i, descriptor) in descriptors1.outer_iter().enumerate() {
214 let (indices, distances) = kdtree
215 .query(
216 &descriptor
217 .as_slice()
218 .expect("slice conversion should succeed")
219 .iter()
220 .map(|&x| x as f32)
221 .collect::<Vec<f32>>(),
222 2,
223 )
224 .map_err(|e| {
225 VisionError::Other(anyhow::anyhow!("Feature matching failed: {}", e))
226 })?;
227
228 if distances.len() >= 2 && distances[1] > 0.0 {
230 let ratio = distances[0] / distances[1];
231 if ratio < 0.7 {
232 matches.push(FeatureMatch::new(i, indices[0], distances[0] as f64));
233 }
234 }
235 }
236
237 Ok(matches)
238 }
239
240 pub fn estimate_transform(
242 &self,
243 source: &Array2<f64>,
244 _target: &Array2<f64>,
245 ) -> Result<TransformResult> {
246 let rotation = Rotation::identity();
249 let translation = Array1::zeros(source.ncols());
250
251 Ok(TransformResult {
252 rotation,
253 translation,
254 scale: 1.0,
255 error: 0.0,
256 })
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263 use scirs2_core::ndarray::arr2;
264
265 #[test]
266 fn test_spatial_processor_creation() {
267 let config = SpatialConfig::default();
268 let processor = SpatialProcessor::new(config);
269 assert!(processor.kdtree.is_none());
270 }
271
272 #[test]
273 fn test_spatial_point_creation() {
274 let coords = Array1::from(vec![1.0, 2.0, 3.0]);
275 let point = SpatialPoint::new(coords.clone());
276 assert_eq!(point.dimension(), 3);
277 assert_eq!(point.coordinates, coords);
278 }
279
280 #[test]
281 fn test_feature_match_creation() {
282 let match_result = FeatureMatch::new(0, 1, 0.5);
283 assert_eq!(match_result.query_idx, 0);
284 assert_eq!(match_result.target_idx, 1);
285 assert_eq!(match_result.distance, 0.5);
286 assert!(match_result.confidence > 0.0);
287 }
288
289 #[test]
290 fn test_distance_metric() {
291 let a = arr2(&[[1.0, 2.0], [3.0, 4.0]]);
292 let b = arr2(&[[2.0, 3.0], [4.0, 5.0]]);
293
294 let metric = DistanceMetric::Euclidean;
295 let distance = metric.compute(&a.view(), &b.view());
296 assert!(distance.is_ok());
297 }
298}