patme 0.4.4__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.
Potentially problematic release.
This version of patme might be problematic. Click here for more details.
- patme/__init__.py +52 -0
- patme/buildtools/__init__.py +7 -0
- patme/buildtools/rce_releasecreator.py +336 -0
- patme/buildtools/release.py +26 -0
- patme/femtools/__init__.py +5 -0
- patme/femtools/abqmsgfilechecker.py +137 -0
- patme/femtools/fecall.py +1092 -0
- patme/geometry/__init__.py +0 -0
- patme/geometry/area.py +124 -0
- patme/geometry/coordinatesystem.py +635 -0
- patme/geometry/intersect.py +284 -0
- patme/geometry/line.py +183 -0
- patme/geometry/misc.py +420 -0
- patme/geometry/plane.py +464 -0
- patme/geometry/rotate.py +244 -0
- patme/geometry/scale.py +152 -0
- patme/geometry/shape2d.py +50 -0
- patme/geometry/transformations.py +1831 -0
- patme/geometry/translate.py +139 -0
- patme/mechanics/__init__.py +4 -0
- patme/mechanics/loads.py +435 -0
- patme/mechanics/material.py +1260 -0
- patme/service/__init__.py +7 -0
- patme/service/decorators.py +85 -0
- patme/service/duration.py +96 -0
- patme/service/exceptionhook.py +104 -0
- patme/service/exceptions.py +36 -0
- patme/service/io/__init__.py +3 -0
- patme/service/io/basewriter.py +122 -0
- patme/service/logger.py +375 -0
- patme/service/mathutils.py +108 -0
- patme/service/misc.py +71 -0
- patme/service/moveimports.py +217 -0
- patme/service/stringutils.py +419 -0
- patme/service/systemutils.py +290 -0
- patme/sshtools/__init__.py +3 -0
- patme/sshtools/cara.py +435 -0
- patme/sshtools/clustercaller.py +420 -0
- patme/sshtools/facluster.py +350 -0
- patme/sshtools/sshcall.py +168 -0
- patme-0.4.4.dist-info/LICENSE +21 -0
- patme-0.4.4.dist-info/LICENSES/MIT.txt +9 -0
- patme-0.4.4.dist-info/METADATA +168 -0
- patme-0.4.4.dist-info/RECORD +46 -0
- patme-0.4.4.dist-info/WHEEL +4 -0
- patme-0.4.4.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
# Copyright (C) 2020 Deutsches Zentrum fuer Luft- und Raumfahrt(DLR, German Aerospace Center) <www.dlr.de>
|
|
2
|
+
# SPDX-FileCopyrightText: 2022 German Aerospace Center (DLR)
|
|
3
|
+
#
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
The following conditions and Copyright is related to two functionalities of Transformation:
|
|
8
|
+
Transformation.setRotationByAxisAndAngle()
|
|
9
|
+
Transformation.estimateTransformationFromPointSets()
|
|
10
|
+
|
|
11
|
+
For detailed information please refer to '<http://www.lfd.uci.edu/~gohlke/code/transformations.py.html>'_ (23.01.2014).
|
|
12
|
+
|
|
13
|
+
References
|
|
14
|
+
----------
|
|
15
|
+
(1) Matrices and transformations. Ronald Goldman.
|
|
16
|
+
In "Graphics Gems I", pp 472-475. Morgan Kaufmann, 1990.
|
|
17
|
+
(2) More matrices and transformations: shear and pseudo-perspective.
|
|
18
|
+
Ronald Goldman. In "Graphics Gems II", pp 320-323. Morgan Kaufmann, 1991.
|
|
19
|
+
(3) Decomposing a matrix into simple transformations. Spencer Thomas.
|
|
20
|
+
In "Graphics Gems II", pp 320-323. Morgan Kaufmann, 1991.
|
|
21
|
+
(4) Recovering the data from the transformation matrix. Ronald Goldman.
|
|
22
|
+
In "Graphics Gems II", pp 324-331. Morgan Kaufmann, 1991.
|
|
23
|
+
(5) Euler angle conversion. Ken Shoemake.
|
|
24
|
+
In "Graphics Gems IV", pp 222-229. Morgan Kaufmann, 1994.
|
|
25
|
+
(6) Arcball rotation control. Ken Shoemake.
|
|
26
|
+
In "Graphics Gems IV", pp 175-192. Morgan Kaufmann, 1994.
|
|
27
|
+
(7) Representing attitude: Euler angles, unit quaternions, and rotation
|
|
28
|
+
vectors. James Diebel. 2006.
|
|
29
|
+
(8) A discussion of the solution for the best rotation to relate two sets
|
|
30
|
+
of vectors. W Kabsch. Acta Cryst. 1978. A34, 827-828.
|
|
31
|
+
(9) Closed-form solution of absolute orientation using unit quaternions.
|
|
32
|
+
BKP Horn. J Opt Soc Am A. 1987. 4(4):629-642.
|
|
33
|
+
(10) Quaternions. Ken Shoemake.
|
|
34
|
+
http://www.sfu.ca/~jwa3/cmpt461/files/quatut.pdf
|
|
35
|
+
(11) From quaternion to matrix and back. JMP van Waveren. 2005.
|
|
36
|
+
http://www.intel.com/cd/ids/developer/asmo-na/eng/293748.htm
|
|
37
|
+
(12) Uniform random rotations. Ken Shoemake.
|
|
38
|
+
In "Graphics Gems III", pp 124-132. Morgan Kaufmann, 1992.
|
|
39
|
+
(13) Quaternion in molecular modeling. CFF Karney.
|
|
40
|
+
J Mol Graph Mod, 25(5):595-604
|
|
41
|
+
(14) New method for extracting the quaternion from a rotation matrix.
|
|
42
|
+
Itzhack Y Bar-Itzhack, J Guid Contr Dynam. 2000. 23(6): 1085-1087.
|
|
43
|
+
(15) Multiple View Geometry in Computer Vision. Hartley and Zissermann.
|
|
44
|
+
Cambridge University Press; 2nd Ed. 2004. Chapter 4, Algorithm 4.7, p 130.
|
|
45
|
+
(16) Column Vectors vs. Row Vectors.
|
|
46
|
+
http://steve.hollasch.net/cgindex/math/matrix/column-vec.html
|
|
47
|
+
"""
|
|
48
|
+
# Copyright (c) 2006-2014, Christoph Gohlke
|
|
49
|
+
# Copyright (c) 2006-2014, The Regents of the University of California
|
|
50
|
+
# Produced at the Laboratory for Fluorescence Dynamics
|
|
51
|
+
# All rights reserved.
|
|
52
|
+
#
|
|
53
|
+
# Redistribution and use in source and binary forms, with or without
|
|
54
|
+
# modification, are permitted provided that the following conditions are met:
|
|
55
|
+
#
|
|
56
|
+
# * Redistributions of source code must retain the above copyright
|
|
57
|
+
# notice, this list of conditions and the following disclaimer.
|
|
58
|
+
# * Redistributions in binary form must reproduce the above copyright
|
|
59
|
+
# notice, this list of conditions and the following disclaimer in the
|
|
60
|
+
# documentation and/or other materials provided with the distribution.
|
|
61
|
+
# * Neither the name of the copyright holders nor the names of any
|
|
62
|
+
# contributors may be used to endorse or promote products derived
|
|
63
|
+
# from this software without specific prior written permission.
|
|
64
|
+
#
|
|
65
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
66
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
67
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
68
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
69
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
70
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
71
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
72
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
73
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
74
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
75
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
|
76
|
+
|
|
77
|
+
import warnings
|
|
78
|
+
|
|
79
|
+
import numpy as np
|
|
80
|
+
|
|
81
|
+
from patme import epsilon
|
|
82
|
+
from patme.geometry.plane import Plane
|
|
83
|
+
from patme.geometry.rotate import Rotation
|
|
84
|
+
from patme.geometry.scale import Scaling
|
|
85
|
+
from patme.geometry.translate import Translation
|
|
86
|
+
from patme.service.exceptions import ImproperParameterError, InternalError
|
|
87
|
+
from patme.service.logger import log
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class Transformation(np.ndarray):
|
|
91
|
+
"""
|
|
92
|
+
This class describes a Transformation in 3D space which is represented by a 3x3 matrix.
|
|
93
|
+
|
|
94
|
+
With this class coordinate transformations in 3D space can be created and operations may be performed.
|
|
95
|
+
For that purpose the class inherits from np.ndarray which is a class for storing
|
|
96
|
+
matrix values and performing operations on them. For further details on np.ndarray
|
|
97
|
+
please refer to the numpy/scipy documentation.
|
|
98
|
+
|
|
99
|
+
Transformations are represented by a 4x4 matrix. A transformation is composed of
|
|
100
|
+
a rotation matrix r_ii, a translation t_i, a scaling s_i and a projection p_i the following way::
|
|
101
|
+
|
|
102
|
+
[[ s_1*r_11, r_21, r_31, t_1],
|
|
103
|
+
[ r_12, s_2*r_22, r_32, t_2],
|
|
104
|
+
[ r_13, r_23, s_3*r_33, t_3],
|
|
105
|
+
[ p_1 , p_2 , p_3 , 1 ]]
|
|
106
|
+
|
|
107
|
+
Actually Scalings are not actively supported in this class. Thus is it still
|
|
108
|
+
mathematically possible.
|
|
109
|
+
|
|
110
|
+
Transformations can be created and used in the following ways::
|
|
111
|
+
|
|
112
|
+
>>> import numpy as np
|
|
113
|
+
>>> from patme.geometry.coordinatesystem import Transformation
|
|
114
|
+
>>> from patme.geometry.translate import Translation
|
|
115
|
+
>>> from patme.geometry.rotate import Rotation
|
|
116
|
+
>>> t = Translation([5,2,0])
|
|
117
|
+
>>> # creating identity transformation
|
|
118
|
+
>>> tr = Transformation()
|
|
119
|
+
>>> tr
|
|
120
|
+
Transformation([[1., 0., 0., 0.],
|
|
121
|
+
[0., 1., 0., 0.],
|
|
122
|
+
[0., 0., 1., 0.],
|
|
123
|
+
[0., 0., 0., 1.]])
|
|
124
|
+
>>> # setting a translation
|
|
125
|
+
>>> tr.translation = t
|
|
126
|
+
>>> tr
|
|
127
|
+
Transformation([[1., 0., 0., 5.],
|
|
128
|
+
[0., 1., 0., 2.],
|
|
129
|
+
[0., 0., 1., 0.],
|
|
130
|
+
[0., 0., 0., 1.]])
|
|
131
|
+
|
|
132
|
+
>>> ####################### adding a translation
|
|
133
|
+
>>> tr.addTranslation(t)
|
|
134
|
+
>>> tr
|
|
135
|
+
Transformation([[ 1., 0., 0., 10.],
|
|
136
|
+
[ 0., 1., 0., 4.],
|
|
137
|
+
[ 0., 0., 1., 0.],
|
|
138
|
+
[ 0., 0., 0., 1.]])
|
|
139
|
+
|
|
140
|
+
>>> ####################### setting a rotation
|
|
141
|
+
>>> rx = Rotation()
|
|
142
|
+
>>> rx.angles = (np.pi/2,0,0)
|
|
143
|
+
>>> ry = Rotation()
|
|
144
|
+
>>> ry.angles = (0,np.pi/2,0)
|
|
145
|
+
>>> tr.rotation = rx
|
|
146
|
+
>>> tr.round() + 0 # ".round()" avoids long floats and "+ 0" turns all -0 in 0
|
|
147
|
+
Transformation([[ 1., 0., 0., 10.],
|
|
148
|
+
[ 0., 0., -1., 4.],
|
|
149
|
+
[ 0., 1., 0., 0.],
|
|
150
|
+
[ 0., 0., 0., 1.]])
|
|
151
|
+
>>> # adding a rotation
|
|
152
|
+
>>> tr.addRotation(ry)
|
|
153
|
+
>>> tr.round() + 0
|
|
154
|
+
Transformation([[ 0., 0., 1., 10.],
|
|
155
|
+
[ 1., 0., 0., 4.],
|
|
156
|
+
[ 0., 1., 0., 0.],
|
|
157
|
+
[ 0., 0., 0., 1.]])
|
|
158
|
+
>>> # concatenation of transformations
|
|
159
|
+
>>> tr2 = Transformation()
|
|
160
|
+
>>> tr2.translation = [0,0,1]
|
|
161
|
+
>>> tr3 = tr*tr2
|
|
162
|
+
>>> tr3.round() + 0
|
|
163
|
+
Transformation([[ 0., 0., 1., 11.],
|
|
164
|
+
[ 1., 0., 0., 4.],
|
|
165
|
+
[ 0., 1., 0., 0.],
|
|
166
|
+
[ 0., 0., 0., 1.]])
|
|
167
|
+
>>> # inverting transformation
|
|
168
|
+
>>> tr3inv = tr3.getInverse()
|
|
169
|
+
>>> tr3inv.round() + 0
|
|
170
|
+
Transformation([[ 0., 1., 0., -4.],
|
|
171
|
+
[ 0., 0., 1., 0.],
|
|
172
|
+
[ 1., 0., 0., -11.],
|
|
173
|
+
[ 0., 0., 0., 1.]])
|
|
174
|
+
>>> (tr3*tr3inv).round() + 0 # retruns identity matrix
|
|
175
|
+
Transformation([[1., 0., 0., 0.],
|
|
176
|
+
[0., 1., 0., 0.],
|
|
177
|
+
[0., 0., 1., 0.],
|
|
178
|
+
[0., 0., 0., 1.]])
|
|
179
|
+
|
|
180
|
+
>>> ####################### transformation of translations
|
|
181
|
+
>>> # MOST IMPORTANT !!!
|
|
182
|
+
>>> tr3*t
|
|
183
|
+
Translation([11., 9., 2.])
|
|
184
|
+
>>> t1 = tr3inv * (tr3 * t)
|
|
185
|
+
>>> t1.round()
|
|
186
|
+
Translation([5., 2., 0.])
|
|
187
|
+
|
|
188
|
+
>>> ####################### setting a scaling
|
|
189
|
+
>>> sx = Scaling()
|
|
190
|
+
>>> sx.factors = (2.0,1.0,1.0)
|
|
191
|
+
>>> sxyz = Scaling()
|
|
192
|
+
>>> sxyz.factors = (2.0,2.0,2.0)
|
|
193
|
+
>>> tr.scaling = sx # also resets the rotation
|
|
194
|
+
>>> tr
|
|
195
|
+
Transformation([[ 2., 0., 0., 10.],
|
|
196
|
+
[ 0., 1., 0., 4.],
|
|
197
|
+
[ 0., 0., 1., 0.],
|
|
198
|
+
[ 0., 0., 0., 1.]])
|
|
199
|
+
>>> # adding a scaling
|
|
200
|
+
>>> tr.addScaling(sxyz)
|
|
201
|
+
>>> tr
|
|
202
|
+
Transformation([[ 4., 0., 0., 10.],
|
|
203
|
+
[ 0., 2., 0., 4.],
|
|
204
|
+
[ 0., 0., 2., 0.],
|
|
205
|
+
[ 0., 0., 0., 1.]])
|
|
206
|
+
"""
|
|
207
|
+
|
|
208
|
+
__hash__ = object.__hash__
|
|
209
|
+
"""hash reimplementation due to definition of __eq__. See __hash__ doc for more details.
|
|
210
|
+
https://docs.python.org/3.4/reference/datamodel.html#object.__hash__"""
|
|
211
|
+
|
|
212
|
+
def __new__(cls, input_array=None, tId=None, description=None):
|
|
213
|
+
"""constructing np.ndarray instance. For more information see
|
|
214
|
+
http://docs.scipy.org/doc/numpy/user/basics.subclassing.html#basics-subclassing"""
|
|
215
|
+
# Input array is an already formed ndarray instance
|
|
216
|
+
# We first cast to be our class type
|
|
217
|
+
if input_array is None:
|
|
218
|
+
input_array = np.identity(4, dtype=np.float64)
|
|
219
|
+
if not hasattr(input_array, "shape"):
|
|
220
|
+
input_array = np.asarray(input_array)
|
|
221
|
+
if not input_array.shape == (4, 4):
|
|
222
|
+
raise ImproperParameterError(
|
|
223
|
+
"The given transformation must be a 4x4 matrix like object. Got this instead: " + str(input_array)
|
|
224
|
+
)
|
|
225
|
+
obj = input_array.view(cls)
|
|
226
|
+
return obj
|
|
227
|
+
|
|
228
|
+
def setTransformation(self, t=None, r=None, s=None, t_id=None):
|
|
229
|
+
"""doc"""
|
|
230
|
+
if np.any(t):
|
|
231
|
+
self[:3, 3] = t * 1
|
|
232
|
+
|
|
233
|
+
# # scale the transformation matrix
|
|
234
|
+
# # get scaling in each dimension and perform a matrix multiplication
|
|
235
|
+
# sc1 = trafo.scale_matrix(s.vals.x, None, [1, 0, 0])
|
|
236
|
+
# sc2 = trafo.scale_matrix(s.vals.y, None, [0, 1, 0])
|
|
237
|
+
# sc3 = trafo.scale_matrix(s.vals.z, None, [0, 0, 1])
|
|
238
|
+
# self = np.dot(np.dot(sc1, sc2), np.dot(sc3, self))
|
|
239
|
+
|
|
240
|
+
if np.any(r):
|
|
241
|
+
self[:3, :3] = r
|
|
242
|
+
if np.any(s):
|
|
243
|
+
self.addScaling(s)
|
|
244
|
+
if t_id:
|
|
245
|
+
self.id = t_id
|
|
246
|
+
|
|
247
|
+
def isIdentity(self):
|
|
248
|
+
"""doc"""
|
|
249
|
+
return np.allclose(np.identity(4, dtype=np.float64), self, atol=epsilon)
|
|
250
|
+
|
|
251
|
+
def getInverse(self):
|
|
252
|
+
"""invert Transformation
|
|
253
|
+
Rotation = R^transpose and Position = -R^transposed*p"""
|
|
254
|
+
ret = Transformation()
|
|
255
|
+
rT = self.rotation.T
|
|
256
|
+
sT = self.scaling.getInverse()
|
|
257
|
+
ret[:3, :3] = rT * sT
|
|
258
|
+
ret[:3, 3] = (rT * self.translation) * -1
|
|
259
|
+
return ret
|
|
260
|
+
|
|
261
|
+
def __invert__(self):
|
|
262
|
+
"""invert Coordinatesystem
|
|
263
|
+
Rotation = R^Transponiert und Position = -R^Transponiert*p"""
|
|
264
|
+
self[:, :] = self.getInverse()
|
|
265
|
+
|
|
266
|
+
def __mul__(self, other):
|
|
267
|
+
"""doc"""
|
|
268
|
+
if other is None:
|
|
269
|
+
raise ImproperParameterError("Got None for multiplication with transformation")
|
|
270
|
+
|
|
271
|
+
otherOrg = other
|
|
272
|
+
other = np.asarray(other)
|
|
273
|
+
if other.shape == (3,):
|
|
274
|
+
other = np.array([other[0], other[1], other[2], 1.0])
|
|
275
|
+
cls = otherOrg.__class__
|
|
276
|
+
if cls is np.ndarray:
|
|
277
|
+
cls = np.array
|
|
278
|
+
return cls((self @ other)[:3])
|
|
279
|
+
|
|
280
|
+
elif other.shape == (4,):
|
|
281
|
+
return np.array(self @ other)
|
|
282
|
+
|
|
283
|
+
elif other.shape == (4, 4):
|
|
284
|
+
return self @ other
|
|
285
|
+
|
|
286
|
+
elif other.shape[0] == 3:
|
|
287
|
+
# input is interpreted as array comprising of Translations
|
|
288
|
+
other = np.insert(other, 3, 1, axis=0)
|
|
289
|
+
return np.array(self @ other)[:3, :]
|
|
290
|
+
|
|
291
|
+
elif other.shape[0] == 4:
|
|
292
|
+
return np.array(np.dot(self, other))
|
|
293
|
+
|
|
294
|
+
# elif other.shape[1] == 3:
|
|
295
|
+
# other = np.insert(other, 3, 1, axis=1)
|
|
296
|
+
# return np.array(self @ other.T).T[:,:3]
|
|
297
|
+
|
|
298
|
+
# elif other.shape[1] == 4:
|
|
299
|
+
# return np.array(np.dot(self, other.T)).T
|
|
300
|
+
else:
|
|
301
|
+
raise ImproperParameterError(
|
|
302
|
+
"only array sizes of 3, 4, 4x4, 3xn and 4xn are supported. Got instead: " + str(other.shape)
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
def __imul__(self, other):
|
|
306
|
+
"""doc"""
|
|
307
|
+
return self * other
|
|
308
|
+
|
|
309
|
+
def __eq__(self, other):
|
|
310
|
+
"""doc"""
|
|
311
|
+
try:
|
|
312
|
+
return np.allclose(self, other)
|
|
313
|
+
except TypeError:
|
|
314
|
+
return False
|
|
315
|
+
|
|
316
|
+
def addTranslation(self, t):
|
|
317
|
+
"""doc"""
|
|
318
|
+
if np.array(t).shape != (3,):
|
|
319
|
+
log.error(
|
|
320
|
+
'Could not set translation "%s". An iterable with 3 scalars or an object of type Translation is needed!'
|
|
321
|
+
% t
|
|
322
|
+
)
|
|
323
|
+
self[:3, 3] += t[:]
|
|
324
|
+
|
|
325
|
+
def addRotation(self, rot):
|
|
326
|
+
"""doc"""
|
|
327
|
+
if np.array(rot).shape != (3, 3):
|
|
328
|
+
raise ImproperParameterError(
|
|
329
|
+
'Could not set rotation "%s". An iterable with 3x3 scalars or an object of type Rotation is needed!'
|
|
330
|
+
% rot
|
|
331
|
+
)
|
|
332
|
+
self[:3, :3] = np.dot(self[:3, :3], rot)
|
|
333
|
+
|
|
334
|
+
def addScaling(self, scal):
|
|
335
|
+
"""doc"""
|
|
336
|
+
if np.array(scal).shape != (3, 3):
|
|
337
|
+
raise ImproperParameterError(
|
|
338
|
+
'Could not set scaling "%s". An iterable with 3x3 scalars or an object of type Scaling is needed!'
|
|
339
|
+
% scal
|
|
340
|
+
)
|
|
341
|
+
self[:3, :3] = np.dot(self[:3, :3], scal)
|
|
342
|
+
|
|
343
|
+
def setRotationByAxisAndAngle(self, axis, angle):
|
|
344
|
+
"""This method converts a rotation around a specified axis by a specified angle into rotation matrix.
|
|
345
|
+
The mathematical algorithm can be found in [Wiki2013]_ and also in [Tayl2013]_.
|
|
346
|
+
|
|
347
|
+
:param axis: instance of L{Translation}, specifying the rotational axis to be rotated about
|
|
348
|
+
:param angle: float, variable specifying the rotating angle in degrees about the axis
|
|
349
|
+
.. note:: The angle is being transformed into radians.
|
|
350
|
+
|
|
351
|
+
.. _[Wiki2013]: http://en.wikipedia.org/wiki/Rotation_matrix#cite_ref-2 (08.10.2013)
|
|
352
|
+
.. _[Tayl1994] Taylor, Camillo; Kriegman (1994). "Minimization on the Lie Group SO(3) and Related Manifolds". Technical Report. No. 9405 (Yale University).
|
|
353
|
+
"""
|
|
354
|
+
|
|
355
|
+
if np.array(axis).shape != (3,):
|
|
356
|
+
log.error("Could not set rotation. An iterable with 3 scalars or an object of type Translation is needed!")
|
|
357
|
+
|
|
358
|
+
if not (isinstance(angle, float) or isinstance(angle, int)):
|
|
359
|
+
log.error("Could not set rotation. An angle in radiant of type float is needed!")
|
|
360
|
+
|
|
361
|
+
angle *= np.pi / 180.0
|
|
362
|
+
unitVector = axis / np.linalg.norm(axis)
|
|
363
|
+
sinAngle, cosAngle = np.sin(angle), np.cos(angle)
|
|
364
|
+
|
|
365
|
+
r = np.diag(cosAngle + unitVector[:3] ** 2 * (1.0 - cosAngle))
|
|
366
|
+
|
|
367
|
+
r[0, 1] = unitVector[0] * unitVector[1] * (1.0 - cosAngle) - unitVector[2] * sinAngle
|
|
368
|
+
r[1, 0] = unitVector[0] * unitVector[1] * (1.0 - cosAngle) + unitVector[2] * sinAngle
|
|
369
|
+
|
|
370
|
+
r[0, 2] = unitVector[0] * unitVector[2] * (1.0 - cosAngle) + unitVector[1] * sinAngle
|
|
371
|
+
r[2, 0] = unitVector[0] * unitVector[2] * (1.0 - cosAngle) - unitVector[1] * sinAngle
|
|
372
|
+
|
|
373
|
+
r[1, 2] = unitVector[1] * unitVector[2] * (1.0 - cosAngle) - unitVector[0] * sinAngle
|
|
374
|
+
r[2, 1] = unitVector[1] * unitVector[2] * (1.0 - cosAngle) + unitVector[0] * sinAngle
|
|
375
|
+
|
|
376
|
+
self.rotation = r
|
|
377
|
+
|
|
378
|
+
def getAxisAndAngleByRotation(self):
|
|
379
|
+
"""This method is intended to decompose the transformation instance into one rotation axis and a corresponding rotation angle.
|
|
380
|
+
The mathematical algorithm can be found in [Wiki2013]_.
|
|
381
|
+
|
|
382
|
+
:returns: tuple, containing the rotation axis as Translation and the rotation angle in radians
|
|
383
|
+
|
|
384
|
+
.. _[Wiki2013]: http://en.wikipedia.org/wiki/Rotation_matrix#cite_ref-2 (08.10.2013)
|
|
385
|
+
"""
|
|
386
|
+
|
|
387
|
+
def getAxis(rotationMatrix):
|
|
388
|
+
|
|
389
|
+
rotMatEigenResult = np.linalg.eig(rotationMatrix)
|
|
390
|
+
# ---FIND MATRIX EIGENVALUE EQUAL TO 1
|
|
391
|
+
if not complex(1.0) in rotMatEigenResult[0]:
|
|
392
|
+
raise InternalError("Result of eigenanalysis of rotation matrix contains no eigenvalue equal to 1.")
|
|
393
|
+
else:
|
|
394
|
+
for i in [0, 1, 2]:
|
|
395
|
+
if rotMatEigenResult[0][i] == complex(1.0):
|
|
396
|
+
eigenValueIndex = i
|
|
397
|
+
|
|
398
|
+
# ---X-COORDINATE
|
|
399
|
+
xReal = rotMatEigenResult[1][0][eigenValueIndex].real
|
|
400
|
+
xImag = rotMatEigenResult[1][0][eigenValueIndex].imag
|
|
401
|
+
if xImag > epsilon:
|
|
402
|
+
raise InternalError("Rotation axis x-coordinate is complex.")
|
|
403
|
+
|
|
404
|
+
yReal = rotMatEigenResult[1][1][eigenValueIndex].real
|
|
405
|
+
yImag = rotMatEigenResult[1][1][eigenValueIndex].imag
|
|
406
|
+
if yImag > epsilon:
|
|
407
|
+
raise InternalError("Rotation axis y-coordinate is complex.")
|
|
408
|
+
|
|
409
|
+
zReal = rotMatEigenResult[1][2][eigenValueIndex].real
|
|
410
|
+
zImag = rotMatEigenResult[1][2][eigenValueIndex].imag
|
|
411
|
+
if zImag > epsilon:
|
|
412
|
+
raise InternalError("Rotation axis z-coordinate is complex.")
|
|
413
|
+
|
|
414
|
+
return Translation([xReal, yReal, zReal])
|
|
415
|
+
|
|
416
|
+
rotationMatrix = self.getRotation()
|
|
417
|
+
# ---DETERMINE AXIS
|
|
418
|
+
# the roation axis has to be an eigenvector of the rotation matrix
|
|
419
|
+
# the axis is equivalent to the eigenvector belonging to the first eigenvalue of the matrix
|
|
420
|
+
with warnings.catch_warnings():
|
|
421
|
+
|
|
422
|
+
warnings.simplefilter("ignore")
|
|
423
|
+
axis = getAxis(rotationMatrix)
|
|
424
|
+
|
|
425
|
+
# ---DETERMINE ANGLE
|
|
426
|
+
# ---RETRIEVE VECTOR PERPENDICULAR TO AXIS
|
|
427
|
+
tmpPlane = Plane().generatePlane(positioningPoint=Translation([0.0, 0.0, 0.0]), planeNormalVector=axis)
|
|
428
|
+
axisPerpendiculaVector = tmpPlane.planeOrientationVector1
|
|
429
|
+
|
|
430
|
+
rotatedAxisPerpendiculaVector = rotationMatrix * axisPerpendiculaVector
|
|
431
|
+
arcos = np.dot(axisPerpendiculaVector, rotatedAxisPerpendiculaVector)
|
|
432
|
+
arcos = arcos / (np.linalg.norm(axisPerpendiculaVector) * np.linalg.norm(rotatedAxisPerpendiculaVector))
|
|
433
|
+
|
|
434
|
+
return (axis, np.arccos(arcos))
|
|
435
|
+
|
|
436
|
+
def estimateTransformationFromPointSets(self, fromPoints, toPoints):
|
|
437
|
+
r"""
|
|
438
|
+
This method is intended for superimposing arrays of 3D homogeneous coordinates.
|
|
439
|
+
It calculates the Transformation representing the transformation of the fromPoints into
|
|
440
|
+
the corresponding toPoints.
|
|
441
|
+
|
|
442
|
+
:param fromPoints: list, containing at least three instances of Translation
|
|
443
|
+
:param toPoints: list, containing at least three instances of Translation corresponding to the points within the list toPoints
|
|
444
|
+
|
|
445
|
+
:returns: self, with translation, rotation and scaling set adequately
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
:Author:
|
|
449
|
+
`Christoph Gohlke <http://www.lfd.uci.edu/~gohlke/>`_
|
|
450
|
+
|
|
451
|
+
:Editor:
|
|
452
|
+
`Falk Heinecke 23.01.2014`
|
|
453
|
+
|
|
454
|
+
:Organization:
|
|
455
|
+
Laboratory for Fluorescence Dynamics, University of California, Irvine
|
|
456
|
+
|
|
457
|
+
:Version: 2013.06.29
|
|
458
|
+
|
|
459
|
+
Requirements
|
|
460
|
+
------------
|
|
461
|
+
* `CPython 2.7 or 3.3 <http://www.python.org>`_
|
|
462
|
+
* `Numpy 1.7 <http://www.numpy.org>`_
|
|
463
|
+
* `Transformations.c 2013.01.18 <http://www.lfd.uci.edu/~gohlke/>`_
|
|
464
|
+
(recommended for speedup of some functions)
|
|
465
|
+
|
|
466
|
+
Return affine transform matrix to register two point sets.
|
|
467
|
+
|
|
468
|
+
v0 and v1 are shape (ndims, \*) arrays of at least ndims non-homogeneous
|
|
469
|
+
coordinates, where ndims is the dimensionality of the coordinate space.
|
|
470
|
+
|
|
471
|
+
A rigid/Euclidean transformation matrix is returned.
|
|
472
|
+
|
|
473
|
+
Similarity and Euclidean transformation matrices
|
|
474
|
+
are calculated by minimizing the weighted sum of squared deviations
|
|
475
|
+
(RMSD) according to the algorithm by Kabsch [8].
|
|
476
|
+
|
|
477
|
+
Example:
|
|
478
|
+
|
|
479
|
+
>>> v0 = [Translation([1., 0., 0.]), Translation([0., 1., 0.]), Translation([0., 0., 1.])]
|
|
480
|
+
>>> v1 = [Translation([2., 0., 0.]), Translation([1., 1., 0.]), Translation([1., 0., 1.])]
|
|
481
|
+
>>> tr = Transformation().estimateTransformationFromPointSets(v0, v1)
|
|
482
|
+
>>> tr.round().astype(int)
|
|
483
|
+
Transformation([[1, 0, 0, 1],
|
|
484
|
+
[0, 1, 0, 0],
|
|
485
|
+
[0, 0, 1, 0],
|
|
486
|
+
[0, 0, 0, 1]])
|
|
487
|
+
"""
|
|
488
|
+
|
|
489
|
+
fromPoints = np.array(fromPoints, dtype=np.float64, copy=True)
|
|
490
|
+
toPoints = np.array(toPoints, dtype=np.float64, copy=True)
|
|
491
|
+
# points need to be stored as columns within the array
|
|
492
|
+
fromPoints = np.transpose(fromPoints)
|
|
493
|
+
toPoints = np.transpose(toPoints)
|
|
494
|
+
|
|
495
|
+
ndims = fromPoints.shape[0]
|
|
496
|
+
if ndims < 2 or fromPoints.shape[1] < ndims or fromPoints.shape != toPoints.shape:
|
|
497
|
+
raise ValueError("input arrays are of wrong shape or type")
|
|
498
|
+
|
|
499
|
+
# move centroids to origin
|
|
500
|
+
t0 = -np.mean(fromPoints, axis=1)
|
|
501
|
+
M0 = np.identity(ndims + 1)
|
|
502
|
+
M0[:ndims, ndims] = t0
|
|
503
|
+
fromPoints += t0.reshape(ndims, 1)
|
|
504
|
+
t1 = -np.mean(toPoints, axis=1)
|
|
505
|
+
M1 = np.identity(ndims + 1)
|
|
506
|
+
M1[:ndims, ndims] = t1
|
|
507
|
+
toPoints += t1.reshape(ndims, 1)
|
|
508
|
+
|
|
509
|
+
# Rigid transformation via SVD of covariance matrix
|
|
510
|
+
u, s, vh = np.linalg.svd(np.dot(toPoints, fromPoints.T))
|
|
511
|
+
# rotation matrix from SVD orthonormal bases
|
|
512
|
+
R = np.dot(u, vh)
|
|
513
|
+
if np.linalg.det(R) < 0.0:
|
|
514
|
+
# R does not constitute right handed system
|
|
515
|
+
R -= np.outer(u[:, ndims - 1], vh[ndims - 1, :] * 2.0)
|
|
516
|
+
s[-1] *= -1.0
|
|
517
|
+
# homogeneous transformation matrix
|
|
518
|
+
M = np.identity(ndims + 1)
|
|
519
|
+
M[:ndims, :ndims] = R
|
|
520
|
+
|
|
521
|
+
if 1: # scale:
|
|
522
|
+
# Affine transformation; scale is ratio of RMS deviations from centroid
|
|
523
|
+
fromPoints *= fromPoints
|
|
524
|
+
toPoints *= toPoints
|
|
525
|
+
M[:ndims, :ndims] *= np.sqrt(np.sum(toPoints) / np.sum(fromPoints))
|
|
526
|
+
|
|
527
|
+
# move centroids back
|
|
528
|
+
M = np.dot(np.linalg.inv(M1), np.dot(M, M0))
|
|
529
|
+
M /= M[ndims, ndims]
|
|
530
|
+
|
|
531
|
+
self.rotation = M[:3, :3]
|
|
532
|
+
self.translation = M[:3, 3]
|
|
533
|
+
|
|
534
|
+
return self
|
|
535
|
+
|
|
536
|
+
def mirror(self):
|
|
537
|
+
"""Method to create a new coordinate system object with mirroring the axis
|
|
538
|
+
on the x-z plane."""
|
|
539
|
+
transformation = Transformation(
|
|
540
|
+
[
|
|
541
|
+
[1, -1, 1, 1],
|
|
542
|
+
[-1, 1, -1, -1],
|
|
543
|
+
[1, -1, 1, 1],
|
|
544
|
+
[0, 0, 0, 1],
|
|
545
|
+
]
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
return np.multiply(self.copy(), transformation)
|
|
549
|
+
|
|
550
|
+
def _setTranslation(self, translation):
|
|
551
|
+
"""doc"""
|
|
552
|
+
if not np.array(translation).shape == (3,):
|
|
553
|
+
raise ImproperParameterError(
|
|
554
|
+
'Could not set translation "%s".'
|
|
555
|
+
+ " An iterable with 3 scalars or an object of type Translation is needed!" % translation
|
|
556
|
+
)
|
|
557
|
+
self[:3, 3] = translation[:]
|
|
558
|
+
|
|
559
|
+
def _getTranslation(self):
|
|
560
|
+
"""doc"""
|
|
561
|
+
return np.array(self[:3, 3])
|
|
562
|
+
|
|
563
|
+
def _setRotation(self, rot):
|
|
564
|
+
"""doc"""
|
|
565
|
+
if not np.array(rot).shape == (3, 3):
|
|
566
|
+
raise ImproperParameterError(
|
|
567
|
+
'Could not set rotation "%s".'
|
|
568
|
+
+ " An iterable with 3x3 scalars or an object of type Rotation is needed!" % rot
|
|
569
|
+
)
|
|
570
|
+
self[:3, :3] = rot
|
|
571
|
+
|
|
572
|
+
def _getRotation(self):
|
|
573
|
+
"""doc"""
|
|
574
|
+
scaleFactors = 1.0 / np.outer(self.scaling.factors[:3], np.ones(3))
|
|
575
|
+
return Rotation(np.multiply(self[:3, :3], scaleFactors))
|
|
576
|
+
|
|
577
|
+
def _setScaling(self, scal):
|
|
578
|
+
"""doc"""
|
|
579
|
+
if np.array(scal).shape != (3, 3):
|
|
580
|
+
raise ImproperParameterError(
|
|
581
|
+
'Could not set scaling "%s".'
|
|
582
|
+
+ " An iterable with 3x3 scalars or an object of type Scaling is needed!" % scal
|
|
583
|
+
)
|
|
584
|
+
self[:3, :3] = scal
|
|
585
|
+
|
|
586
|
+
def _getScaling(self):
|
|
587
|
+
"""doc"""
|
|
588
|
+
scaling = Scaling()
|
|
589
|
+
scaling.factors = (np.linalg.norm(self[0, :3]), np.linalg.norm(self[1, :3]), np.linalg.norm(self[2, :3]))
|
|
590
|
+
|
|
591
|
+
return scaling
|
|
592
|
+
|
|
593
|
+
def getReflectedCoordinateSystem(self, reflectionType):
|
|
594
|
+
"""this routine reflects the respective coordinatesystem by the given reflection Type
|
|
595
|
+
reflectionType "xAxis" generates a reflection about the x-axis
|
|
596
|
+
reflect = [[1,0.,0.],
|
|
597
|
+
[0.,-1, 0.],
|
|
598
|
+
[0.,0.,1]]
|
|
599
|
+
reflectionType "yAxis" generates a reflection about the y-axis
|
|
600
|
+
reflect = [[-1,0.,0.],
|
|
601
|
+
[0.,1, 0.],
|
|
602
|
+
[0.,0.,1]]
|
|
603
|
+
reflectionType "zAxis" generates a reflection about the z-axis
|
|
604
|
+
reflect = [[1,0.,0.],
|
|
605
|
+
[0.,1, 0.],
|
|
606
|
+
[0.,0.,-1]]
|
|
607
|
+
"""
|
|
608
|
+
|
|
609
|
+
idxToReflectedDict = {"xAxis": 1, "yAxis": 0, "zAxis": 2}
|
|
610
|
+
if reflectionType in idxToReflectedDict.keys():
|
|
611
|
+
reflect = np.identity(3, np.float64)
|
|
612
|
+
idx = idxToReflectedDict[reflectionType]
|
|
613
|
+
reflect[idx, idx] *= -1
|
|
614
|
+
else:
|
|
615
|
+
raise ImproperParameterError("the reflection Type is not correct: " + reflectionType)
|
|
616
|
+
|
|
617
|
+
if not np.array(reflect).shape == (3, 3):
|
|
618
|
+
log.error(
|
|
619
|
+
'Could not set reflection "%s". An iterable with 3x3 scalars or an object of type Reflection is needed!'
|
|
620
|
+
% reflect
|
|
621
|
+
)
|
|
622
|
+
self[:3, :3] = np.dot(self[:3, :3], reflect)
|
|
623
|
+
self[:3, 3] = np.dot(self[:3, 3], reflect)
|
|
624
|
+
|
|
625
|
+
return self
|
|
626
|
+
|
|
627
|
+
translation = property(fset=_setTranslation, fget=_getTranslation)
|
|
628
|
+
rotation = property(fset=_setRotation, fget=_getRotation)
|
|
629
|
+
scaling = property(fset=_setScaling, fget=_getScaling)
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
if __name__ == "__main__":
|
|
633
|
+
import doctest
|
|
634
|
+
|
|
635
|
+
doctest.testmod()
|