Skip to content

Commit 66f5d9b

Browse files
committed
- Added trait ToPointCloud to make ICP easy to use with point clouds
loaded from various sources. - Added modelz integration - `estimate_transform` has slightly changed to allow `ToPointCloud` in parameters
1 parent f5aedca commit 66f5d9b

File tree

16 files changed

+207
-51
lines changed

16 files changed

+207
-51
lines changed

.github/workflows/cd.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ jobs:
3535
cargo rdme --check
3636
3737
- name: Run tests
38-
run: cargo test
38+
run: cargo test --features modelz
3939

4040
- name: Publish crate modern-icp
4141
uses: katyo/publish-crates@v2

.zed/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"allTargets": true
1111
},
1212
"cargo": {
13-
"features": ["rerun"],
13+
"features": ["rerun", "modelz"],
1414
"target": "wasm32-unknown-unknown"
1515
}
1616
}

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
44
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
55

6+
## [0.4.0] - 2025-07-05
7+
8+
- Added trait `ToPointCloud` to make ICP easy to use with point clouds loaded from various sources.
9+
- Added modelz integration
10+
- `estimate_transform` has slightly changed to allow `ToPointCloud` in parameters
11+
612
## [0.3.0] - 2025-07-02
713

814
- Renamed `is_small_squared_distance_error` to `same_squared_distance_error`

Cargo.lock

Lines changed: 28 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "modern-icp"
3-
version = "0.3.0"
3+
version = "0.4.0"
44
edition = "2024"
55
authors = ["Marc-Stefan Cassola"]
66
categories = ["graphics", "algorithms", "mathematics"]
@@ -15,6 +15,7 @@ repository = "https://github.com/Synphonyte/modern-icp"
1515
cfg-if = "1"
1616
kdtree = "0.7.0"
1717
lazy_static = { version = "1.5.0", optional = true }
18+
modelz = { version = "0.1.5", optional = true }
1819
nalgebra = "0.33.2"
1920
num-traits = "0.2.19"
2021
rerun = { version = "0.23", optional = true }
@@ -28,3 +29,4 @@ rand = "0.8.5"
2829
default = []
2930
rerun = ["dep:rerun", "dep:lazy_static", "nalgebra/convert-glam029"]
3031
serde = ["dep:serde", "nalgebra/serde-serialize"]
32+
modelz = ["dep:modelz"]

README.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ A modern modular pure Rust implementation of the Iterative Closest Point algorit
1313

1414
```rust
1515
let (alignee_transform, error_sum) = estimate_transform(
16-
&alignee_cloud,
16+
alignee_cloud,
1717
&target_cloud,
1818
20, // max iterations
1919
BidirectionalDistance::new(&target_cloud),
@@ -24,4 +24,25 @@ let (alignee_transform, error_sum) = estimate_transform(
2424
);
2525
```
2626

27+
### Integrations
28+
29+
An integrations with the modelz crate is provided so you can use `Model3D` with the `estimate_transform` function.
30+
31+
```rust
32+
use modelz::Model3D;
33+
34+
if let (Ok(alignee), Ok(target)) = (Model3D::load("alignee.gltf"), Model3D::load("target.stl")) {
35+
let (transform, error_sum) = estimate_transform(
36+
alignee,
37+
&target,
38+
20, // max iterations
39+
BidirectionalDistance::new(&target),
40+
accept_all,
41+
reject_3_sigma_dist,
42+
point_to_plane_lls::estimate_isometry,
43+
same_squared_distance_error(1.0),
44+
);
45+
}
46+
```
47+
2748
<!-- cargo-rdme end -->

src/align/correspondence/bidirectional_distance.rs

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::correspondence::{
22
CorrespondenceEstimator, Correspondences, get_ordered_correspondences_and_distances_nn,
33
};
4-
use crate::{PointCloud, PointCloudPoint, kd_tree_of_point_cloud};
4+
use crate::{PointCloud, PointCloudPoint, ToPointCloud, kd_tree_of_point_cloud};
55
use kdtree::KdTree;
66
use nalgebra::{RealField, Scalar};
77
use num_traits::{Float, One, Zero};
@@ -10,44 +10,50 @@ use num_traits::{Float, One, Zero};
1010
/// Bidirectional Distance algorithm.
1111
///
1212
/// See this [paper from Dong et al.](https://doi.org/10.1049/iet-cvi.2016.0058)
13-
pub struct BidirectionalDistance<'a, T>
13+
pub struct BidirectionalDistance<T>
1414
where
1515
T: Scalar + RealField + Float + One + Zero,
1616
{
17-
target_tree: KdTree<T, usize, &'a [T]>,
17+
target_tree: KdTree<T, usize, Vec<T>>,
18+
target_cloud: PointCloud<T, 3>,
1819
}
1920

20-
impl<'a, T> CorrespondenceEstimator<'a, T, PointCloud<T, 3>, 3> for BidirectionalDistance<'a, T>
21+
impl<'a, T, PC> CorrespondenceEstimator<'a, T, PC, 3> for BidirectionalDistance<T>
2122
where
2223
T: Scalar + RealField + Float + One + Zero,
24+
PC: ToPointCloud<T, 3>,
2325
{
24-
fn new(target: &'a PointCloud<T, 3>) -> Self {
26+
fn new(target: &'a PC) -> Self {
27+
let target_cloud = target.to_point_cloud();
28+
2529
BidirectionalDistance {
26-
target_tree: kd_tree_of_point_cloud(target),
30+
target_tree: kd_tree_of_point_cloud(&target_cloud),
31+
target_cloud,
2732
}
2833
}
2934

30-
fn find_correspondences<'b, FP>(
31-
&self,
35+
fn find_correspondences<'b, 't, FP>(
36+
&'t self,
3237
alignee: &'b PointCloud<T, 3>,
33-
target: &'b PointCloud<T, 3>,
38+
_target: &'b PC,
3439
filter_points: &mut FP,
35-
) -> Correspondences<'b, T, 3>
40+
) -> Correspondences<'b, 't, T, 3>
3641
where
3742
FP: FnMut(&PointCloudPoint<T, 3>) -> bool,
43+
'b: 't,
3844
{
3945
let (alignee_point_cloud, corresponding_target_point_cloud, alignee_to_target_distances) =
4046
get_ordered_correspondences_and_distances_nn(
4147
&self.target_tree,
4248
alignee,
43-
target,
49+
&self.target_cloud,
4450
filter_points,
4551
);
4652

4753
let (target_point_cloud, corresponding_alignee_point_cloud, target_to_alignee_distances) =
4854
get_ordered_correspondences_and_distances_nn(
4955
&kd_tree_of_point_cloud(alignee),
50-
target,
56+
&self.target_cloud,
5157
alignee,
5258
filter_points,
5359
);

src/align/correspondence/cylinder.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,15 @@ where
7171
}
7272
}
7373

74-
fn find_correspondences<'b, FP>(
75-
&self,
74+
fn find_correspondences<'b, 't, FP>(
75+
&'t self,
7676
alignee: &'b PointCloud<T, 3>,
7777
_target: &'b T,
7878
filter_points: &mut FP,
79-
) -> Correspondences<'b, T, 3>
79+
) -> Correspondences<'b, 't, T, 3>
8080
where
8181
FP: FnMut(&PointCloudPoint<T, 3>) -> bool,
82+
'b: 't,
8283
{
8384
let mut point_cloud = self.point_cloud.borrow_mut();
8485
point_cloud.clear();

src/align/correspondence/mod.rs

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use num_traits::{Float, One, Zero};
1313
use std::fmt::Debug;
1414

1515
/// Contains all the correspondences found by the correspondence estimator.
16-
pub struct Correspondences<'a, T, const D: usize>
16+
pub struct Correspondences<'a, 't, T, const D: usize>
1717
where
1818
T: Copy + PartialEq + Debug + 'static,
1919
{
@@ -22,12 +22,12 @@ where
2222
pub alignee_point_cloud: MaskedPointCloud<'a, T, D>,
2323

2424
/// Every point in this iterator corresponds to the point in the `alignee_point_cloud` with the same index.
25-
pub corresponding_target_point_cloud: MaskedPointCloud<'a, T, D>,
25+
pub corresponding_target_point_cloud: MaskedPointCloud<'t, T, D>,
2626

2727
/// Iterator over the points of the target that have a corresponcence point in the alignee.
2828
/// (found in the `corresponding_alignee_point_cloud`).
2929
/// This is only used by bidrectional distance correspondence estimators.
30-
pub target_point_cloud: MaskedPointCloud<'a, T, D>,
30+
pub target_point_cloud: MaskedPointCloud<'t, T, D>,
3131

3232
/// Every point in this iterator corresponds to the point in the `target_point_cloud` with the same index.
3333
/// This is only used by bidrectional distance correspondence estimators.
@@ -43,15 +43,16 @@ where
4343
pub target_to_alignee_distances: Vec<T>,
4444
}
4545

46-
impl<'a, T, const D: usize> Correspondences<'a, T, D>
46+
impl<'a, 't, T, const D: usize> Correspondences<'a, 't, T, D>
4747
where
4848
T: Copy + PartialEq + Debug + 'static,
49+
'a: 't,
4950
{
5051
/// Use this for simple one-way correspondence estimators.
5152
pub fn from_simple_one_way_correspondences(
5253
alignee_point_cloud: MaskedPointCloud<'a, T, D>,
5354
alignee: &'a PointCloud<T, D>,
54-
corresponding_target_point_cloud: MaskedPointCloud<'a, T, D>,
55+
corresponding_target_point_cloud: MaskedPointCloud<'t, T, D>,
5556
alignee_to_target_distances: Vec<T>,
5657
) -> Self {
5758
let mut empty_alignee_cloud = MaskedPointCloud::new(alignee);
@@ -90,29 +91,30 @@ where
9091
/// It takes a reference to a `PointCloudPoint` and returns a boolean which is `true` if the point should be included.
9192
///
9293
/// See the [`Correspondences`] return type documentation for more information.
93-
fn find_correspondences<'b, FP>(
94-
&self,
94+
fn find_correspondences<'b, 't, FP>(
95+
&'t self,
9596
alignee: &'b PointCloud<T, D>,
9697
target: &'b TG,
9798
filter_points: &mut FP,
98-
) -> Correspondences<'b, T, D>
99+
) -> Correspondences<'b, 't, T, D>
99100
where
100-
FP: FnMut(&PointCloudPoint<T, 3>) -> bool;
101+
FP: FnMut(&PointCloudPoint<T, 3>) -> bool,
102+
'b: 't;
101103
}
102104

103105
/// For every point in `data_set_x` finds the nearest point in `data_set_y` using the KD-Tree `tree`.
104106
/// Returns a masked point_cloud referencing `data_set_x` that contains all points allowed by `filter_points`
105107
/// and another masked point_cloud referencing `data_set_y` that correspond to the points in the first
106108
/// returned masked point_cloud.
107109
/// Finally it returns a list of the distances between the points of the two returned point_clouds.
108-
pub fn get_ordered_correspondences_and_distances_nn<'a, T, FP>(
109-
tree: &KdTree<T, usize, &[T]>,
110+
pub fn get_ordered_correspondences_and_distances_nn<'a, 't, T, FP>(
111+
tree: &KdTree<T, usize, Vec<T>>,
110112
data_set_x: &'a PointCloud<T, 3>,
111-
data_set_y: &'a PointCloud<T, 3>,
113+
data_set_y: &'t PointCloud<T, 3>,
112114
filter_points: &mut FP,
113115
) -> (
114116
MaskedPointCloud<'a, T, 3>,
115-
MaskedPointCloud<'a, T, 3>,
117+
MaskedPointCloud<'t, T, 3>,
116118
Vec<T>,
117119
)
118120
where

src/align/correspondence/nearest_neighbor.rs

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,48 @@
11
use crate::correspondence::{
22
CorrespondenceEstimator, Correspondences, get_ordered_correspondences_and_distances_nn,
33
};
4-
use crate::{PointCloud, PointCloudPoint, kd_tree_of_point_cloud};
4+
use crate::{PointCloud, PointCloudPoint, ToPointCloud, kd_tree_of_point_cloud};
55
use kdtree::KdTree;
66
use nalgebra::{RealField, Scalar};
77
use num_traits::{Float, One, Zero};
88

9-
pub struct NearestNeighbor<'a, T>
9+
pub struct NearestNeighbor<T>
1010
where
1111
T: Scalar + RealField + Float + One + Zero,
1212
{
13-
tree: KdTree<T, usize, &'a [T]>,
13+
tree: KdTree<T, usize, Vec<T>>,
14+
target_cloud: PointCloud<T, 3>,
1415
}
1516

16-
impl<'a, T> CorrespondenceEstimator<'a, T, PointCloud<T, 3>, 3> for NearestNeighbor<'a, T>
17+
impl<'a, T, PC> CorrespondenceEstimator<'a, T, PC, 3> for NearestNeighbor<T>
1718
where
1819
T: Scalar + RealField + Float + One + Zero,
20+
PC: ToPointCloud<T, 3>,
1921
{
20-
fn new(target: &'a PointCloud<T, 3>) -> Self {
22+
fn new(target: &'a PC) -> Self {
23+
let target_cloud = target.to_point_cloud();
24+
2125
NearestNeighbor {
22-
tree: kd_tree_of_point_cloud(target),
26+
tree: kd_tree_of_point_cloud(&target_cloud),
27+
target_cloud,
2328
}
2429
}
2530

26-
fn find_correspondences<'b, FP>(
27-
&self,
31+
fn find_correspondences<'b, 't, FP>(
32+
&'t self,
2833
alignee: &'b PointCloud<T, 3>,
29-
target: &'b PointCloud<T, 3>,
34+
_target: &'b PC,
3035
filter_points: &mut FP,
31-
) -> Correspondences<'b, T, 3>
36+
) -> Correspondences<'b, 't, T, 3>
3237
where
3338
FP: FnMut(&PointCloudPoint<T, 3>) -> bool,
39+
'b: 't,
3440
{
3541
let (alignee_point_cloud, corresponding_points_iter, distances) =
3642
get_ordered_correspondences_and_distances_nn(
3743
&self.tree,
3844
alignee,
39-
target,
45+
&self.target_cloud,
4046
filter_points,
4147
);
4248

0 commit comments

Comments
 (0)