Skip to content

Commit 35e2cc9

Browse files
committed
Update gitignore
1 parent 614af9e commit 35e2cc9

2 files changed

Lines changed: 130 additions & 0 deletions

File tree

prepmd/lib/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
"""
4+
Created on Tue Feb 17 13:53:45 2026
5+
6+
@author: rob
7+
"""
8+
9+
from . import icp

prepmd/lib/icp.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
"""
2+
ICP method in python by Clay Fannigan. Improvement by Max Bazik. This version
3+
originally had scaling, added by Alvin Wan. I have removed the scalilng but
4+
kept some changes that allow for differently-sized point clouds to be aligned.
5+
This code was originally licensed under the Apache License version 2.0.
6+
"""
7+
8+
import numpy as np
9+
from sklearn.neighbors import NearestNeighbors
10+
11+
def best_fit_transform(A, B):
12+
'''
13+
Calculates the least-squares best-fit transform between corresponding 3D points A->B
14+
Input:
15+
A: Nx3 numpy array of corresponding 3D points
16+
B: Nx3 numpy array of corresponding 3D points
17+
Returns:
18+
T: 4x4 homogeneous transformation matrix
19+
R: 3x3 rotation matrix
20+
t: 3x1 column vector for translation
21+
s: 3x1 column vector for scaling
22+
'''
23+
24+
assert len(A) == len(B)
25+
26+
# translate points to their centroids
27+
centroid_A = np.mean(A, axis=0)
28+
centroid_B = np.mean(B, axis=0)
29+
AA = A - centroid_A
30+
BB = B - centroid_B
31+
32+
# rotation matrix
33+
H = np.dot(AA.T, BB)
34+
U, S, Vt = np.linalg.svd(H)
35+
R = np.dot(Vt.T, U.T)
36+
37+
# special reflection case
38+
if np.linalg.det(R) < 0:
39+
Vt[2,:] *= -1
40+
R = np.dot(Vt.T, U.T)
41+
42+
# translation
43+
t = centroid_B.T - np.dot(R, centroid_A.T)
44+
45+
# homogeneous transformation
46+
T = np.identity(4)
47+
T[0:3, 0:3] = R
48+
T[0:3, 3] = t
49+
50+
return T, R, t
51+
52+
def nearest_neighbor(src, dst):
53+
'''
54+
Find the nearest (Euclidean) neighbor in dst for each point in src
55+
Input:
56+
src: Nx3 array of points
57+
dst: Nx3 array of points
58+
Output:
59+
distances: Euclidean distances of the nearest neighbor
60+
indices: dst indices of the nearest neighbor
61+
'''
62+
63+
neigh = NearestNeighbors(n_neighbors=1)
64+
neigh.fit(dst)
65+
distances, indices = neigh.kneighbors(src, return_distance=True)
66+
return distances.ravel(), indices.ravel()
67+
68+
def icp(A, B, init_pose=None, max_iterations=100, tolerance=1e-10):
69+
'''
70+
The Iterative Closest Point method
71+
Input:
72+
A: Nx3 numpy array of source 3D points
73+
B: Nx3 numpy array of destination 3D point
74+
init_pose: 4x4 homogeneous transformation
75+
max_iterations: exit algorithm after max_iterations
76+
tolerance: convergence criteria
77+
Output:
78+
T: final homogeneous transformation
79+
distances: Euclidean distances (errors) of the nearest neighbor
80+
'''
81+
82+
# make points homogeneous, copy them so as to maintain the originals
83+
src = np.ones((4,A.shape[0]))
84+
dst = np.ones((4,B.shape[0]))
85+
src[0:3,:] = np.copy(A.T)
86+
dst[0:3,:] = np.copy(B.T)
87+
88+
# apply the initial pose estimation
89+
if init_pose is not None:
90+
src = np.dot(init_pose, src)
91+
92+
prev_error = 0
93+
94+
try:
95+
96+
for i in range(max_iterations):
97+
# find the nearest neighbours between the current source and destination points
98+
distances, indices = nearest_neighbor(src[0:3,:].T, dst[0:3,:].T)
99+
100+
# compute the transformation between the current source and nearest destination points
101+
T, _, _ = best_fit_transform(src[0:3,:].T, dst[0:3,indices].T)
102+
103+
# update the current source
104+
src = T.dot(src)
105+
106+
# check error
107+
mean_error = np.sum(distances) / distances.size
108+
if abs(prev_error-mean_error) < tolerance:
109+
break
110+
prev_error = mean_error
111+
112+
# calculate final transformation
113+
T, _, _ = best_fit_transform(A, src[0:3,:].T)
114+
115+
return T, distances
116+
except ValueError as e:
117+
print(e)
118+
return np.eye(4), 1, np.array([np.inf])
119+
except np.linalg.linalg.LinAlgError as e:
120+
print(e)
121+
return np.eye(4), 1, np.array([np.inf])

0 commit comments

Comments
 (0)