mapping_algorithms/point_clouds/
mod.rs

1// SPDX-License-Identifier: MIT
2/*
3 * Copyright (c) [2023 - Present] Emily Matheys <emilymatt96@gmail.com>
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to deal
7 * in the Software without restriction, including without limitation the rights
8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included in all
13 * copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 * SOFTWARE.
22 */
23
24pub use downsample::downsample_point_cloud_voxel;
25pub use icp::{
26    icp, icp_iteration, ICPConfiguration, ICPConfigurationBuilder, ICPError, ICPResult, ICPSuccess,
27};
28pub use lex_sort::{lex_sort, lex_sort_in_place, lex_sort_ref};
29pub use nearest_neighbour::find_nearest_neighbour_naive;
30
31use nalgebra::{
32    AbstractRotation, ClosedAddAssign, ClosedDivAssign, Isometry, Point, RealField, Scalar,
33};
34use num_traits::{AsPrimitive, Zero};
35
36use crate::{array, Vec};
37
38mod downsample;
39mod icp;
40mod lex_sort;
41mod nearest_neighbour;
42
43#[cfg(feature = "pregenerated")]
44#[doc = "Contains pregenerated functions for single precision point cloud mapping-algorithms."]
45pub mod single_precision {
46    pub use super::icp::single_precision::*;
47}
48
49#[cfg(feature = "pregenerated")]
50#[doc = "Contains pregenerated functions for double precision point cloud mapping-algorithms."]
51pub mod double_precision {
52    pub use super::icp::double_precision::*;
53}
54
55/// Calculates the mean(centroid) of the point cloud.
56///
57/// # Arguments
58/// * points: a slice of [`Point`], representing the point cloud.
59///
60/// # Generics
61/// * `T`: Either an [`f32`] or [`f64`].
62/// * `N`: A const usize, representing the number of dimensions in the points.
63///
64/// # Returns
65/// A [`Point`], representing the point cloud centroid.
66/// Returns Point::default() if point cloud is empty.
67#[inline]
68#[cfg_attr(
69    feature = "tracing",
70    tracing::instrument("Calculate Mean Point", skip_all)
71)]
72pub fn calculate_point_cloud_center<T, const N: usize>(points: &[Point<T, N>]) -> Point<T, N>
73where
74    T: ClosedAddAssign + ClosedDivAssign + Copy + Scalar + Zero,
75    usize: AsPrimitive<T>,
76{
77    if points.is_empty() {
78        return Point::default();
79    }
80
81    points
82        .iter()
83        .fold(Point::<T, N>::from([T::zero(); N]), |acc, it| {
84            Point::from(acc.coords + it.coords)
85        })
86        / points.len().as_()
87}
88
89/// Generates a randomized points cloud within a specified spherical range.
90///
91/// # Arguments
92/// * `num_points`: a [`usize`], specifying the amount of points to generate
93/// * `range`: a [`crate::ops::RangeInclusive`] specifying the normal distribution of points.
94///
95/// # Generics
96/// * `T`: Either an [`f32`] or [`f64`].
97/// * `N`: A const usize, representing the number of dimensions to use.
98///
99/// # Returns
100/// A [`Vec`] of [`Point`] representing the point cloud.
101#[cfg_attr(
102    feature = "tracing",
103    tracing::instrument("Generate Randomized Point Cloud", skip_all, level = "debug")
104)]
105pub fn generate_point_cloud<T, const N: usize>(
106    num_points: usize,
107    ranges: [crate::ops::RangeInclusive<T>; N],
108) -> Vec<Point<T, N>>
109where
110    T: PartialOrd + rand::distributions::uniform::SampleUniform + Scalar,
111{
112    use rand::{Rng, SeedableRng};
113    let mut rng = rand::rngs::SmallRng::seed_from_u64(3765665954583626552);
114
115    (0..num_points)
116        .map(|_| Point::from(array::from_fn(|idx| rng.gen_range(ranges[idx].clone()))))
117        .collect()
118} // Just calls a different function a number of times, no specific test needed
119
120/// Transform a point cloud, returning a transformed copy.
121/// This function does not mutate the original point cloud.
122///
123/// # Arguments
124/// * `source_points`: a slice of [`Point`], representing the point cloud
125/// * `isometry_matrix`: a transform that implements [`AbstractRotation`], to use for the transformation.
126///
127/// # Generics
128/// * `T`: Either an [`f32`] or [`f64`].
129/// * `N`: A const usize, either `2` or `3`.
130/// * `R`: An [`AbstractRotation`] for `T` and `N`.
131///
132/// # Returns
133/// A [`Vec`] of [`Point`] containing the transformed point cloud.
134#[inline]
135#[cfg_attr(
136    feature = "tracing",
137    tracing::instrument("Transform Point Cloud", skip_all)
138)]
139pub fn transform_point_cloud<T, const N: usize, R>(
140    source_points: &[Point<T, N>],
141    isometry_matrix: Isometry<T, R, N>,
142) -> Vec<Point<T, N>>
143where
144    T: RealField,
145    R: AbstractRotation<T, N>,
146{
147    source_points
148        .iter()
149        .map(|point| isometry_matrix.transform_point(point))
150        .collect()
151} // Just calls a different function a number of times, no specific test needed
152
153#[cfg(test)]
154mod tests {
155    use nalgebra::{Point2, Point3};
156
157    use super::*;
158
159    #[test]
160    fn test_empty_point_cloud_center() {
161        assert_eq!(calculate_point_cloud_center(&[]), Point2::new(0.0, 0.0));
162    }
163
164    #[test]
165    fn test_calculate_point_cloud_center() {
166        let point_cloud = [
167            Point3::new(1.0, 2.0, 3.0),
168            Point3::new(2.0, 3.0, 4.0),
169            Point3::new(3.0, 4.0, 5.0),
170            Point3::new(-2.0, -1.0, 0.0),
171            Point3::new(-5.0, -2.0, -3.0),
172            Point3::new(1.0, 0.0, 0.0),
173        ];
174
175        assert_eq!(
176            calculate_point_cloud_center(point_cloud.as_slice()),
177            Point3::new(0.0, 1.0, 1.5)
178        );
179    }
180}