#![allow(dead_code)]
pub mod distance;
pub mod interpolation;
pub mod matching;
pub mod structures;
pub mod transforms;
use crate::{Result, VisionError};
use scirs2_core::ndarray::{Array1, Array2, ArrayView2};
use scirs2_spatial::distance::{cosine, euclidean, manhattan, EuclideanDistance};
use scirs2_spatial::kdtree::KDTree;
use scirs2_spatial::transform::{RigidTransform, Rotation};
use torsh_tensor::Tensor;
#[derive(Debug, Clone)]
pub struct SpatialConfig {
pub distance_metric: DistanceMetric,
pub k_neighbors: usize,
pub use_simd: bool,
pub tolerance: f64,
}
impl Default for SpatialConfig {
fn default() -> Self {
Self {
distance_metric: DistanceMetric::Euclidean,
k_neighbors: 5,
use_simd: true,
tolerance: 1e-8,
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum DistanceMetric {
Euclidean,
Manhattan,
Cosine,
Chebyshev,
Minkowski(f64),
}
impl DistanceMetric {
pub fn compute(&self, a: &ArrayView2<f64>, b: &ArrayView2<f64>) -> Result<f64> {
let distance = match self {
DistanceMetric::Euclidean => euclidean(
a.as_slice().expect("slice conversion should succeed"),
b.as_slice().expect("slice conversion should succeed"),
),
DistanceMetric::Manhattan => manhattan(
a.as_slice().expect("slice conversion should succeed"),
b.as_slice().expect("slice conversion should succeed"),
),
DistanceMetric::Cosine => cosine(
a.as_slice().expect("slice conversion should succeed"),
b.as_slice().expect("slice conversion should succeed"),
),
_ => euclidean(
a.as_slice().expect("slice conversion should succeed"),
b.as_slice().expect("slice conversion should succeed"),
), };
Ok(distance)
}
}
#[derive(Debug, Clone)]
pub struct SpatialPoint {
pub coordinates: Array1<f64>,
pub data: Option<Tensor>,
pub id: Option<usize>,
}
impl SpatialPoint {
pub fn new(coordinates: Array1<f64>) -> Self {
Self {
coordinates,
data: None,
id: None,
}
}
pub fn with_data(coordinates: Array1<f64>, data: Tensor) -> Self {
Self {
coordinates,
data: Some(data),
id: None,
}
}
pub fn dimension(&self) -> usize {
self.coordinates.len()
}
}
#[derive(Debug, Clone)]
pub struct FeatureMatch {
pub query_idx: usize,
pub target_idx: usize,
pub distance: f64,
pub confidence: f64,
}
impl FeatureMatch {
pub fn new(query_idx: usize, target_idx: usize, distance: f64) -> Self {
Self {
query_idx,
target_idx,
distance,
confidence: 1.0 / (1.0 + distance),
}
}
}
#[derive(Debug, Clone)]
pub struct TransformResult {
pub rotation: Rotation,
pub translation: Array1<f64>,
pub scale: f64,
pub error: f64,
}
pub struct SpatialProcessor {
config: SpatialConfig,
kdtree: Option<KDTree<f32, EuclideanDistance<f32>>>,
}
impl SpatialProcessor {
pub fn new(config: SpatialConfig) -> Self {
Self {
config,
kdtree: None,
}
}
pub fn build_index(&mut self, points: &Array2<f64>) -> Result<()> {
let points_f32 = points.mapv(|x| x as f32);
let kdtree = KDTree::new(&points_f32)
.map_err(|e| VisionError::Other(anyhow::anyhow!("Failed to build KDTree: {}", e)))?;
self.kdtree = Some(kdtree);
Ok(())
}
pub fn find_neighbors(&self, query: &ArrayView2<f64>) -> Result<Vec<Vec<usize>>> {
let kdtree = self
.kdtree
.as_ref()
.ok_or_else(|| VisionError::InvalidInput("Spatial index not built".to_string()))?;
let mut all_neighbors = Vec::new();
for query_point in query.outer_iter() {
let (indices, _distances) = kdtree
.query(
&query_point
.as_slice()
.expect("slice conversion should succeed")
.iter()
.map(|&x| x as f32)
.collect::<Vec<f32>>(),
self.config.k_neighbors,
)
.map_err(|e| {
VisionError::Other(anyhow::anyhow!("Neighbor search failed: {}", e))
})?;
all_neighbors.push(indices);
}
Ok(all_neighbors)
}
pub fn match_features(
&self,
descriptors1: &Array2<f64>,
descriptors2: &Array2<f64>,
) -> Result<Vec<FeatureMatch>> {
let descriptors2_f32 = descriptors2.mapv(|x| x as f32);
let kdtree = KDTree::new(&descriptors2_f32)
.map_err(|e| VisionError::Other(anyhow::anyhow!("Failed to build KDTree: {}", e)))?;
let mut matches = Vec::new();
for (i, descriptor) in descriptors1.outer_iter().enumerate() {
let (indices, distances) = kdtree
.query(
&descriptor
.as_slice()
.expect("slice conversion should succeed")
.iter()
.map(|&x| x as f32)
.collect::<Vec<f32>>(),
2,
)
.map_err(|e| {
VisionError::Other(anyhow::anyhow!("Feature matching failed: {}", e))
})?;
if distances.len() >= 2 && distances[1] > 0.0 {
let ratio = distances[0] / distances[1];
if ratio < 0.7 {
matches.push(FeatureMatch::new(i, indices[0], distances[0] as f64));
}
}
}
Ok(matches)
}
pub fn estimate_transform(
&self,
source: &Array2<f64>,
_target: &Array2<f64>,
) -> Result<TransformResult> {
let rotation = Rotation::identity();
let translation = Array1::zeros(source.ncols());
Ok(TransformResult {
rotation,
translation,
scale: 1.0,
error: 0.0,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::arr2;
#[test]
fn test_spatial_processor_creation() {
let config = SpatialConfig::default();
let processor = SpatialProcessor::new(config);
assert!(processor.kdtree.is_none());
}
#[test]
fn test_spatial_point_creation() {
let coords = Array1::from(vec![1.0, 2.0, 3.0]);
let point = SpatialPoint::new(coords.clone());
assert_eq!(point.dimension(), 3);
assert_eq!(point.coordinates, coords);
}
#[test]
fn test_feature_match_creation() {
let match_result = FeatureMatch::new(0, 1, 0.5);
assert_eq!(match_result.query_idx, 0);
assert_eq!(match_result.target_idx, 1);
assert_eq!(match_result.distance, 0.5);
assert!(match_result.confidence > 0.0);
}
#[test]
fn test_distance_metric() {
let a = arr2(&[[1.0, 2.0], [3.0, 4.0]]);
let b = arr2(&[[2.0, 3.0], [4.0, 5.0]]);
let metric = DistanceMetric::Euclidean;
let distance = metric.compute(&a.view(), &b.view());
assert!(distance.is_ok());
}
}