mapping_algorithms/point_clouds/icp/types.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
24use nalgebra::{AbstractRotation, Isometry, Point, Scalar};
25use num_traits::AsPrimitive;
26
27use crate::Debug;
28
29/// Contains the resulting transform, the resulting Mean Squared Error, and the number of iterations taken for a successful ICP convergence.
30#[derive(Debug)]
31pub struct ICPSuccess<T: Scalar, R: AbstractRotation<T, N>, const N: usize> {
32 /// An isometric matrix, containing the translation and rotation between the point sets.
33 /// In 2D space, its rotation component would be a [`UnitComplex`](nalgebra::UnitComplex), in 3D space it would be a [`UnitQuaternion`](nalgebra::UnitQuaternion).
34 pub transform: Isometry<T, R, N>,
35 /// Mean Squared Error, this is the distances between each point in `points_a` and its corresponding point in `points_b`,
36 /// This can be used to determine whether the ICP converged correctly, or simply on its local minimum.
37 pub mse: T,
38 /// The amount of iterations passed until convergence.
39 pub iteration_num: usize,
40}
41
42/// An error type containing the various errors that might arise during an ICP algorithm, when compiling with the `std` feature, it will also derive [`thiserror::Error`].
43#[derive(Copy, Clone, Debug, Eq, PartialEq)]
44#[cfg_attr(feature = "std", derive(thiserror::Error))]
45pub enum ICPError<T: Scalar, const N: usize> {
46 /// The source point cloud is empty.
47 SourcePointCloudEmpty,
48 /// The target point cloud is empty.
49 TargetPointCloudEmpty,
50 /// A source point had no nearest neighbour in the target point cloud.
51 NoNearestNeighbour,
52 /// The amount of iterations was set to zero.
53 IterationNumIsZero,
54 /// The Mean Squared Error interval threshold was set to zero.
55 MSEIntervalThreshold,
56 /// The Mean Squared Error absolute threshold was set to zero.
57 MSEAbsoluteThreshold,
58 /// The Current iteration did not converge, returns the current mean points.
59 IterationDidNotConverge((Point<T, N>, Point<T, N>)),
60 /// The algorithm did not converge after the maximum amount of iterations.
61 AlrogithmDidNotConverge,
62}
63
64/// A type alias for the result of an ICP algorithm, containing either the successful result or an error.
65pub type ICPResult<T, R, const N: usize> = Result<ICPSuccess<T, R, N>, ICPError<T, N>>;
66
67/// A struct specifying configuration options for an ICP algorithm.
68#[derive(Clone, Debug)]
69pub struct ICPConfiguration<T> {
70 /// Whether to use a KDTree structure to find nearest neighbours, becomes increasingly effective with point cloud growth.
71 pub(crate) use_kd_tree: bool,
72 /// The amount of iterations before giving up and exiting the algorithm.
73 pub(crate) max_iterations: usize,
74 /// When provided, the algorithm will consider itself converged when the MSE is smaller than the given value, without any more iterations.
75 pub(crate) mse_absolute_threshold: Option<T>,
76 /// This will specify the interval between iteration MSE's than when reached, will declare ICP convergence.
77 pub(crate) mse_interval_threshold: T,
78}
79
80impl<T: 'static + Copy> ICPConfiguration<T>
81where
82 f32: AsPrimitive<T>,
83{
84 /// Returns a builder for the configuration struct.
85 ///
86 /// # Returns
87 /// An [`ICPConfigurationBuilder`].
88 pub fn builder() -> ICPConfigurationBuilder<T> {
89 ICPConfigurationBuilder {
90 _internal: ICPConfiguration {
91 use_kd_tree: false,
92 max_iterations: 20,
93 mse_absolute_threshold: None,
94 mse_interval_threshold: 0.01.as_(),
95 },
96 }
97 }
98}
99
100/// A Builder-pattern struct for safely constructing an [`ICPConfiguration`] struct.
101#[derive(Clone, Debug)]
102pub struct ICPConfigurationBuilder<T> {
103 _internal: ICPConfiguration<T>,
104}
105
106impl<T: Copy> ICPConfigurationBuilder<T> {
107 /// Enables usage of a KD Tree structure to find nearest neighbours, or use a native On^2 search,
108 /// a KD Tree becomes increasingly effective with point cloud growth.
109 ///
110 /// # Arguments
111 /// * `use_kd_tree`: Whether to use a KD Tree search method.
112 ///
113 /// # Returns
114 /// A copy of self, with the updated parameters
115 pub fn with_kd_tree(&self, use_kd_tree: bool) -> Self {
116 Self {
117 _internal: ICPConfiguration {
118 use_kd_tree,
119 ..self._internal
120 },
121 }
122 }
123
124 /// The amount of iterations before giving up and exiting the algorithm.
125 ///
126 /// # Arguments
127 /// * `max_iterations`: The maximum number of iterations to allow.
128 ///
129 /// # Returns
130 /// A copy of self, with the updated parameters
131 pub fn with_max_iterations(&self, max_iterations: usize) -> Self {
132 Self {
133 _internal: ICPConfiguration {
134 max_iterations,
135 ..self._internal
136 },
137 }
138 }
139
140 /// When provided, the algorithm will consider itself converged when the MSE is smaller than the given value, without any more iterations.
141 ///
142 /// # Arguments
143 /// * `mse_absolute_threshold`: If is [`Some`], sets the minimum accepted MSE difference, that will return a convergence.
144 ///
145 /// # Returns
146 /// A copy of self, with the updated parameters
147 pub fn with_absolute_mse_threshold(&self, mse_absolute_threshold: Option<T>) -> Self {
148 Self {
149 _internal: ICPConfiguration {
150 mse_absolute_threshold,
151 ..self._internal
152 },
153 }
154 }
155
156 /// This will specify the interval between iteration MSE's than when reached, will declare ICP convergence.
157 ///
158 /// # Arguments
159 /// * `mse_interval_threshold`: The minimum threshold for an MSE, anything below will return a convergence.
160 ///
161 /// # Returns
162 /// A copy of self, with the updated parameters
163 pub fn with_mse_interval_threshold(&self, mse_interval_threshold: T) -> Self {
164 Self {
165 _internal: ICPConfiguration {
166 mse_interval_threshold,
167 ..self._internal
168 },
169 }
170 }
171
172 /// Generates an [`ICPConfiguration`] from the struct currently contained by the builder
173 ///
174 /// # Returns
175 /// An [`ICPConfiguration`], note that this does not consume the builder, leaving it intact for another use.
176 pub fn build(&self) -> ICPConfiguration<T> {
177 self._internal.clone()
178 }
179}