cgse-coordinates 2023.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cgse_coordinates-2023.1.0.dist-info/METADATA +10 -0
- cgse_coordinates-2023.1.0.dist-info/RECORD +15 -0
- cgse_coordinates-2023.1.0.dist-info/WHEEL +5 -0
- cgse_coordinates-2023.1.0.dist-info/entry_points.txt +2 -0
- cgse_coordinates-2023.1.0.dist-info/top_level.txt +1 -0
- egse/coordinates/__init__.py +531 -0
- egse/coordinates/avoidance.py +103 -0
- egse/coordinates/cslmodel.py +127 -0
- egse/coordinates/laser_tracker_to_dict.py +120 -0
- egse/coordinates/point.py +707 -0
- egse/coordinates/pyplot.py +195 -0
- egse/coordinates/referenceFrame.py +1279 -0
- egse/coordinates/refmodel.py +737 -0
- egse/coordinates/rotationMatrix.py +85 -0
- egse/coordinates/transform3d_addon.py +419 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The RotationMatrix provides a number of convenience methods to define and apply rotations.
|
|
3
|
+
|
|
4
|
+
@author: pierre
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import transforms3d as t3
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
class RotationMatrix():
|
|
11
|
+
"""
|
|
12
|
+
RotationMatrix(angle1, angle2, angle3, rot_config="sxyz", active=True)
|
|
13
|
+
|
|
14
|
+
The ``angle`` parameters provide the amplitude of rotation around an axis
|
|
15
|
+
according to the order of rotations corresponding to the ``rot_config`` parameter.
|
|
16
|
+
|
|
17
|
+
The ``rot_config`` parameter is a string that consists of four letter
|
|
18
|
+
indicating the rotation system and the order of applying the rotations. The default
|
|
19
|
+
for this parameter is ``"rxyz"`` where the first character can be 'r' or 's' and
|
|
20
|
+
the next three characters define the axes of rotation in the order specified, here 'xyz'.
|
|
21
|
+
|
|
22
|
+
* 'r' stands for rotating system (intrinsic rotations)
|
|
23
|
+
* 's' stands for static system (extrinsic rotations)
|
|
24
|
+
* "rxyz" imposes angle1 describes the rotation around X
|
|
25
|
+
* even if two angles = 0, the match between angle orders and rot_config is still critical
|
|
26
|
+
|
|
27
|
+
:param str rot_config: otation system to be used, 4 characters. Default = "sxyz"
|
|
28
|
+
|
|
29
|
+
:param float angle1: amplitude of rotations around the first axis
|
|
30
|
+
|
|
31
|
+
:param float angle2: amplitude of rotations around the second axis
|
|
32
|
+
|
|
33
|
+
:param float angle3: amplitude of rotations around the third axis
|
|
34
|
+
|
|
35
|
+
:param bool active: when True the object rotates **in** a fixed coord system,
|
|
36
|
+
when False the coord system rotates **around** a fixed object
|
|
37
|
+
The default for the transform3d.euleur package is to represent movements
|
|
38
|
+
of the coordinate system itself --> **around** the object <==> passive
|
|
39
|
+
"""
|
|
40
|
+
_ROT_CONFIG_DEFAULT = 'sxyz'
|
|
41
|
+
|
|
42
|
+
def __init__(self,angle1,angle2,angle3,rot_config=_ROT_CONFIG_DEFAULT,active=True):
|
|
43
|
+
R = t3.euler.euler2mat(angle1,angle2,angle3,rot_config)
|
|
44
|
+
if active:
|
|
45
|
+
self.R = R
|
|
46
|
+
else:
|
|
47
|
+
self.R = R.T
|
|
48
|
+
rot_config_array = np.array(list(rot_config[1:]))
|
|
49
|
+
angles_array = np.array([angle1,angle2,angle3])
|
|
50
|
+
self.angles_hash = {}
|
|
51
|
+
for axis in ['x','y','z']:
|
|
52
|
+
self.angles_hash[axis] = angles_array[np.where(rot_config_array==axis)[0][0]]
|
|
53
|
+
self.active=active
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def getRotationMatrix(self):
|
|
57
|
+
'''Return the Rotation Matrix'''
|
|
58
|
+
return self.R
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def trace(self):
|
|
62
|
+
return np.sum([self.R[i,i] for i in range(3)])
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def getAngle(self, axis):
|
|
66
|
+
"""
|
|
67
|
+
Returns the angle
|
|
68
|
+
|
|
69
|
+
:param str axis: 'x', 'y', 'z'
|
|
70
|
+
|
|
71
|
+
:return: the angle
|
|
72
|
+
"""
|
|
73
|
+
return self.angles_hash[axis]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def apply(self, vectors):
|
|
77
|
+
"""
|
|
78
|
+
if self = active, the output = the coords of these vectors after rotation
|
|
79
|
+
|
|
80
|
+
if self = passive, the ouput = the coords of these vectors after transformation to the rotated coordinate system
|
|
81
|
+
|
|
82
|
+
:param vectors: an array of shape [3,N] gathering a set of vectors along its columns
|
|
83
|
+
"""
|
|
84
|
+
return np.dot(self.R, vectors)
|
|
85
|
+
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Created on Mon Jun 25 16:25:33 2018
|
|
5
|
+
|
|
6
|
+
@author: pierre
|
|
7
|
+
"""
|
|
8
|
+
import numpy
|
|
9
|
+
import numpy as np
|
|
10
|
+
import math
|
|
11
|
+
import transforms3d as t3
|
|
12
|
+
|
|
13
|
+
from egse.coordinates.rotationMatrix import RotationMatrix
|
|
14
|
+
|
|
15
|
+
def affine_isEuclidian(matrix):
|
|
16
|
+
"""
|
|
17
|
+
Tests if a matrix is a pure solid-body euclidian rotation + translation (no shear or scaling)
|
|
18
|
+
|
|
19
|
+
We only need to check that
|
|
20
|
+
. the rotation part is orthogonal : R @ R.T = I
|
|
21
|
+
. the det(R) = 1 (=> this is not a reflexion)
|
|
22
|
+
"""
|
|
23
|
+
rotation = matrix[:3,:3]
|
|
24
|
+
return np.allclose((rotation @ rotation.T),np.identity(3)) & np.allclose(np.linalg.det(matrix),1)
|
|
25
|
+
|
|
26
|
+
def affine_inverse(matrix):
|
|
27
|
+
"""
|
|
28
|
+
affine_inverse(matrix)
|
|
29
|
+
|
|
30
|
+
WARNING:
|
|
31
|
+
This is NOT a generic inversion of an affine transformation matrix
|
|
32
|
+
|
|
33
|
+
This returns the affine transformation inverting that produced by the input matrix,
|
|
34
|
+
|
|
35
|
+
ASSSUMING that only rotation and translation were involved
|
|
36
|
+
in the affine transformation, no zoom, no shear!
|
|
37
|
+
|
|
38
|
+
That preserves the fact that the orthogonal property of the part of the input matrix
|
|
39
|
+
corresponding to rotation => the inverse is simply the transpose.
|
|
40
|
+
|
|
41
|
+
Pierre Royer
|
|
42
|
+
"""
|
|
43
|
+
#import numpy as np
|
|
44
|
+
|
|
45
|
+
# Extract Rotation matrix and translation vector from input affine transformation
|
|
46
|
+
R = matrix[:3,:3]
|
|
47
|
+
t = matrix[:3,3]
|
|
48
|
+
#
|
|
49
|
+
# Invert the rotation and the translation
|
|
50
|
+
Rinv = R.T
|
|
51
|
+
tinv = -t
|
|
52
|
+
#
|
|
53
|
+
# The inverse affine is composed from R^-1 for the rotation and -(R^-1 . t) for the translation
|
|
54
|
+
result = np.identity(4)
|
|
55
|
+
result[:3,:3] = Rinv
|
|
56
|
+
result[:3,3] = np.dot(Rinv,tinv)
|
|
57
|
+
|
|
58
|
+
if affine_isEuclidian(result):
|
|
59
|
+
return result
|
|
60
|
+
else:
|
|
61
|
+
print("WARNING: This is not a rigid-body transformation matrix")
|
|
62
|
+
#print(f"R.T-based (.6f) = \n {np.round(result,6)}")
|
|
63
|
+
#print(f"np.inverse (.6f) = \n {np.round(np.linalg.inv(matrix),6)}")
|
|
64
|
+
return np.linalg.inv(matrix)
|
|
65
|
+
|
|
66
|
+
def affine_matrix_from_points(v0, v1, shear=False, scale=False, usesvd=True):
|
|
67
|
+
"""affine_matrix_from_points(v0, v1, shear=False, scale=False, usesvd=True)
|
|
68
|
+
Return affine transform matrix to register two point sets.
|
|
69
|
+
|
|
70
|
+
v0 and v1 are shape (ndims, \*) arrays of at least ndims non-homogeneous
|
|
71
|
+
coordinates, where ndims is the dimensionality of the coordinate space.
|
|
72
|
+
|
|
73
|
+
If shear is False, a similarity transformation matrix is returned.
|
|
74
|
+
If also scale is False, a rigid/Euclidean transformation matrix
|
|
75
|
+
is returned.
|
|
76
|
+
|
|
77
|
+
By default the algorithm by Hartley and Zissermann [15] is used.
|
|
78
|
+
If usesvd is True, similarity and Euclidean transformation matrices
|
|
79
|
+
are calculated by minimizing the weighted sum of squared deviations
|
|
80
|
+
(RMSD) according to the algorithm by Kabsch [8].
|
|
81
|
+
Otherwise, and if ndims is 3, the quaternion based algorithm by Horn [9]
|
|
82
|
+
is used, which is slower when using this Python implementation.
|
|
83
|
+
|
|
84
|
+
The returned matrix performs rotation, translation and uniform scaling
|
|
85
|
+
(if specified).
|
|
86
|
+
|
|
87
|
+
>>> v0 = [[0, 1031, 1031, 0], [0, 0, 1600, 1600]]
|
|
88
|
+
>>> v1 = [[675, 826, 826, 677], [55, 52, 281, 277]]
|
|
89
|
+
>>> affine_matrix_from_points(v0, v1)
|
|
90
|
+
array([[ 0.14549, 0.00062, 675.50008],
|
|
91
|
+
[ 0.00048, 0.14094, 53.24971],
|
|
92
|
+
[ 0. , 0. , 1. ]])
|
|
93
|
+
>>> T = translation_matrix(numpy.random.random(3)-0.5)
|
|
94
|
+
>>> R = random_rotation_matrix(numpy.random.random(3))
|
|
95
|
+
>>> S = scale_matrix(random.random())
|
|
96
|
+
>>> M = concatenate_matrices(T, R, S)
|
|
97
|
+
>>> v0 = (numpy.random.rand(4, 100) - 0.5) * 20
|
|
98
|
+
>>> v0[3] = 1
|
|
99
|
+
>>> v1 = numpy.dot(M, v0)
|
|
100
|
+
>>> v0[:3] += numpy.random.normal(0, 1e-8, 300).reshape(3, -1)
|
|
101
|
+
>>> M = affine_matrix_from_points(v0[:3], v1[:3])
|
|
102
|
+
>>> numpy.allclose(v1, numpy.dot(M, v0))
|
|
103
|
+
True
|
|
104
|
+
|
|
105
|
+
More examples in superimposition_matrix()
|
|
106
|
+
|
|
107
|
+
Author: this function was extracted from the original transformations.py
|
|
108
|
+
written by Christoph Golke:
|
|
109
|
+
https://www.lfd.uci.edu/~gohlke/code/transformations.py.html
|
|
110
|
+
|
|
111
|
+
usesvd controls the use of a method based on Singular Value Decomposition (SVD)
|
|
112
|
+
--> when True, it is equivalent to rigid_transform_3D (see below)
|
|
113
|
+
|
|
114
|
+
"""
|
|
115
|
+
import numpy
|
|
116
|
+
v0 = numpy.array(v0, dtype=numpy.float64, copy=True)
|
|
117
|
+
v1 = numpy.array(v1, dtype=numpy.float64, copy=True)
|
|
118
|
+
|
|
119
|
+
ndims = v0.shape[0]
|
|
120
|
+
if ndims < 2 or v0.shape[1] < ndims or v0.shape != v1.shape:
|
|
121
|
+
print(f"ndims {ndims} v0/1.shape {v0.shape} {v1.shape} v0/1 class {v0.__class__} {v1.__class__}")
|
|
122
|
+
raise ValueError('input arrays are of wrong shape or type')
|
|
123
|
+
|
|
124
|
+
# move centroids to origin
|
|
125
|
+
t0 = -numpy.mean(v0, axis=1)
|
|
126
|
+
M0 = numpy.identity(ndims+1)
|
|
127
|
+
M0[:ndims, ndims] = t0
|
|
128
|
+
v0 += t0.reshape(ndims, 1)
|
|
129
|
+
t1 = -numpy.mean(v1, axis=1)
|
|
130
|
+
M1 = numpy.identity(ndims+1)
|
|
131
|
+
M1[:ndims, ndims] = t1
|
|
132
|
+
v1 += t1.reshape(ndims, 1)
|
|
133
|
+
|
|
134
|
+
if shear:
|
|
135
|
+
# Affine transformation
|
|
136
|
+
A = numpy.concatenate((v0, v1), axis=0)
|
|
137
|
+
u, s, vh = numpy.linalg.svd(A.T)
|
|
138
|
+
vh = vh[:ndims].T
|
|
139
|
+
B = vh[:ndims]
|
|
140
|
+
C = vh[ndims:2*ndims]
|
|
141
|
+
t = numpy.dot(C, numpy.linalg.pinv(B))
|
|
142
|
+
t = numpy.concatenate((t, numpy.zeros((ndims, 1))), axis=1)
|
|
143
|
+
M = numpy.vstack((t, ((0.0,)*ndims) + (1.0,)))
|
|
144
|
+
elif usesvd or ndims != 3:
|
|
145
|
+
# Rigid transformation via SVD of covariance matrix
|
|
146
|
+
u, s, vh = numpy.linalg.svd(numpy.dot(v1, v0.T))
|
|
147
|
+
# rotation matrix from SVD orthonormal bases
|
|
148
|
+
R = numpy.dot(u, vh)
|
|
149
|
+
if numpy.linalg.det(R) < 0.0:
|
|
150
|
+
# R does not constitute right handed system
|
|
151
|
+
R -= numpy.outer(u[:, ndims-1], vh[ndims-1, :]*2.0)
|
|
152
|
+
s[-1] *= -1.0
|
|
153
|
+
# homogeneous transformation matrix
|
|
154
|
+
M = numpy.identity(ndims+1)
|
|
155
|
+
M[:ndims, :ndims] = R
|
|
156
|
+
else:
|
|
157
|
+
# Rigid transformation matrix via quaternion
|
|
158
|
+
# compute symmetric matrix N
|
|
159
|
+
xx, yy, zz = numpy.sum(v0 * v1, axis=1)
|
|
160
|
+
xy, yz, zx = numpy.sum(v0 * numpy.roll(v1, -1, axis=0), axis=1)
|
|
161
|
+
xz, yx, zy = numpy.sum(v0 * numpy.roll(v1, -2, axis=0), axis=1)
|
|
162
|
+
N = [[xx+yy+zz, 0.0, 0.0, 0.0],
|
|
163
|
+
[yz-zy, xx-yy-zz, 0.0, 0.0],
|
|
164
|
+
[zx-xz, xy+yx, yy-xx-zz, 0.0],
|
|
165
|
+
[xy-yx, zx+xz, yz+zy, zz-xx-yy]]
|
|
166
|
+
# quaternion: eigenvector corresponding to most positive eigenvalue
|
|
167
|
+
w, V = numpy.linalg.eigh(N)
|
|
168
|
+
q = V[:, numpy.argmax(w)]
|
|
169
|
+
q /= _vector_norm(q) # unit quaternion
|
|
170
|
+
# homogeneous transformation matrix
|
|
171
|
+
M = _quaternion_matrix(q)
|
|
172
|
+
|
|
173
|
+
if scale and not shear:
|
|
174
|
+
# Affine transformation; scale is ratio of RMS deviations from centroid
|
|
175
|
+
v0 *= v0
|
|
176
|
+
v1 *= v1
|
|
177
|
+
M[:ndims, :ndims] *= math.sqrt(numpy.sum(v1) / numpy.sum(v0))
|
|
178
|
+
|
|
179
|
+
# move centroids back
|
|
180
|
+
M = numpy.dot(numpy.linalg.inv(M1), numpy.dot(M, M0))
|
|
181
|
+
M /= M[ndims, ndims]
|
|
182
|
+
return M
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _vector_norm(data, axis=None, out=None):
|
|
186
|
+
"""Return length, i.e. Euclidean norm, of ndarray along axis.
|
|
187
|
+
|
|
188
|
+
>>> v = numpy.random.random(3)
|
|
189
|
+
>>> n = vector_norm(v)
|
|
190
|
+
>>> numpy.allclose(n, numpy.linalg.norm(v))
|
|
191
|
+
True
|
|
192
|
+
>>> v = numpy.random.rand(6, 5, 3)
|
|
193
|
+
>>> n = vector_norm(v, axis=-1)
|
|
194
|
+
>>> numpy.allclose(n, numpy.sqrt(numpy.sum(v*v, axis=2)))
|
|
195
|
+
True
|
|
196
|
+
>>> n = vector_norm(v, axis=1)
|
|
197
|
+
>>> numpy.allclose(n, numpy.sqrt(numpy.sum(v*v, axis=1)))
|
|
198
|
+
True
|
|
199
|
+
>>> v = numpy.random.rand(5, 4, 3)
|
|
200
|
+
>>> n = numpy.empty((5, 3))
|
|
201
|
+
>>> vector_norm(v, axis=1, out=n)
|
|
202
|
+
>>> numpy.allclose(n, numpy.sqrt(numpy.sum(v*v, axis=1)))
|
|
203
|
+
True
|
|
204
|
+
>>> vector_norm([])
|
|
205
|
+
0.0
|
|
206
|
+
>>> vector_norm([1])
|
|
207
|
+
1.0
|
|
208
|
+
|
|
209
|
+
This function is called by affine_matrix_from_points when usesvd=False
|
|
210
|
+
|
|
211
|
+
Author: this function was extracted from the original transformations.py
|
|
212
|
+
written by Christoph Golke:
|
|
213
|
+
https://www.lfd.uci.edu/~gohlke/code/transformations.py.html
|
|
214
|
+
|
|
215
|
+
"""
|
|
216
|
+
data = numpy.array(data, dtype=numpy.float64, copy=True)
|
|
217
|
+
if out is None:
|
|
218
|
+
if data.ndim == 1:
|
|
219
|
+
return math.sqrt(numpy.dot(data, data))
|
|
220
|
+
data *= data
|
|
221
|
+
out = numpy.atleast_1d(numpy.sum(data, axis=axis))
|
|
222
|
+
numpy.sqrt(out, out)
|
|
223
|
+
return out
|
|
224
|
+
else:
|
|
225
|
+
data *= data
|
|
226
|
+
numpy.sum(data, axis=axis, out=out)
|
|
227
|
+
numpy.sqrt(out, out)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _quaternion_matrix(quaternion):
|
|
231
|
+
"""Return homogeneous rotation matrix from quaternion.
|
|
232
|
+
|
|
233
|
+
>>> M = quaternion_matrix([0.99810947, 0.06146124, 0, 0])
|
|
234
|
+
>>> numpy.allclose(M, rotation_matrix(0.123, [1, 0, 0]))
|
|
235
|
+
True
|
|
236
|
+
>>> M = quaternion_matrix([1, 0, 0, 0])
|
|
237
|
+
>>> numpy.allclose(M, numpy.identity(4))
|
|
238
|
+
True
|
|
239
|
+
>>> M = quaternion_matrix([0, 1, 0, 0])
|
|
240
|
+
>>> numpy.allclose(M, numpy.diag([1, -1, -1, 1]))
|
|
241
|
+
True
|
|
242
|
+
|
|
243
|
+
This function is called by affine_matrix_from_points when usesvd=False
|
|
244
|
+
|
|
245
|
+
Author: this function was extracted from the original transformations.py
|
|
246
|
+
written by Christoph Golke:
|
|
247
|
+
https://www.lfd.uci.edu/~gohlke/code/transformations.py.html
|
|
248
|
+
"""
|
|
249
|
+
_EPS = np.finfo(float).eps * 5
|
|
250
|
+
|
|
251
|
+
q = numpy.array(quaternion, dtype=numpy.float64, copy=True)
|
|
252
|
+
n = numpy.dot(q, q)
|
|
253
|
+
if n < _EPS:
|
|
254
|
+
return numpy.identity(4)
|
|
255
|
+
q *= math.sqrt(2.0 / n)
|
|
256
|
+
q = numpy.outer(q, q)
|
|
257
|
+
return numpy.array([
|
|
258
|
+
[1.0-q[2, 2]-q[3, 3], q[1, 2]-q[3, 0], q[1, 3]+q[2, 0], 0.0],
|
|
259
|
+
[ q[1, 2]+q[3, 0], 1.0-q[1, 1]-q[3, 3], q[2, 3]-q[1, 0], 0.0],
|
|
260
|
+
[ q[1, 3]-q[2, 0], q[2, 3]+q[1, 0], 1.0-q[1, 1]-q[2, 2], 0.0],
|
|
261
|
+
[ 0.0, 0.0, 0.0, 1.0]])
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def rigid_transform_3D(fromA, toB, verbose=True):
|
|
265
|
+
"""rigid_transform_3D(fromA, toB, verbose=True)
|
|
266
|
+
|
|
267
|
+
INPUT
|
|
268
|
+
Afrom, Bto 3xn arrays = xyz coords of n points to be registered
|
|
269
|
+
|
|
270
|
+
OUTPUT
|
|
271
|
+
Rotation + translation transformation matrix registering fromA into toB
|
|
272
|
+
|
|
273
|
+
Author : Nghia Ho - 2013 - http://nghiaho.com/?page_id=671
|
|
274
|
+
"Finding optimal rotation and translation between corresponding 3D points"
|
|
275
|
+
Based on "A Method for Registration of 3-D Shapes", by Besl and McKay, 1992.
|
|
276
|
+
|
|
277
|
+
This is based on Singular Value Decomposition (SVD)
|
|
278
|
+
--> it is equivalent to affine_matrix_from_points with parameter usesvd=True
|
|
279
|
+
"""
|
|
280
|
+
A = fromA.T
|
|
281
|
+
B = toB.T
|
|
282
|
+
|
|
283
|
+
assert len(A) == len(B)
|
|
284
|
+
|
|
285
|
+
N = A.shape[0]; # total points
|
|
286
|
+
|
|
287
|
+
centroid_A = np.mean(A, axis=0)
|
|
288
|
+
centroid_B = np.mean(B, axis=0)
|
|
289
|
+
|
|
290
|
+
# centre the points
|
|
291
|
+
AA = A - np.tile(centroid_A, (N, 1))
|
|
292
|
+
BB = B - np.tile(centroid_B, (N, 1))
|
|
293
|
+
|
|
294
|
+
# @ is matrix multiplication for array
|
|
295
|
+
H = np.transpose(AA) @ BB
|
|
296
|
+
|
|
297
|
+
U, S, Vt = np.linalg.svd(H)
|
|
298
|
+
|
|
299
|
+
R = Vt.T @ U.T
|
|
300
|
+
|
|
301
|
+
# special reflection case
|
|
302
|
+
if np.linalg.det(R) < 0:
|
|
303
|
+
print ("Reflection detected")
|
|
304
|
+
Vt[2,:] *= -1
|
|
305
|
+
R = Vt.T @ U.T
|
|
306
|
+
|
|
307
|
+
t = -R @ centroid_A.T + centroid_B.T
|
|
308
|
+
|
|
309
|
+
result = np.identity(4)
|
|
310
|
+
result[:3,:3] = R
|
|
311
|
+
result[:3,3] = t
|
|
312
|
+
|
|
313
|
+
return result
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def translationRotationToTransformation(translation,rotation,rot_config="sxyz",active=True,degrees=True,translationFirst=False):
|
|
317
|
+
"""
|
|
318
|
+
translationRotationToTransformation(translation,rotation,rot_config="sxyz",active=True,degrees=True,translationFirst=False)
|
|
319
|
+
|
|
320
|
+
translationFirst : translation first
|
|
321
|
+
False first 3 rows of transformation matrix = (R t) [usual convention and default here]
|
|
322
|
+
True first 3 rows of transformation matrix = (R Rt) [used in the hexapod]
|
|
323
|
+
"""
|
|
324
|
+
import transforms3d as t3
|
|
325
|
+
import numpy as np
|
|
326
|
+
# Zoom - unit
|
|
327
|
+
zdef = np.array([1,1,1])
|
|
328
|
+
# Shear
|
|
329
|
+
sdef = np.array([0,0,0])
|
|
330
|
+
translation = np.array(translation)
|
|
331
|
+
#if degrees: rotation = np.deg2rad(np.array(rotation))
|
|
332
|
+
if degrees: rotation = np.array([np.deg2rad(item) for item in rotation])
|
|
333
|
+
rotx,roty,rotz = rotation
|
|
334
|
+
rmat = RotationMatrix(rotx,roty,rotz,rot_config=rot_config,active=active)
|
|
335
|
+
#
|
|
336
|
+
if translationFirst:
|
|
337
|
+
result = np.identity(4)
|
|
338
|
+
result[:3,:3] = rmat.R
|
|
339
|
+
result[:3,3] = rmat.R @ translation
|
|
340
|
+
else:
|
|
341
|
+
result = t3.affines.compose(translation,rmat.R,Z=zdef,S=sdef)
|
|
342
|
+
return result
|
|
343
|
+
|
|
344
|
+
def translationRotationFromTransformation(transformation,rot_config="sxyz",active=True,degrees=True,translationFirst=False):
|
|
345
|
+
"""
|
|
346
|
+
translationRotationFromTransformation(transformation,rot_config="sxyz",active=True,degrees=True,translationFirst=False)
|
|
347
|
+
|
|
348
|
+
translationFirst : translation first
|
|
349
|
+
False first 3 rows of transformation matrix = (R t) [usual convention and default here]
|
|
350
|
+
True first 3 rows of transformation matrix = (R Rt) [used in the hexapod]
|
|
351
|
+
"""
|
|
352
|
+
translation = transformation[:3,3]
|
|
353
|
+
rotation = t3.euler.mat2euler(transformation,axes=rot_config)
|
|
354
|
+
if degrees: rotation = np.array([np.rad2deg(item) for item in rotation])
|
|
355
|
+
if translationFirst:
|
|
356
|
+
translation = transformation[:3,:3].T @ translation
|
|
357
|
+
return translation,rotation
|
|
358
|
+
|
|
359
|
+
tr2T = translationRotationToTransformation
|
|
360
|
+
T2tr = translationRotationFromTransformation
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def vectorPlaneIntersection(pt, frame, epsilon=1.e-6):
|
|
364
|
+
"""
|
|
365
|
+
return the coordinates of the intersection of a vector with a plane.
|
|
366
|
+
|
|
367
|
+
pt = input vector. Point object, expressing the vector
|
|
368
|
+
vector origin = pt.ref.getOrigin().coordinates[:3]
|
|
369
|
+
vector direction = pt.coordinates[:3]
|
|
370
|
+
frame = input plane. ReferenceFrame object whose x-y plane is the target plane for intersection
|
|
371
|
+
|
|
372
|
+
If the vector's own reference frame is 'frame', the problem is trivial
|
|
373
|
+
|
|
374
|
+
In all cases, the coordinates of the interesection point are provided as a Point object, in "frame" coordinates
|
|
375
|
+
|
|
376
|
+
Ref:
|
|
377
|
+
https://stackoverflow.com/questions/5666222/3d-line-plane-intersection
|
|
378
|
+
"""
|
|
379
|
+
|
|
380
|
+
from egse.coordinates.point import Point
|
|
381
|
+
|
|
382
|
+
if pt.ref == frame:
|
|
383
|
+
# The point is defined in frame => the origin of the vector is the origin of the target plane.
|
|
384
|
+
return np.array([0,0,0])
|
|
385
|
+
else:
|
|
386
|
+
# Express all inputs in 'frame'
|
|
387
|
+
|
|
388
|
+
# Vector Origin (p0)
|
|
389
|
+
vec_orig = Point(pt.ref.getOrigin().coordinates[:3],ref=pt.ref, name='ptorig').expressIn(frame)[:3]
|
|
390
|
+
# Vector End (p1)
|
|
391
|
+
vec_end = pt.expressIn(frame)[:3]
|
|
392
|
+
# Vector (u)
|
|
393
|
+
vec = vec_end - vec_orig
|
|
394
|
+
|
|
395
|
+
# A point in Plane (pco)
|
|
396
|
+
#plane_orig = np.array([0,0,0],dtype=float)
|
|
397
|
+
plane_orig = frame.getOrigin().coordinates[:3]
|
|
398
|
+
# Normal to the plane (pno)
|
|
399
|
+
plane_normal = frame.getAxis('z').coordinates[:3]
|
|
400
|
+
|
|
401
|
+
# Vector to normal 'angle'
|
|
402
|
+
vec_x_normal = np.dot(vec, plane_normal)
|
|
403
|
+
|
|
404
|
+
# Test if there is an intersection (and if it's unique)
|
|
405
|
+
# --> input vector and normal mustn't be perpendicular, else the vector is // to the plane or inside it
|
|
406
|
+
#
|
|
407
|
+
if (np.allclose(vec_x_normal, 0., atol=epsilon)):
|
|
408
|
+
print('The input vector is // to the plane normal (or inside the plane)')
|
|
409
|
+
print('--> there exists no intersection (or an infinity of them)')
|
|
410
|
+
return None
|
|
411
|
+
else:
|
|
412
|
+
# Vector from the point in the plane to the origin of the vector (w)
|
|
413
|
+
plane_to_vec = vec_orig - plane_orig
|
|
414
|
+
|
|
415
|
+
# Solution ("how many 'vectors' away is the interesection ?")
|
|
416
|
+
vec_multiplicator = - np.dot(plane_normal, plane_to_vec) / vec_x_normal
|
|
417
|
+
|
|
418
|
+
return Point(vec_orig + (vec * vec_multiplicator), ref=frame)
|
|
419
|
+
|