Skip to content

Commit 48e6c32

Browse files
committed
fix: ✨ transformation object
a class to rotate and translate data so it is axis aligned and centred on 0,0
1 parent 75941f0 commit 48e6c32

File tree

1 file changed

+79
-0
lines changed

1 file changed

+79
-0
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import numpy as np
2+
from sklearn import decomposition
3+
from sklearn.preprocessing import StandardScaler
4+
5+
6+
class EuclideanTransformation:
7+
def __init__(self, dimensions=2):
8+
"""Transforms points into a new coordinate
9+
system where the main eigenvector is aligned with x
10+
11+
Parameters
12+
----------
13+
dimensions : int, optional
14+
Do transformation in map view or on 3d volume, by default 2
15+
"""
16+
self.rotation = None
17+
self.translation = None
18+
self.dimensions = dimensions
19+
self.angle = 0
20+
21+
def fit(self, points: np.ndarray):
22+
"""Fit the transformation to a point cloud
23+
This function will find the main eigenvector of the point cloud
24+
and rotate the point cloud so that this is aligned with x
25+
26+
27+
Parameters
28+
----------
29+
points : np.ndarray
30+
xyz points as as numpy array
31+
"""
32+
points = np.array(points)
33+
if points.shape[1] < self.dimensions:
34+
raise ValueError(
35+
"Points must have at least {} dimensions".format(self.dimensions)
36+
)
37+
# standardise the points so that centre is 0
38+
self.translation = np.mean(points, axis=0)
39+
# find main eigenvector and and calculate the angle of this with x
40+
pca = decomposition.PCA(n_components=self.dimensions).fit(
41+
points[:, : self.dimensions] - self.translation[None, : self.dimensions]
42+
)
43+
coeffs = pca.components_
44+
self.angle = -np.arccos(np.dot(coeffs[0, :], [1, 0]))
45+
self.rotation = self._rotation(self.angle)
46+
47+
def _rotation(self, angle):
48+
return np.array(
49+
[
50+
[np.cos(angle), -np.sin(angle), 0],
51+
[np.sin(angle), np.cos(angle), 0],
52+
[0, 0, 1],
53+
]
54+
)
55+
56+
def fit_transform(self, points: np.ndarray) -> np.ndarray:
57+
self.fit(points)
58+
return self.transform(points)
59+
60+
def transform(self, points: np.ndarray) -> np.ndarray:
61+
"""_summary_
62+
63+
Parameters
64+
----------
65+
points : _type_
66+
_description_
67+
68+
Returns
69+
-------
70+
_type_
71+
_description_
72+
"""
73+
return np.dot(points - self.translation, self.rotation)
74+
75+
def inverse_transform(self, points: np.ndarray) -> np.ndarray:
76+
return np.dot(points, self._rotation(-self.angle)) + self.translation
77+
78+
def __call__(self, points: np.ndarray) -> np.ndarray:
79+
return self.transform(points)

0 commit comments

Comments
 (0)