You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

transformations.py 56KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706
  1. # -*- coding: utf-8 -*-
  2. # transformations.py
  3. # Copyright (c) 2006, Christoph Gohlke
  4. # Copyright (c) 2006-2009, The Regents of the University of California
  5. # All rights reserved.
  6. #
  7. # Redistribution and use in source and binary forms, with or without
  8. # modification, are permitted provided that the following conditions are met:
  9. #
  10. # * Redistributions of source code must retain the above copyright
  11. # notice, this list of conditions and the following disclaimer.
  12. # * Redistributions in binary form must reproduce the above copyright
  13. # notice, this list of conditions and the following disclaimer in the
  14. # documentation and/or other materials provided with the distribution.
  15. # * Neither the name of the copyright holders nor the names of any
  16. # contributors may be used to endorse or promote products derived
  17. # from this software without specific prior written permission.
  18. #
  19. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  20. # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  21. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  22. # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  23. # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  24. # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  25. # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  26. # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  27. # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  28. # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  29. # POSSIBILITY OF SUCH DAMAGE.
  30. """Homogeneous Transformation Matrices and Quaternions.
  31. A library for calculating 4x4 matrices for translating, rotating, reflecting,
  32. scaling, shearing, projecting, orthogonalizing, and superimposing arrays of
  33. 3D homogeneous coordinates as well as for converting between rotation matrices,
  34. Euler angles, and quaternions. Also includes an Arcball control object and
  35. functions to decompose transformation matrices.
  36. :Authors:
  37. `Christoph Gohlke <http://www.lfd.uci.edu/~gohlke/>`__,
  38. Laboratory for Fluorescence Dynamics, University of California, Irvine
  39. :Version: 20090418
  40. Requirements
  41. ------------
  42. * `Python 2.6 <http://www.python.org>`__
  43. * `Numpy 1.3 <http://numpy.scipy.org>`__
  44. * `transformations.c 20090418 <http://www.lfd.uci.edu/~gohlke/>`__
  45. (optional implementation of some functions in C)
  46. Notes
  47. -----
  48. Matrices (M) can be inverted using numpy.linalg.inv(M), concatenated using
  49. numpy.dot(M0, M1), or used to transform homogeneous coordinates (v) using
  50. numpy.dot(M, v) for shape (4, \*) "point of arrays", respectively
  51. numpy.dot(v, M.T) for shape (\*, 4) "array of points".
  52. Calculations are carried out with numpy.float64 precision.
  53. This Python implementation is not optimized for speed.
  54. Vector, point, quaternion, and matrix function arguments are expected to be
  55. "array like", i.e. tuple, list, or numpy arrays.
  56. Return types are numpy arrays unless specified otherwise.
  57. Angles are in radians unless specified otherwise.
  58. Quaternions ix+jy+kz+w are represented as [x, y, z, w].
  59. Use the transpose of transformation matrices for OpenGL glMultMatrixd().
  60. A triple of Euler angles can be applied/interpreted in 24 ways, which can
  61. be specified using a 4 character string or encoded 4-tuple:
  62. *Axes 4-string*: e.g. 'sxyz' or 'ryxy'
  63. - first character : rotations are applied to 's'tatic or 'r'otating frame
  64. - remaining characters : successive rotation axis 'x', 'y', or 'z'
  65. *Axes 4-tuple*: e.g. (0, 0, 0, 0) or (1, 1, 1, 1)
  66. - inner axis: code of axis ('x':0, 'y':1, 'z':2) of rightmost matrix.
  67. - parity : even (0) if inner axis 'x' is followed by 'y', 'y' is followed
  68. by 'z', or 'z' is followed by 'x'. Otherwise odd (1).
  69. - repetition : first and last axis are same (1) or different (0).
  70. - frame : rotations are applied to static (0) or rotating (1) frame.
  71. References
  72. ----------
  73. (1) Matrices and transformations. Ronald Goldman.
  74. In "Graphics Gems I", pp 472-475. Morgan Kaufmann, 1990.
  75. (2) More matrices and transformations: shear and pseudo-perspective.
  76. Ronald Goldman. In "Graphics Gems II", pp 320-323. Morgan Kaufmann, 1991.
  77. (3) Decomposing a matrix into simple transformations. Spencer Thomas.
  78. In "Graphics Gems II", pp 320-323. Morgan Kaufmann, 1991.
  79. (4) Recovering the data from the transformation matrix. Ronald Goldman.
  80. In "Graphics Gems II", pp 324-331. Morgan Kaufmann, 1991.
  81. (5) Euler angle conversion. Ken Shoemake.
  82. In "Graphics Gems IV", pp 222-229. Morgan Kaufmann, 1994.
  83. (6) Arcball rotation control. Ken Shoemake.
  84. In "Graphics Gems IV", pp 175-192. Morgan Kaufmann, 1994.
  85. (7) Representing attitude: Euler angles, unit quaternions, and rotation
  86. vectors. James Diebel. 2006.
  87. (8) A discussion of the solution for the best rotation to relate two sets
  88. of vectors. W Kabsch. Acta Cryst. 1978. A34, 827-828.
  89. (9) Closed-form solution of absolute orientation using unit quaternions.
  90. BKP Horn. J Opt Soc Am A. 1987. 4(4), 629-642.
  91. (10) Quaternions. Ken Shoemake.
  92. http://www.sfu.ca/~jwa3/cmpt461/files/quatut.pdf
  93. (11) From quaternion to matrix and back. JMP van Waveren. 2005.
  94. http://www.intel.com/cd/ids/developer/asmo-na/eng/293748.htm
  95. (12) Uniform random rotations. Ken Shoemake.
  96. In "Graphics Gems III", pp 124-132. Morgan Kaufmann, 1992.
  97. Examples
  98. --------
  99. >>> alpha, beta, gamma = 0.123, -1.234, 2.345
  100. >>> origin, xaxis, yaxis, zaxis = (0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1)
  101. >>> I = identity_matrix()
  102. >>> Rx = rotation_matrix(alpha, xaxis)
  103. >>> Ry = rotation_matrix(beta, yaxis)
  104. >>> Rz = rotation_matrix(gamma, zaxis)
  105. >>> R = concatenate_matrices(Rx, Ry, Rz)
  106. >>> euler = euler_from_matrix(R, 'rxyz')
  107. >>> numpy.allclose([alpha, beta, gamma], euler)
  108. True
  109. >>> Re = euler_matrix(alpha, beta, gamma, 'rxyz')
  110. >>> is_same_transform(R, Re)
  111. True
  112. >>> al, be, ga = euler_from_matrix(Re, 'rxyz')
  113. >>> is_same_transform(Re, euler_matrix(al, be, ga, 'rxyz'))
  114. True
  115. >>> qx = quaternion_about_axis(alpha, xaxis)
  116. >>> qy = quaternion_about_axis(beta, yaxis)
  117. >>> qz = quaternion_about_axis(gamma, zaxis)
  118. >>> q = quaternion_multiply(qx, qy)
  119. >>> q = quaternion_multiply(q, qz)
  120. >>> Rq = quaternion_matrix(q)
  121. >>> is_same_transform(R, Rq)
  122. True
  123. >>> S = scale_matrix(1.23, origin)
  124. >>> T = translation_matrix((1, 2, 3))
  125. >>> Z = shear_matrix(beta, xaxis, origin, zaxis)
  126. >>> R = random_rotation_matrix(numpy.random.rand(3))
  127. >>> M = concatenate_matrices(T, R, Z, S)
  128. >>> scale, shear, angles, trans, persp = decompose_matrix(M)
  129. >>> numpy.allclose(scale, 1.23)
  130. True
  131. >>> numpy.allclose(trans, (1, 2, 3))
  132. True
  133. >>> numpy.allclose(shear, (0, math.tan(beta), 0))
  134. True
  135. >>> is_same_transform(R, euler_matrix(axes='sxyz', *angles))
  136. True
  137. >>> M1 = compose_matrix(scale, shear, angles, trans, persp)
  138. >>> is_same_transform(M, M1)
  139. True
  140. """
  141. from __future__ import division
  142. import warnings
  143. import math
  144. import numpy
  145. # Documentation in HTML format can be generated with Epydoc
  146. __docformat__ = "restructuredtext en"
  147. def identity_matrix():
  148. """Return 4x4 identity/unit matrix.
  149. >>> I = identity_matrix()
  150. >>> numpy.allclose(I, numpy.dot(I, I))
  151. True
  152. >>> numpy.sum(I), numpy.trace(I)
  153. (4.0, 4.0)
  154. >>> numpy.allclose(I, numpy.identity(4, dtype=numpy.float64))
  155. True
  156. """
  157. return numpy.identity(4, dtype=numpy.float64)
  158. def translation_matrix(direction):
  159. """Return matrix to translate by direction vector.
  160. >>> v = numpy.random.random(3) - 0.5
  161. >>> numpy.allclose(v, translation_matrix(v)[:3, 3])
  162. True
  163. """
  164. M = numpy.identity(4)
  165. M[:3, 3] = direction[:3]
  166. return M
  167. def translation_from_matrix(matrix):
  168. """Return translation vector from translation matrix.
  169. >>> v0 = numpy.random.random(3) - 0.5
  170. >>> v1 = translation_from_matrix(translation_matrix(v0))
  171. >>> numpy.allclose(v0, v1)
  172. True
  173. """
  174. return numpy.array(matrix, copy=False)[:3, 3].copy()
  175. def reflection_matrix(point, normal):
  176. """Return matrix to mirror at plane defined by point and normal vector.
  177. >>> v0 = numpy.random.random(4) - 0.5
  178. >>> v0[3] = 1.0
  179. >>> v1 = numpy.random.random(3) - 0.5
  180. >>> R = reflection_matrix(v0, v1)
  181. >>> numpy.allclose(2., numpy.trace(R))
  182. True
  183. >>> numpy.allclose(v0, numpy.dot(R, v0))
  184. True
  185. >>> v2 = v0.copy()
  186. >>> v2[:3] += v1
  187. >>> v3 = v0.copy()
  188. >>> v2[:3] -= v1
  189. >>> numpy.allclose(v2, numpy.dot(R, v3))
  190. True
  191. """
  192. normal = unit_vector(normal[:3])
  193. M = numpy.identity(4)
  194. M[:3, :3] -= 2.0 * numpy.outer(normal, normal)
  195. M[:3, 3] = (2.0 * numpy.dot(point[:3], normal)) * normal
  196. return M
  197. def reflection_from_matrix(matrix):
  198. """Return mirror plane point and normal vector from reflection matrix.
  199. >>> v0 = numpy.random.random(3) - 0.5
  200. >>> v1 = numpy.random.random(3) - 0.5
  201. >>> M0 = reflection_matrix(v0, v1)
  202. >>> point, normal = reflection_from_matrix(M0)
  203. >>> M1 = reflection_matrix(point, normal)
  204. >>> is_same_transform(M0, M1)
  205. True
  206. """
  207. M = numpy.array(matrix, dtype=numpy.float64, copy=False)
  208. # normal: unit eigenvector corresponding to eigenvalue -1
  209. l, V = numpy.linalg.eig(M[:3, :3])
  210. i = numpy.where(abs(numpy.real(l) + 1.0) < 1e-8)[0]
  211. if not len(i):
  212. raise ValueError("no unit eigenvector corresponding to eigenvalue -1")
  213. normal = numpy.real(V[:, i[0]]).squeeze()
  214. # point: any unit eigenvector corresponding to eigenvalue 1
  215. l, V = numpy.linalg.eig(M)
  216. i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-8)[0]
  217. if not len(i):
  218. raise ValueError("no unit eigenvector corresponding to eigenvalue 1")
  219. point = numpy.real(V[:, i[-1]]).squeeze()
  220. point /= point[3]
  221. return point, normal
  222. def rotation_matrix(angle, direction, point=None):
  223. """Return matrix to rotate about axis defined by point and direction.
  224. >>> angle = (random.random() - 0.5) * (2*math.pi)
  225. >>> direc = numpy.random.random(3) - 0.5
  226. >>> point = numpy.random.random(3) - 0.5
  227. >>> R0 = rotation_matrix(angle, direc, point)
  228. >>> R1 = rotation_matrix(angle-2*math.pi, direc, point)
  229. >>> is_same_transform(R0, R1)
  230. True
  231. >>> R0 = rotation_matrix(angle, direc, point)
  232. >>> R1 = rotation_matrix(-angle, -direc, point)
  233. >>> is_same_transform(R0, R1)
  234. True
  235. >>> I = numpy.identity(4, numpy.float64)
  236. >>> numpy.allclose(I, rotation_matrix(math.pi*2, direc))
  237. True
  238. >>> numpy.allclose(2., numpy.trace(rotation_matrix(math.pi/2,
  239. ... direc, point)))
  240. True
  241. """
  242. sina = math.sin(angle)
  243. cosa = math.cos(angle)
  244. direction = unit_vector(direction[:3])
  245. # rotation matrix around unit vector
  246. R = numpy.array(((cosa, 0.0, 0.0),
  247. (0.0, cosa, 0.0),
  248. (0.0, 0.0, cosa)), dtype=numpy.float64)
  249. R += numpy.outer(direction, direction) * (1.0 - cosa)
  250. direction *= sina
  251. R += numpy.array(((0.0, -direction[2], direction[1]),
  252. (direction[2], 0.0, -direction[0]),
  253. (-direction[1], direction[0], 0.0)),
  254. dtype=numpy.float64)
  255. M = numpy.identity(4)
  256. M[:3, :3] = R
  257. if point is not None:
  258. # rotation not around origin
  259. point = numpy.array(point[:3], dtype=numpy.float64, copy=False)
  260. M[:3, 3] = point - numpy.dot(R, point)
  261. return M
  262. def rotation_from_matrix(matrix):
  263. """Return rotation angle and axis from rotation matrix.
  264. >>> angle = (random.random() - 0.5) * (2*math.pi)
  265. >>> direc = numpy.random.random(3) - 0.5
  266. >>> point = numpy.random.random(3) - 0.5
  267. >>> R0 = rotation_matrix(angle, direc, point)
  268. >>> angle, direc, point = rotation_from_matrix(R0)
  269. >>> R1 = rotation_matrix(angle, direc, point)
  270. >>> is_same_transform(R0, R1)
  271. True
  272. """
  273. R = numpy.array(matrix, dtype=numpy.float64, copy=False)
  274. R33 = R[:3, :3]
  275. # direction: unit eigenvector of R33 corresponding to eigenvalue of 1
  276. l, W = numpy.linalg.eig(R33.T)
  277. i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-8)[0]
  278. if not len(i):
  279. raise ValueError("no unit eigenvector corresponding to eigenvalue 1")
  280. direction = numpy.real(W[:, i[-1]]).squeeze()
  281. # point: unit eigenvector of R33 corresponding to eigenvalue of 1
  282. l, Q = numpy.linalg.eig(R)
  283. i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-8)[0]
  284. if not len(i):
  285. raise ValueError("no unit eigenvector corresponding to eigenvalue 1")
  286. point = numpy.real(Q[:, i[-1]]).squeeze()
  287. point /= point[3]
  288. # rotation angle depending on direction
  289. cosa = (numpy.trace(R33) - 1.0) / 2.0
  290. if abs(direction[2]) > 1e-8:
  291. sina = (R[1, 0] + (cosa-1.0)*direction[0]*direction[1]) / direction[2]
  292. elif abs(direction[1]) > 1e-8:
  293. sina = (R[0, 2] + (cosa-1.0)*direction[0]*direction[2]) / direction[1]
  294. else:
  295. sina = (R[2, 1] + (cosa-1.0)*direction[1]*direction[2]) / direction[0]
  296. angle = math.atan2(sina, cosa)
  297. return angle, direction, point
  298. def scale_matrix(factor, origin=None, direction=None):
  299. """Return matrix to scale by factor around origin in direction.
  300. Use factor -1 for point symmetry.
  301. >>> v = (numpy.random.rand(4, 5) - 0.5) * 20.0
  302. >>> v[3] = 1.0
  303. >>> S = scale_matrix(-1.234)
  304. >>> numpy.allclose(numpy.dot(S, v)[:3], -1.234*v[:3])
  305. True
  306. >>> factor = random.random() * 10 - 5
  307. >>> origin = numpy.random.random(3) - 0.5
  308. >>> direct = numpy.random.random(3) - 0.5
  309. >>> S = scale_matrix(factor, origin)
  310. >>> S = scale_matrix(factor, origin, direct)
  311. """
  312. if direction is None:
  313. # uniform scaling
  314. M = numpy.array(((factor, 0.0, 0.0, 0.0),
  315. (0.0, factor, 0.0, 0.0),
  316. (0.0, 0.0, factor, 0.0),
  317. (0.0, 0.0, 0.0, 1.0)), dtype=numpy.float64)
  318. if origin is not None:
  319. M[:3, 3] = origin[:3]
  320. M[:3, 3] *= 1.0 - factor
  321. else:
  322. # nonuniform scaling
  323. direction = unit_vector(direction[:3])
  324. factor = 1.0 - factor
  325. M = numpy.identity(4)
  326. M[:3, :3] -= factor * numpy.outer(direction, direction)
  327. if origin is not None:
  328. M[:3, 3] = (factor * numpy.dot(origin[:3], direction)) * direction
  329. return M
  330. def scale_from_matrix(matrix):
  331. """Return scaling factor, origin and direction from scaling matrix.
  332. >>> factor = random.random() * 10 - 5
  333. >>> origin = numpy.random.random(3) - 0.5
  334. >>> direct = numpy.random.random(3) - 0.5
  335. >>> S0 = scale_matrix(factor, origin)
  336. >>> factor, origin, direction = scale_from_matrix(S0)
  337. >>> S1 = scale_matrix(factor, origin, direction)
  338. >>> is_same_transform(S0, S1)
  339. True
  340. >>> S0 = scale_matrix(factor, origin, direct)
  341. >>> factor, origin, direction = scale_from_matrix(S0)
  342. >>> S1 = scale_matrix(factor, origin, direction)
  343. >>> is_same_transform(S0, S1)
  344. True
  345. """
  346. M = numpy.array(matrix, dtype=numpy.float64, copy=False)
  347. M33 = M[:3, :3]
  348. factor = numpy.trace(M33) - 2.0
  349. try:
  350. # direction: unit eigenvector corresponding to eigenvalue factor
  351. l, V = numpy.linalg.eig(M33)
  352. i = numpy.where(abs(numpy.real(l) - factor) < 1e-8)[0][0]
  353. direction = numpy.real(V[:, i]).squeeze()
  354. direction /= vector_norm(direction)
  355. except IndexError:
  356. # uniform scaling
  357. factor = (factor + 2.0) / 3.0
  358. direction = None
  359. # origin: any eigenvector corresponding to eigenvalue 1
  360. l, V = numpy.linalg.eig(M)
  361. i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-8)[0]
  362. if not len(i):
  363. raise ValueError("no eigenvector corresponding to eigenvalue 1")
  364. origin = numpy.real(V[:, i[-1]]).squeeze()
  365. origin /= origin[3]
  366. return factor, origin, direction
  367. def projection_matrix(point, normal, direction=None,
  368. perspective=None, pseudo=False):
  369. """Return matrix to project onto plane defined by point and normal.
  370. Using either perspective point, projection direction, or none of both.
  371. If pseudo is True, perspective projections will preserve relative depth
  372. such that Perspective = dot(Orthogonal, PseudoPerspective).
  373. >>> P = projection_matrix((0, 0, 0), (1, 0, 0))
  374. >>> numpy.allclose(P[1:, 1:], numpy.identity(4)[1:, 1:])
  375. True
  376. >>> point = numpy.random.random(3) - 0.5
  377. >>> normal = numpy.random.random(3) - 0.5
  378. >>> direct = numpy.random.random(3) - 0.5
  379. >>> persp = numpy.random.random(3) - 0.5
  380. >>> P0 = projection_matrix(point, normal)
  381. >>> P1 = projection_matrix(point, normal, direction=direct)
  382. >>> P2 = projection_matrix(point, normal, perspective=persp)
  383. >>> P3 = projection_matrix(point, normal, perspective=persp, pseudo=True)
  384. >>> is_same_transform(P2, numpy.dot(P0, P3))
  385. True
  386. >>> P = projection_matrix((3, 0, 0), (1, 1, 0), (1, 0, 0))
  387. >>> v0 = (numpy.random.rand(4, 5) - 0.5) * 20.0
  388. >>> v0[3] = 1.0
  389. >>> v1 = numpy.dot(P, v0)
  390. >>> numpy.allclose(v1[1], v0[1])
  391. True
  392. >>> numpy.allclose(v1[0], 3.0-v1[1])
  393. True
  394. """
  395. M = numpy.identity(4)
  396. point = numpy.array(point[:3], dtype=numpy.float64, copy=False)
  397. normal = unit_vector(normal[:3])
  398. if perspective is not None:
  399. # perspective projection
  400. perspective = numpy.array(perspective[:3], dtype=numpy.float64,
  401. copy=False)
  402. M[0, 0] = M[1, 1] = M[2, 2] = numpy.dot(perspective-point, normal)
  403. M[:3, :3] -= numpy.outer(perspective, normal)
  404. if pseudo:
  405. # preserve relative depth
  406. M[:3, :3] -= numpy.outer(normal, normal)
  407. M[:3, 3] = numpy.dot(point, normal) * (perspective+normal)
  408. else:
  409. M[:3, 3] = numpy.dot(point, normal) * perspective
  410. M[3, :3] = -normal
  411. M[3, 3] = numpy.dot(perspective, normal)
  412. elif direction is not None:
  413. # parallel projection
  414. direction = numpy.array(direction[:3], dtype=numpy.float64, copy=False)
  415. scale = numpy.dot(direction, normal)
  416. M[:3, :3] -= numpy.outer(direction, normal) / scale
  417. M[:3, 3] = direction * (numpy.dot(point, normal) / scale)
  418. else:
  419. # orthogonal projection
  420. M[:3, :3] -= numpy.outer(normal, normal)
  421. M[:3, 3] = numpy.dot(point, normal) * normal
  422. return M
  423. def projection_from_matrix(matrix, pseudo=False):
  424. """Return projection plane and perspective point from projection matrix.
  425. Return values are same as arguments for projection_matrix function:
  426. point, normal, direction, perspective, and pseudo.
  427. >>> point = numpy.random.random(3) - 0.5
  428. >>> normal = numpy.random.random(3) - 0.5
  429. >>> direct = numpy.random.random(3) - 0.5
  430. >>> persp = numpy.random.random(3) - 0.5
  431. >>> P0 = projection_matrix(point, normal)
  432. >>> result = projection_from_matrix(P0)
  433. >>> P1 = projection_matrix(*result)
  434. >>> is_same_transform(P0, P1)
  435. True
  436. >>> P0 = projection_matrix(point, normal, direct)
  437. >>> result = projection_from_matrix(P0)
  438. >>> P1 = projection_matrix(*result)
  439. >>> is_same_transform(P0, P1)
  440. True
  441. >>> P0 = projection_matrix(point, normal, perspective=persp, pseudo=False)
  442. >>> result = projection_from_matrix(P0, pseudo=False)
  443. >>> P1 = projection_matrix(*result)
  444. >>> is_same_transform(P0, P1)
  445. True
  446. >>> P0 = projection_matrix(point, normal, perspective=persp, pseudo=True)
  447. >>> result = projection_from_matrix(P0, pseudo=True)
  448. >>> P1 = projection_matrix(*result)
  449. >>> is_same_transform(P0, P1)
  450. True
  451. """
  452. M = numpy.array(matrix, dtype=numpy.float64, copy=False)
  453. M33 = M[:3, :3]
  454. l, V = numpy.linalg.eig(M)
  455. i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-8)[0]
  456. if not pseudo and len(i):
  457. # point: any eigenvector corresponding to eigenvalue 1
  458. point = numpy.real(V[:, i[-1]]).squeeze()
  459. point /= point[3]
  460. # direction: unit eigenvector corresponding to eigenvalue 0
  461. l, V = numpy.linalg.eig(M33)
  462. i = numpy.where(abs(numpy.real(l)) < 1e-8)[0]
  463. if not len(i):
  464. raise ValueError("no eigenvector corresponding to eigenvalue 0")
  465. direction = numpy.real(V[:, i[0]]).squeeze()
  466. direction /= vector_norm(direction)
  467. # normal: unit eigenvector of M33.T corresponding to eigenvalue 0
  468. l, V = numpy.linalg.eig(M33.T)
  469. i = numpy.where(abs(numpy.real(l)) < 1e-8)[0]
  470. if len(i):
  471. # parallel projection
  472. normal = numpy.real(V[:, i[0]]).squeeze()
  473. normal /= vector_norm(normal)
  474. return point, normal, direction, None, False
  475. else:
  476. # orthogonal projection, where normal equals direction vector
  477. return point, direction, None, None, False
  478. else:
  479. # perspective projection
  480. i = numpy.where(abs(numpy.real(l)) > 1e-8)[0]
  481. if not len(i):
  482. raise ValueError(
  483. "no eigenvector not corresponding to eigenvalue 0")
  484. point = numpy.real(V[:, i[-1]]).squeeze()
  485. point /= point[3]
  486. normal = - M[3, :3]
  487. perspective = M[:3, 3] / numpy.dot(point[:3], normal)
  488. if pseudo:
  489. perspective -= normal
  490. return point, normal, None, perspective, pseudo
  491. def clip_matrix(left, right, bottom, top, near, far, perspective=False):
  492. """Return matrix to obtain normalized device coordinates from frustrum.
  493. The frustrum bounds are axis-aligned along x (left, right),
  494. y (bottom, top) and z (near, far).
  495. Normalized device coordinates are in range [-1, 1] if coordinates are
  496. inside the frustrum.
  497. If perspective is True the frustrum is a truncated pyramid with the
  498. perspective point at origin and direction along z axis, otherwise an
  499. orthographic canonical view volume (a box).
  500. Homogeneous coordinates transformed by the perspective clip matrix
  501. need to be dehomogenized (devided by w coordinate).
  502. >>> frustrum = numpy.random.rand(6)
  503. >>> frustrum[1] += frustrum[0]
  504. >>> frustrum[3] += frustrum[2]
  505. >>> frustrum[5] += frustrum[4]
  506. >>> M = clip_matrix(*frustrum, perspective=False)
  507. >>> numpy.dot(M, [frustrum[0], frustrum[2], frustrum[4], 1.0])
  508. array([-1., -1., -1., 1.])
  509. >>> numpy.dot(M, [frustrum[1], frustrum[3], frustrum[5], 1.0])
  510. array([ 1., 1., 1., 1.])
  511. >>> M = clip_matrix(*frustrum, perspective=True)
  512. >>> v = numpy.dot(M, [frustrum[0], frustrum[2], frustrum[4], 1.0])
  513. >>> v / v[3]
  514. array([-1., -1., -1., 1.])
  515. >>> v = numpy.dot(M, [frustrum[1], frustrum[3], frustrum[4], 1.0])
  516. >>> v / v[3]
  517. array([ 1., 1., -1., 1.])
  518. """
  519. if left >= right or bottom >= top or near >= far:
  520. raise ValueError("invalid frustrum")
  521. if perspective:
  522. if near <= _EPS:
  523. raise ValueError("invalid frustrum: near <= 0")
  524. t = 2.0 * near
  525. M = ((-t/(right-left), 0.0, (right+left)/(right-left), 0.0),
  526. (0.0, -t/(top-bottom), (top+bottom)/(top-bottom), 0.0),
  527. (0.0, 0.0, -(far+near)/(far-near), t*far/(far-near)),
  528. (0.0, 0.0, -1.0, 0.0))
  529. else:
  530. M = ((2.0/(right-left), 0.0, 0.0, (right+left)/(left-right)),
  531. (0.0, 2.0/(top-bottom), 0.0, (top+bottom)/(bottom-top)),
  532. (0.0, 0.0, 2.0/(far-near), (far+near)/(near-far)),
  533. (0.0, 0.0, 0.0, 1.0))
  534. return numpy.array(M, dtype=numpy.float64)
  535. def shear_matrix(angle, direction, point, normal):
  536. """Return matrix to shear by angle along direction vector on shear plane.
  537. The shear plane is defined by a point and normal vector. The direction
  538. vector must be orthogonal to the plane's normal vector.
  539. A point P is transformed by the shear matrix into P" such that
  540. the vector P-P" is parallel to the direction vector and its extent is
  541. given by the angle of P-P'-P", where P' is the orthogonal projection
  542. of P onto the shear plane.
  543. >>> angle = (random.random() - 0.5) * 4*math.pi
  544. >>> direct = numpy.random.random(3) - 0.5
  545. >>> point = numpy.random.random(3) - 0.5
  546. >>> normal = numpy.cross(direct, numpy.random.random(3))
  547. >>> S = shear_matrix(angle, direct, point, normal)
  548. >>> numpy.allclose(1.0, numpy.linalg.det(S))
  549. True
  550. """
  551. normal = unit_vector(normal[:3])
  552. direction = unit_vector(direction[:3])
  553. if abs(numpy.dot(normal, direction)) > 1e-6:
  554. raise ValueError("direction and normal vectors are not orthogonal")
  555. angle = math.tan(angle)
  556. M = numpy.identity(4)
  557. M[:3, :3] += angle * numpy.outer(direction, normal)
  558. M[:3, 3] = -angle * numpy.dot(point[:3], normal) * direction
  559. return M
  560. def shear_from_matrix(matrix):
  561. """Return shear angle, direction and plane from shear matrix.
  562. >>> angle = (random.random() - 0.5) * 4*math.pi
  563. >>> direct = numpy.random.random(3) - 0.5
  564. >>> point = numpy.random.random(3) - 0.5
  565. >>> normal = numpy.cross(direct, numpy.random.random(3))
  566. >>> S0 = shear_matrix(angle, direct, point, normal)
  567. >>> angle, direct, point, normal = shear_from_matrix(S0)
  568. >>> S1 = shear_matrix(angle, direct, point, normal)
  569. >>> is_same_transform(S0, S1)
  570. True
  571. """
  572. M = numpy.array(matrix, dtype=numpy.float64, copy=False)
  573. M33 = M[:3, :3]
  574. # normal: cross independent eigenvectors corresponding to the eigenvalue 1
  575. l, V = numpy.linalg.eig(M33)
  576. i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-4)[0]
  577. if len(i) < 2:
  578. raise ValueError("No two linear independent eigenvectors found %s" % l)
  579. V = numpy.real(V[:, i]).squeeze().T
  580. lenorm = -1.0
  581. for i0, i1 in ((0, 1), (0, 2), (1, 2)):
  582. n = numpy.cross(V[i0], V[i1])
  583. l = vector_norm(n)
  584. if l > lenorm:
  585. lenorm = l
  586. normal = n
  587. normal /= lenorm
  588. # direction and angle
  589. direction = numpy.dot(M33 - numpy.identity(3), normal)
  590. angle = vector_norm(direction)
  591. direction /= angle
  592. angle = math.atan(angle)
  593. # point: eigenvector corresponding to eigenvalue 1
  594. l, V = numpy.linalg.eig(M)
  595. i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-8)[0]
  596. if not len(i):
  597. raise ValueError("no eigenvector corresponding to eigenvalue 1")
  598. point = numpy.real(V[:, i[-1]]).squeeze()
  599. point /= point[3]
  600. return angle, direction, point, normal
  601. def decompose_matrix(matrix):
  602. """Return sequence of transformations from transformation matrix.
  603. matrix : array_like
  604. Non-degenerative homogeneous transformation matrix
  605. Return tuple of:
  606. scale : vector of 3 scaling factors
  607. shear : list of shear factors for x-y, x-z, y-z axes
  608. angles : list of Euler angles about static x, y, z axes
  609. translate : translation vector along x, y, z axes
  610. perspective : perspective partition of matrix
  611. Raise ValueError if matrix is of wrong type or degenerative.
  612. >>> T0 = translation_matrix((1, 2, 3))
  613. >>> scale, shear, angles, trans, persp = decompose_matrix(T0)
  614. >>> T1 = translation_matrix(trans)
  615. >>> numpy.allclose(T0, T1)
  616. True
  617. >>> S = scale_matrix(0.123)
  618. >>> scale, shear, angles, trans, persp = decompose_matrix(S)
  619. >>> scale[0]
  620. 0.123
  621. >>> R0 = euler_matrix(1, 2, 3)
  622. >>> scale, shear, angles, trans, persp = decompose_matrix(R0)
  623. >>> R1 = euler_matrix(*angles)
  624. >>> numpy.allclose(R0, R1)
  625. True
  626. """
  627. M = numpy.array(matrix, dtype=numpy.float64, copy=True).T
  628. if abs(M[3, 3]) < _EPS:
  629. raise ValueError("M[3, 3] is zero")
  630. M /= M[3, 3]
  631. P = M.copy()
  632. P[:, 3] = 0, 0, 0, 1
  633. if not numpy.linalg.det(P):
  634. raise ValueError("Matrix is singular")
  635. scale = numpy.zeros((3, ), dtype=numpy.float64)
  636. shear = [0, 0, 0]
  637. angles = [0, 0, 0]
  638. if any(abs(M[:3, 3]) > _EPS):
  639. perspective = numpy.dot(M[:, 3], numpy.linalg.inv(P.T))
  640. M[:, 3] = 0, 0, 0, 1
  641. else:
  642. perspective = numpy.array((0, 0, 0, 1), dtype=numpy.float64)
  643. translate = M[3, :3].copy()
  644. M[3, :3] = 0
  645. row = M[:3, :3].copy()
  646. scale[0] = vector_norm(row[0])
  647. row[0] /= scale[0]
  648. shear[0] = numpy.dot(row[0], row[1])
  649. row[1] -= row[0] * shear[0]
  650. scale[1] = vector_norm(row[1])
  651. row[1] /= scale[1]
  652. shear[0] /= scale[1]
  653. shear[1] = numpy.dot(row[0], row[2])
  654. row[2] -= row[0] * shear[1]
  655. shear[2] = numpy.dot(row[1], row[2])
  656. row[2] -= row[1] * shear[2]
  657. scale[2] = vector_norm(row[2])
  658. row[2] /= scale[2]
  659. shear[1:] /= scale[2]
  660. if numpy.dot(row[0], numpy.cross(row[1], row[2])) < 0:
  661. scale *= -1
  662. row *= -1
  663. angles[1] = math.asin(-row[0, 2])
  664. if math.cos(angles[1]):
  665. angles[0] = math.atan2(row[1, 2], row[2, 2])
  666. angles[2] = math.atan2(row[0, 1], row[0, 0])
  667. else:
  668. #angles[0] = math.atan2(row[1, 0], row[1, 1])
  669. angles[0] = math.atan2(-row[2, 1], row[1, 1])
  670. angles[2] = 0.0
  671. return scale, shear, angles, translate, perspective
  672. def compose_matrix(scale=None, shear=None, angles=None, translate=None,
  673. perspective=None):
  674. """Return transformation matrix from sequence of transformations.
  675. This is the inverse of the decompose_matrix function.
  676. Sequence of transformations:
  677. scale : vector of 3 scaling factors
  678. shear : list of shear factors for x-y, x-z, y-z axes
  679. angles : list of Euler angles about static x, y, z axes
  680. translate : translation vector along x, y, z axes
  681. perspective : perspective partition of matrix
  682. >>> scale = numpy.random.random(3) - 0.5
  683. >>> shear = numpy.random.random(3) - 0.5
  684. >>> angles = (numpy.random.random(3) - 0.5) * (2*math.pi)
  685. >>> trans = numpy.random.random(3) - 0.5
  686. >>> persp = numpy.random.random(4) - 0.5
  687. >>> M0 = compose_matrix(scale, shear, angles, trans, persp)
  688. >>> result = decompose_matrix(M0)
  689. >>> M1 = compose_matrix(*result)
  690. >>> is_same_transform(M0, M1)
  691. True
  692. """
  693. M = numpy.identity(4)
  694. if perspective is not None:
  695. P = numpy.identity(4)
  696. P[3, :] = perspective[:4]
  697. M = numpy.dot(M, P)
  698. if translate is not None:
  699. T = numpy.identity(4)
  700. T[:3, 3] = translate[:3]
  701. M = numpy.dot(M, T)
  702. if angles is not None:
  703. R = euler_matrix(angles[0], angles[1], angles[2], 'sxyz')
  704. M = numpy.dot(M, R)
  705. if shear is not None:
  706. Z = numpy.identity(4)
  707. Z[1, 2] = shear[2]
  708. Z[0, 2] = shear[1]
  709. Z[0, 1] = shear[0]
  710. M = numpy.dot(M, Z)
  711. if scale is not None:
  712. S = numpy.identity(4)
  713. S[0, 0] = scale[0]
  714. S[1, 1] = scale[1]
  715. S[2, 2] = scale[2]
  716. M = numpy.dot(M, S)
  717. M /= M[3, 3]
  718. return M
  719. def orthogonalization_matrix(lengths, angles):
  720. """Return orthogonalization matrix for crystallographic cell coordinates.
  721. Angles are expected in degrees.
  722. The de-orthogonalization matrix is the inverse.
  723. >>> O = orthogonalization_matrix((10., 10., 10.), (90., 90., 90.))
  724. >>> numpy.allclose(O[:3, :3], numpy.identity(3, float) * 10)
  725. True
  726. >>> O = orthogonalization_matrix([9.8, 12.0, 15.5], [87.2, 80.7, 69.7])
  727. >>> numpy.allclose(numpy.sum(O), 43.063229)
  728. True
  729. """
  730. a, b, c = lengths
  731. angles = numpy.radians(angles)
  732. sina, sinb, _ = numpy.sin(angles)
  733. cosa, cosb, cosg = numpy.cos(angles)
  734. co = (cosa * cosb - cosg) / (sina * sinb)
  735. return numpy.array((
  736. (a*sinb*math.sqrt(1.0-co*co), 0.0, 0.0, 0.0),
  737. (-a*sinb*co, b*sina, 0.0, 0.0),
  738. (a*cosb, b*cosa, c, 0.0),
  739. (0.0, 0.0, 0.0, 1.0)),
  740. dtype=numpy.float64)
  741. def superimposition_matrix(v0, v1, scaling=False, usesvd=True):
  742. """Return matrix to transform given vector set into second vector set.
  743. v0 and v1 are shape (3, \*) or (4, \*) arrays of at least 3 vectors.
  744. If usesvd is True, the weighted sum of squared deviations (RMSD) is
  745. minimized according to the algorithm by W. Kabsch [8]. Otherwise the
  746. quaternion based algorithm by B. Horn [9] is used (slower when using
  747. this Python implementation).
  748. The returned matrix performs rotation, translation and uniform scaling
  749. (if specified).
  750. >>> v0 = numpy.random.rand(3, 10)
  751. >>> M = superimposition_matrix(v0, v0)
  752. >>> numpy.allclose(M, numpy.identity(4))
  753. True
  754. >>> R = random_rotation_matrix(numpy.random.random(3))
  755. >>> v0 = ((1,0,0), (0,1,0), (0,0,1), (1,1,1))
  756. >>> v1 = numpy.dot(R, v0)
  757. >>> M = superimposition_matrix(v0, v1)
  758. >>> numpy.allclose(v1, numpy.dot(M, v0))
  759. True
  760. >>> v0 = (numpy.random.rand(4, 100) - 0.5) * 20.0
  761. >>> v0[3] = 1.0
  762. >>> v1 = numpy.dot(R, v0)
  763. >>> M = superimposition_matrix(v0, v1)
  764. >>> numpy.allclose(v1, numpy.dot(M, v0))
  765. True
  766. >>> S = scale_matrix(random.random())
  767. >>> T = translation_matrix(numpy.random.random(3)-0.5)
  768. >>> M = concatenate_matrices(T, R, S)
  769. >>> v1 = numpy.dot(M, v0)
  770. >>> v0[:3] += numpy.random.normal(0.0, 1e-9, 300).reshape(3, -1)
  771. >>> M = superimposition_matrix(v0, v1, scaling=True)
  772. >>> numpy.allclose(v1, numpy.dot(M, v0))
  773. True
  774. >>> M = superimposition_matrix(v0, v1, scaling=True, usesvd=False)
  775. >>> numpy.allclose(v1, numpy.dot(M, v0))
  776. True
  777. >>> v = numpy.empty((4, 100, 3), dtype=numpy.float64)
  778. >>> v[:, :, 0] = v0
  779. >>> M = superimposition_matrix(v0, v1, scaling=True, usesvd=False)
  780. >>> numpy.allclose(v1, numpy.dot(M, v[:, :, 0]))
  781. True
  782. """
  783. v0 = numpy.array(v0, dtype=numpy.float64, copy=False)[:3]
  784. v1 = numpy.array(v1, dtype=numpy.float64, copy=False)[:3]
  785. if v0.shape != v1.shape or v0.shape[1] < 3:
  786. raise ValueError("Vector sets are of wrong shape or type.")
  787. # move centroids to origin
  788. t0 = numpy.mean(v0, axis=1)
  789. t1 = numpy.mean(v1, axis=1)
  790. v0 = v0 - t0.reshape(3, 1)
  791. v1 = v1 - t1.reshape(3, 1)
  792. if usesvd:
  793. # Singular Value Decomposition of covariance matrix
  794. u, s, vh = numpy.linalg.svd(numpy.dot(v1, v0.T))
  795. # rotation matrix from SVD orthonormal bases
  796. R = numpy.dot(u, vh)
  797. if numpy.linalg.det(R) < 0.0:
  798. # R does not constitute right handed system
  799. R -= numpy.outer(u[:, 2], vh[2, :]*2.0)
  800. s[-1] *= -1.0
  801. # homogeneous transformation matrix
  802. M = numpy.identity(4)
  803. M[:3, :3] = R
  804. else:
  805. # compute symmetric matrix N
  806. xx, yy, zz = numpy.sum(v0 * v1, axis=1)
  807. xy, yz, zx = numpy.sum(v0 * numpy.roll(v1, -1, axis=0), axis=1)
  808. xz, yx, zy = numpy.sum(v0 * numpy.roll(v1, -2, axis=0), axis=1)
  809. N = ((xx+yy+zz, yz-zy, zx-xz, xy-yx),
  810. (yz-zy, xx-yy-zz, xy+yx, zx+xz),
  811. (zx-xz, xy+yx, -xx+yy-zz, yz+zy),
  812. (xy-yx, zx+xz, yz+zy, -xx-yy+zz))
  813. # quaternion: eigenvector corresponding to most positive eigenvalue
  814. l, V = numpy.linalg.eig(N)
  815. q = V[:, numpy.argmax(l)]
  816. q /= vector_norm(q) # unit quaternion
  817. q = numpy.roll(q, -1) # move w component to end
  818. # homogeneous transformation matrix
  819. M = quaternion_matrix(q)
  820. # scale: ratio of rms deviations from centroid
  821. if scaling:
  822. v0 *= v0
  823. v1 *= v1
  824. M[:3, :3] *= math.sqrt(numpy.sum(v1) / numpy.sum(v0))
  825. # translation
  826. M[:3, 3] = t1
  827. T = numpy.identity(4)
  828. T[:3, 3] = -t0
  829. M = numpy.dot(M, T)
  830. return M
  831. def euler_matrix(ai, aj, ak, axes='sxyz'):
  832. """Return homogeneous rotation matrix from Euler angles and axis sequence.
  833. ai, aj, ak : Euler's roll, pitch and yaw angles
  834. axes : One of 24 axis sequences as string or encoded tuple
  835. >>> R = euler_matrix(1, 2, 3, 'syxz')
  836. >>> numpy.allclose(numpy.sum(R[0]), -1.34786452)
  837. True
  838. >>> R = euler_matrix(1, 2, 3, (0, 1, 0, 1))
  839. >>> numpy.allclose(numpy.sum(R[0]), -0.383436184)
  840. True
  841. >>> ai, aj, ak = (4.0*math.pi) * (numpy.random.random(3) - 0.5)
  842. >>> for axes in _AXES2TUPLE.keys():
  843. ... R = euler_matrix(ai, aj, ak, axes)
  844. >>> for axes in _TUPLE2AXES.keys():
  845. ... R = euler_matrix(ai, aj, ak, axes)
  846. """
  847. try:
  848. firstaxis, parity, repetition, frame = _AXES2TUPLE[axes]
  849. except (AttributeError, KeyError):
  850. _ = _TUPLE2AXES[axes]
  851. firstaxis, parity, repetition, frame = axes
  852. i = firstaxis
  853. j = _NEXT_AXIS[i+parity]
  854. k = _NEXT_AXIS[i-parity+1]
  855. if frame:
  856. ai, ak = ak, ai
  857. if parity:
  858. ai, aj, ak = -ai, -aj, -ak
  859. si, sj, sk = math.sin(ai), math.sin(aj), math.sin(ak)
  860. ci, cj, ck = math.cos(ai), math.cos(aj), math.cos(ak)
  861. cc, cs = ci*ck, ci*sk
  862. sc, ss = si*ck, si*sk
  863. M = numpy.identity(4)
  864. if repetition:
  865. M[i, i] = cj
  866. M[i, j] = sj*si
  867. M[i, k] = sj*ci
  868. M[j, i] = sj*sk
  869. M[j, j] = -cj*ss+cc
  870. M[j, k] = -cj*cs-sc
  871. M[k, i] = -sj*ck
  872. M[k, j] = cj*sc+cs
  873. M[k, k] = cj*cc-ss
  874. else:
  875. M[i, i] = cj*ck
  876. M[i, j] = sj*sc-cs
  877. M[i, k] = sj*cc+ss
  878. M[j, i] = cj*sk
  879. M[j, j] = sj*ss+cc
  880. M[j, k] = sj*cs-sc
  881. M[k, i] = -sj
  882. M[k, j] = cj*si
  883. M[k, k] = cj*ci
  884. return M
  885. def euler_from_matrix(matrix, axes='sxyz'):
  886. """Return Euler angles from rotation matrix for specified axis sequence.
  887. axes : One of 24 axis sequences as string or encoded tuple
  888. Note that many Euler angle triplets can describe one matrix.
  889. >>> R0 = euler_matrix(1, 2, 3, 'syxz')
  890. >>> al, be, ga = euler_from_matrix(R0, 'syxz')
  891. >>> R1 = euler_matrix(al, be, ga, 'syxz')
  892. >>> numpy.allclose(R0, R1)
  893. True
  894. >>> angles = (4.0*math.pi) * (numpy.random.random(3) - 0.5)
  895. >>> for axes in _AXES2TUPLE.keys():
  896. ... R0 = euler_matrix(axes=axes, *angles)
  897. ... R1 = euler_matrix(axes=axes, *euler_from_matrix(R0, axes))
  898. ... if not numpy.allclose(R0, R1): print axes, "failed"
  899. """
  900. try:
  901. firstaxis, parity, repetition, frame = _AXES2TUPLE[axes.lower()]
  902. except (AttributeError, KeyError):
  903. _ = _TUPLE2AXES[axes]
  904. firstaxis, parity, repetition, frame = axes
  905. i = firstaxis
  906. j = _NEXT_AXIS[i+parity]
  907. k = _NEXT_AXIS[i-parity+1]
  908. M = numpy.array(matrix, dtype=numpy.float64, copy=False)[:3, :3]
  909. if repetition:
  910. sy = math.sqrt(M[i, j]*M[i, j] + M[i, k]*M[i, k])
  911. if sy > _EPS:
  912. ax = math.atan2(M[i, j], M[i, k])
  913. ay = math.atan2(sy, M[i, i])
  914. az = math.atan2(M[j, i], -M[k, i])
  915. else:
  916. ax = math.atan2(-M[j, k], M[j, j])
  917. ay = math.atan2(sy, M[i, i])
  918. az = 0.0
  919. else:
  920. cy = math.sqrt(M[i, i]*M[i, i] + M[j, i]*M[j, i])
  921. if cy > _EPS:
  922. ax = math.atan2(M[k, j], M[k, k])
  923. ay = math.atan2(-M[k, i], cy)
  924. az = math.atan2(M[j, i], M[i, i])
  925. else:
  926. ax = math.atan2(-M[j, k], M[j, j])
  927. ay = math.atan2(-M[k, i], cy)
  928. az = 0.0
  929. if parity:
  930. ax, ay, az = -ax, -ay, -az
  931. if frame:
  932. ax, az = az, ax
  933. return ax, ay, az
  934. def euler_from_quaternion(quaternion, axes='sxyz'):
  935. """Return Euler angles from quaternion for specified axis sequence.
  936. >>> angles = euler_from_quaternion([0.06146124, 0, 0, 0.99810947])
  937. >>> numpy.allclose(angles, [0.123, 0, 0])
  938. True
  939. """
  940. return euler_from_matrix(quaternion_matrix(quaternion), axes)
  941. def quaternion_from_euler(ai, aj, ak, axes='sxyz'):
  942. """Return quaternion from Euler angles and axis sequence.
  943. ai, aj, ak : Euler's roll, pitch and yaw angles
  944. axes : One of 24 axis sequences as string or encoded tuple
  945. >>> q = quaternion_from_euler(1, 2, 3, 'ryxz')
  946. >>> numpy.allclose(q, [0.310622, -0.718287, 0.444435, 0.435953])
  947. True
  948. """
  949. try:
  950. firstaxis, parity, repetition, frame = _AXES2TUPLE[axes.lower()]
  951. except (AttributeError, KeyError):
  952. _ = _TUPLE2AXES[axes]
  953. firstaxis, parity, repetition, frame = axes
  954. i = firstaxis
  955. j = _NEXT_AXIS[i+parity]
  956. k = _NEXT_AXIS[i-parity+1]
  957. if frame:
  958. ai, ak = ak, ai
  959. if parity:
  960. aj = -aj
  961. ai /= 2.0
  962. aj /= 2.0
  963. ak /= 2.0
  964. ci = math.cos(ai)
  965. si = math.sin(ai)
  966. cj = math.cos(aj)
  967. sj = math.sin(aj)
  968. ck = math.cos(ak)
  969. sk = math.sin(ak)
  970. cc = ci*ck
  971. cs = ci*sk
  972. sc = si*ck
  973. ss = si*sk
  974. quaternion = numpy.empty((4, ), dtype=numpy.float64)
  975. if repetition:
  976. quaternion[i] = cj*(cs + sc)
  977. quaternion[j] = sj*(cc + ss)
  978. quaternion[k] = sj*(cs - sc)
  979. quaternion[3] = cj*(cc - ss)
  980. else:
  981. quaternion[i] = cj*sc - sj*cs
  982. quaternion[j] = cj*ss + sj*cc
  983. quaternion[k] = cj*cs - sj*sc
  984. quaternion[3] = cj*cc + sj*ss
  985. if parity:
  986. quaternion[j] *= -1
  987. return quaternion
  988. def quaternion_about_axis(angle, axis):
  989. """Return quaternion for rotation about axis.
  990. >>> q = quaternion_about_axis(0.123, (1, 0, 0))
  991. >>> numpy.allclose(q, [0.06146124, 0, 0, 0.99810947])
  992. True
  993. """
  994. quaternion = numpy.zeros((4, ), dtype=numpy.float64)
  995. quaternion[:3] = axis[:3]
  996. qlen = vector_norm(quaternion)
  997. if qlen > _EPS:
  998. quaternion *= math.sin(angle/2.0) / qlen
  999. quaternion[3] = math.cos(angle/2.0)
  1000. return quaternion
  1001. def quaternion_matrix(quaternion):
  1002. """Return homogeneous rotation matrix from quaternion.
  1003. >>> R = quaternion_matrix([0.06146124, 0, 0, 0.99810947])
  1004. >>> numpy.allclose(R, rotation_matrix(0.123, (1, 0, 0)))
  1005. True
  1006. """
  1007. q = numpy.array(quaternion[:4], dtype=numpy.float64, copy=True)
  1008. nq = numpy.dot(q, q)
  1009. if nq < _EPS:
  1010. return numpy.identity(4)
  1011. q *= math.sqrt(2.0 / nq)
  1012. q = numpy.outer(q, q)
  1013. return numpy.array((
  1014. (1.0-q[1, 1]-q[2, 2], q[0, 1]-q[2, 3], q[0, 2]+q[1, 3], 0.0),
  1015. (q[0, 1]+q[2, 3], 1.0-q[0, 0]-q[2, 2], q[1, 2]-q[0, 3], 0.0),
  1016. (q[0, 2]-q[1, 3], q[1, 2]+q[0, 3], 1.0-q[0, 0]-q[1, 1], 0.0),
  1017. (0.0, 0.0, 0.0, 1.0)
  1018. ), dtype=numpy.float64)
  1019. def quaternion_from_matrix(matrix):
  1020. """Return quaternion from rotation matrix.
  1021. >>> R = rotation_matrix(0.123, (1, 2, 3))
  1022. >>> q = quaternion_from_matrix(R)
  1023. >>> numpy.allclose(q, [0.0164262, 0.0328524, 0.0492786, 0.9981095])
  1024. True
  1025. """
  1026. q = numpy.empty((4, ), dtype=numpy.float64)
  1027. M = numpy.array(matrix, dtype=numpy.float64, copy=False)[:4, :4]
  1028. t = numpy.trace(M)
  1029. if t > M[3, 3]:
  1030. q[3] = t
  1031. q[2] = M[1, 0] - M[0, 1]
  1032. q[1] = M[0, 2] - M[2, 0]
  1033. q[0] = M[2, 1] - M[1, 2]
  1034. else:
  1035. i, j, k = 0, 1, 2
  1036. if M[1, 1] > M[0, 0]:
  1037. i, j, k = 1, 2, 0
  1038. if M[2, 2] > M[i, i]:
  1039. i, j, k = 2, 0, 1
  1040. t = M[i, i] - (M[j, j] + M[k, k]) + M[3, 3]
  1041. q[i] = t
  1042. q[j] = M[i, j] + M[j, i]
  1043. q[k] = M[k, i] + M[i, k]
  1044. q[3] = M[k, j] - M[j, k]
  1045. q *= 0.5 / math.sqrt(t * M[3, 3])
  1046. return q
  1047. def quaternion_multiply(quaternion1, quaternion0):
  1048. """Return multiplication of two quaternions.
  1049. >>> q = quaternion_multiply([1, -2, 3, 4], [-5, 6, 7, 8])
  1050. >>> numpy.allclose(q, [-44, -14, 48, 28])
  1051. True
  1052. """
  1053. x0, y0, z0, w0 = quaternion0
  1054. x1, y1, z1, w1 = quaternion1
  1055. return numpy.array((
  1056. x1*w0 + y1*z0 - z1*y0 + w1*x0,
  1057. -x1*z0 + y1*w0 + z1*x0 + w1*y0,
  1058. x1*y0 - y1*x0 + z1*w0 + w1*z0,
  1059. -x1*x0 - y1*y0 - z1*z0 + w1*w0), dtype=numpy.float64)
  1060. def quaternion_conjugate(quaternion):
  1061. """Return conjugate of quaternion.
  1062. >>> q0 = random_quaternion()
  1063. >>> q1 = quaternion_conjugate(q0)
  1064. >>> q1[3] == q0[3] and all(q1[:3] == -q0[:3])
  1065. True
  1066. """
  1067. return numpy.array((-quaternion[0], -quaternion[1],
  1068. -quaternion[2], quaternion[3]), dtype=numpy.float64)
  1069. def quaternion_inverse(quaternion):
  1070. """Return inverse of quaternion.
  1071. >>> q0 = random_quaternion()
  1072. >>> q1 = quaternion_inverse(q0)
  1073. >>> numpy.allclose(quaternion_multiply(q0, q1), [0, 0, 0, 1])
  1074. True
  1075. """
  1076. return quaternion_conjugate(quaternion) / numpy.dot(quaternion, quaternion)
  1077. def quaternion_slerp(quat0, quat1, fraction, spin=0, shortestpath=True):
  1078. """Return spherical linear interpolation between two quaternions.
  1079. >>> q0 = random_quaternion()
  1080. >>> q1 = random_quaternion()
  1081. >>> q = quaternion_slerp(q0, q1, 0.0)
  1082. >>> numpy.allclose(q, q0)
  1083. True
  1084. >>> q = quaternion_slerp(q0, q1, 1.0, 1)
  1085. >>> numpy.allclose(q, q1)
  1086. True
  1087. >>> q = quaternion_slerp(q0, q1, 0.5)
  1088. >>> angle = math.acos(numpy.dot(q0, q))
  1089. >>> numpy.allclose(2.0, math.acos(numpy.dot(q0, q1)) / angle) or \
  1090. numpy.allclose(2.0, math.acos(-numpy.dot(q0, q1)) / angle)
  1091. True
  1092. """
  1093. q0 = unit_vector(quat0[:4])
  1094. q1 = unit_vector(quat1[:4])
  1095. if fraction == 0.0:
  1096. return q0
  1097. elif fraction == 1.0:
  1098. return q1
  1099. d = numpy.dot(q0, q1)
  1100. if abs(abs(d) - 1.0) < _EPS:
  1101. return q0
  1102. if shortestpath and d < 0.0:
  1103. # invert rotation
  1104. d = -d
  1105. q1 *= -1.0
  1106. angle = math.acos(d) + spin * math.pi
  1107. if abs(angle) < _EPS:
  1108. return q0
  1109. isin = 1.0 / math.sin(angle)
  1110. q0 *= math.sin((1.0 - fraction) * angle) * isin
  1111. q1 *= math.sin(fraction * angle) * isin
  1112. q0 += q1
  1113. return q0
  1114. def random_quaternion(rand=None):
  1115. """Return uniform random unit quaternion.
  1116. rand: array like or None
  1117. Three independent random variables that are uniformly distributed
  1118. between 0 and 1.
  1119. >>> q = random_quaternion()
  1120. >>> numpy.allclose(1.0, vector_norm(q))
  1121. True
  1122. >>> q = random_quaternion(numpy.random.random(3))
  1123. >>> q.shape
  1124. (4,)
  1125. """
  1126. if rand is None:
  1127. rand = numpy.random.rand(3)
  1128. else:
  1129. assert len(rand) == 3
  1130. r1 = numpy.sqrt(1.0 - rand[0])
  1131. r2 = numpy.sqrt(rand[0])
  1132. pi2 = math.pi * 2.0
  1133. t1 = pi2 * rand[1]
  1134. t2 = pi2 * rand[2]
  1135. return numpy.array((numpy.sin(t1)*r1,
  1136. numpy.cos(t1)*r1,
  1137. numpy.sin(t2)*r2,
  1138. numpy.cos(t2)*r2), dtype=numpy.float64)
  1139. def random_rotation_matrix(rand=None):
  1140. """Return uniform random rotation matrix.
  1141. rnd: array like
  1142. Three independent random variables that are uniformly distributed
  1143. between 0 and 1 for each returned quaternion.
  1144. >>> R = random_rotation_matrix()
  1145. >>> numpy.allclose(numpy.dot(R.T, R), numpy.identity(4))
  1146. True
  1147. """
  1148. return quaternion_matrix(random_quaternion(rand))
  1149. class Arcball(object):
  1150. """Virtual Trackball Control.
  1151. >>> ball = Arcball()
  1152. >>> ball = Arcball(initial=numpy.identity(4))
  1153. >>> ball.place([320, 320], 320)
  1154. >>> ball.down([500, 250])
  1155. >>> ball.drag([475, 275])
  1156. >>> R = ball.matrix()
  1157. >>> numpy.allclose(numpy.sum(R), 3.90583455)
  1158. True
  1159. >>> ball = Arcball(initial=[0, 0, 0, 1])
  1160. >>> ball.place([320, 320], 320)
  1161. >>> ball.setaxes([1,1,0], [-1, 1, 0])
  1162. >>> ball.setconstrain(True)
  1163. >>> ball.down([400, 200])
  1164. >>> ball.drag([200, 400])
  1165. >>> R = ball.matrix()
  1166. >>> numpy.allclose(numpy.sum(R), 0.2055924)
  1167. True
  1168. >>> ball.next()
  1169. """
  1170. def __init__(self, initial=None):
  1171. """Initialize virtual trackball control.
  1172. initial : quaternion or rotation matrix
  1173. """
  1174. self._axis = None
  1175. self._axes = None
  1176. self._radius = 1.0
  1177. self._center = [0.0, 0.0]
  1178. self._vdown = numpy.array([0, 0, 1], dtype=numpy.float64)
  1179. self._constrain = False
  1180. if initial is None:
  1181. self._qdown = numpy.array([0, 0, 0, 1], dtype=numpy.float64)
  1182. else:
  1183. initial = numpy.array(initial, dtype=numpy.float64)
  1184. if initial.shape == (4, 4):
  1185. self._qdown = quaternion_from_matrix(initial)
  1186. elif initial.shape == (4, ):
  1187. initial /= vector_norm(initial)
  1188. self._qdown = initial
  1189. else:
  1190. raise ValueError("initial not a quaternion or matrix.")
  1191. self._qnow = self._qpre = self._qdown
  1192. def place(self, center, radius):
  1193. """Place Arcball, e.g. when window size changes.
  1194. center : sequence[2]
  1195. Window coordinates of trackball center.
  1196. radius : float
  1197. Radius of trackball in window coordinates.
  1198. """
  1199. self._radius = float(radius)
  1200. self._center[0] = center[0]
  1201. self._center[1] = center[1]
  1202. def setaxes(self, *axes):
  1203. """Set axes to constrain rotations."""
  1204. if axes is None:
  1205. self._axes = None
  1206. else:
  1207. self._axes = [unit_vector(axis) for axis in axes]
  1208. def setconstrain(self, constrain):
  1209. """Set state of constrain to axis mode."""
  1210. self._constrain = constrain == True
  1211. def getconstrain(self):
  1212. """Return state of constrain to axis mode."""
  1213. return self._constrain
  1214. def down(self, point):
  1215. """Set initial cursor window coordinates and pick constrain-axis."""
  1216. self._vdown = arcball_map_to_sphere(point, self._center, self._radius)
  1217. self._qdown = self._qpre = self._qnow
  1218. if self._constrain and self._axes is not None:
  1219. self._axis = arcball_nearest_axis(self._vdown, self._axes)
  1220. self._vdown = arcball_constrain_to_axis(self._vdown, self._axis)
  1221. else:
  1222. self._axis = None
  1223. def drag(self, point):
  1224. """Update current cursor window coordinates."""
  1225. vnow = arcball_map_to_sphere(point, self._center, self._radius)
  1226. if self._axis is not None:
  1227. vnow = arcball_constrain_to_axis(vnow, self._axis)
  1228. self._qpre = self._qnow
  1229. t = numpy.cross(self._vdown, vnow)
  1230. if numpy.dot(t, t) < _EPS:
  1231. self._qnow = self._qdown
  1232. else:
  1233. q = [t[0], t[1], t[2], numpy.dot(self._vdown, vnow)]
  1234. self._qnow = quaternion_multiply(q, self._qdown)
  1235. def next(self, acceleration=0.0):
  1236. """Continue rotation in direction of last drag."""
  1237. q = quaternion_slerp(self._qpre, self._qnow, 2.0+acceleration, False)
  1238. self._qpre, self._qnow = self._qnow, q
  1239. def matrix(self):
  1240. """Return homogeneous rotation matrix."""
  1241. return quaternion_matrix(self._qnow)
  1242. def arcball_map_to_sphere(point, center, radius):
  1243. """Return unit sphere coordinates from window coordinates."""
  1244. v = numpy.array(((point[0] - center[0]) / radius,
  1245. (center[1] - point[1]) / radius,
  1246. 0.0), dtype=numpy.float64)
  1247. n = v[0]*v[0] + v[1]*v[1]
  1248. if n > 1.0:
  1249. v /= math.sqrt(n) # position outside of sphere
  1250. else:
  1251. v[2] = math.sqrt(1.0 - n)
  1252. return v
  1253. def arcball_constrain_to_axis(point, axis):
  1254. """Return sphere point perpendicular to axis."""
  1255. v = numpy.array(point, dtype=numpy.float64, copy=True)
  1256. a = numpy.array(axis, dtype=numpy.float64, copy=True)
  1257. v -= a * numpy.dot(a, v) # on plane
  1258. n = vector_norm(v)
  1259. if n > _EPS:
  1260. if v[2] < 0.0:
  1261. v *= -1.0
  1262. v /= n
  1263. return v
  1264. if a[2] == 1.0:
  1265. return numpy.array([1, 0, 0], dtype=numpy.float64)
  1266. return unit_vector([-a[1], a[0], 0])
  1267. def arcball_nearest_axis(point, axes):
  1268. """Return axis, which arc is nearest to point."""
  1269. point = numpy.array(point, dtype=numpy.float64, copy=False)
  1270. nearest = None
  1271. mx = -1.0
  1272. for axis in axes:
  1273. t = numpy.dot(arcball_constrain_to_axis(point, axis), point)
  1274. if t > mx:
  1275. nearest = axis
  1276. mx = t
  1277. return nearest
  1278. # epsilon for testing whether a number is close to zero
  1279. _EPS = numpy.finfo(float).eps * 4.0
  1280. # axis sequences for Euler angles
  1281. _NEXT_AXIS = [1, 2, 0, 1]
  1282. # map axes strings to/from tuples of inner axis, parity, repetition, frame
  1283. _AXES2TUPLE = {
  1284. 'sxyz': (0, 0, 0, 0), 'sxyx': (0, 0, 1, 0), 'sxzy': (0, 1, 0, 0),
  1285. 'sxzx': (0, 1, 1, 0), 'syzx': (1, 0, 0, 0), 'syzy': (1, 0, 1, 0),
  1286. 'syxz': (1, 1, 0, 0), 'syxy': (1, 1, 1, 0), 'szxy': (2, 0, 0, 0),
  1287. 'szxz': (2, 0, 1, 0), 'szyx': (2, 1, 0, 0), 'szyz': (2, 1, 1, 0),
  1288. 'rzyx': (0, 0, 0, 1), 'rxyx': (0, 0, 1, 1), 'ryzx': (0, 1, 0, 1),
  1289. 'rxzx': (0, 1, 1, 1), 'rxzy': (1, 0, 0, 1), 'ryzy': (1, 0, 1, 1),
  1290. 'rzxy': (1, 1, 0, 1), 'ryxy': (1, 1, 1, 1), 'ryxz': (2, 0, 0, 1),
  1291. 'rzxz': (2, 0, 1, 1), 'rxyz': (2, 1, 0, 1), 'rzyz': (2, 1, 1, 1)}
  1292. _TUPLE2AXES = dict((v, k) for k, v in _AXES2TUPLE.items())
  1293. # helper functions
  1294. def vector_norm(data, axis=None, out=None):
  1295. """Return length, i.e. eucledian norm, of ndarray along axis.
  1296. >>> v = numpy.random.random(3)
  1297. >>> n = vector_norm(v)
  1298. >>> numpy.allclose(n, numpy.linalg.norm(v))
  1299. True
  1300. >>> v = numpy.random.rand(6, 5, 3)
  1301. >>> n = vector_norm(v, axis=-1)
  1302. >>> numpy.allclose(n, numpy.sqrt(numpy.sum(v*v, axis=2)))
  1303. True
  1304. >>> n = vector_norm(v, axis=1)
  1305. >>> numpy.allclose(n, numpy.sqrt(numpy.sum(v*v, axis=1)))
  1306. True
  1307. >>> v = numpy.random.rand(5, 4, 3)
  1308. >>> n = numpy.empty((5, 3), dtype=numpy.float64)
  1309. >>> vector_norm(v, axis=1, out=n)
  1310. >>> numpy.allclose(n, numpy.sqrt(numpy.sum(v*v, axis=1)))
  1311. True
  1312. >>> vector_norm([])
  1313. 0.0
  1314. >>> vector_norm([1.0])
  1315. 1.0
  1316. """
  1317. data = numpy.array(data, dtype=numpy.float64, copy=True)
  1318. if out is None:
  1319. if data.ndim == 1:
  1320. return math.sqrt(numpy.dot(data, data))
  1321. data *= data
  1322. out = numpy.atleast_1d(numpy.sum(data, axis=axis))
  1323. numpy.sqrt(out, out)
  1324. return out
  1325. else:
  1326. data *= data
  1327. numpy.sum(data, axis=axis, out=out)
  1328. numpy.sqrt(out, out)
  1329. def unit_vector(data, axis=None, out=None):
  1330. """Return ndarray normalized by length, i.e. eucledian norm, along axis.
  1331. >>> v0 = numpy.random.random(3)
  1332. >>> v1 = unit_vector(v0)
  1333. >>> numpy.allclose(v1, v0 / numpy.linalg.norm(v0))
  1334. True
  1335. >>> v0 = numpy.random.rand(5, 4, 3)
  1336. >>> v1 = unit_vector(v0, axis=-1)
  1337. >>> v2 = v0 / numpy.expand_dims(numpy.sqrt(numpy.sum(v0*v0, axis=2)), 2)
  1338. >>> numpy.allclose(v1, v2)
  1339. True
  1340. >>> v1 = unit_vector(v0, axis=1)
  1341. >>> v2 = v0 / numpy.expand_dims(numpy.sqrt(numpy.sum(v0*v0, axis=1)), 1)
  1342. >>> numpy.allclose(v1, v2)
  1343. True
  1344. >>> v1 = numpy.empty((5, 4, 3), dtype=numpy.float64)
  1345. >>> unit_vector(v0, axis=1, out=v1)
  1346. >>> numpy.allclose(v1, v2)
  1347. True
  1348. >>> list(unit_vector([]))
  1349. []
  1350. >>> list(unit_vector([1.0]))
  1351. [1.0]
  1352. """
  1353. if out is None:
  1354. data = numpy.array(data, dtype=numpy.float64, copy=True)
  1355. if data.ndim == 1:
  1356. data /= math.sqrt(numpy.dot(data, data))
  1357. return data
  1358. else:
  1359. if out is not data:
  1360. out[:] = numpy.array(data, copy=False)
  1361. data = out
  1362. length = numpy.atleast_1d(numpy.sum(data*data, axis))
  1363. numpy.sqrt(length, length)
  1364. if axis is not None:
  1365. length = numpy.expand_dims(length, axis)
  1366. data /= length
  1367. if out is None:
  1368. return data
  1369. def random_vector(size):
  1370. """Return array of random doubles in the half-open interval [0.0, 1.0).
  1371. >>> v = random_vector(10000)
  1372. >>> numpy.all(v >= 0.0) and numpy.all(v < 1.0)
  1373. True
  1374. >>> v0 = random_vector(10)
  1375. >>> v1 = random_vector(10)
  1376. >>> numpy.any(v0 == v1)
  1377. False
  1378. """
  1379. return numpy.random.random(size)
  1380. def inverse_matrix(matrix):
  1381. """Return inverse of square transformation matrix.
  1382. >>> M0 = random_rotation_matrix()
  1383. >>> M1 = inverse_matrix(M0.T)
  1384. >>> numpy.allclose(M1, numpy.linalg.inv(M0.T))
  1385. True
  1386. >>> for size in range(1, 7):
  1387. ... M0 = numpy.random.rand(size, size)
  1388. ... M1 = inverse_matrix(M0)
  1389. ... if not numpy.allclose(M1, numpy.linalg.inv(M0)): print size
  1390. """
  1391. return numpy.linalg.inv(matrix)
  1392. def concatenate_matrices(*matrices):
  1393. """Return concatenation of series of transformation matrices.
  1394. >>> M = numpy.random.rand(16).reshape((4, 4)) - 0.5
  1395. >>> numpy.allclose(M, concatenate_matrices(M))
  1396. True
  1397. >>> numpy.allclose(numpy.dot(M, M.T), concatenate_matrices(M, M.T))
  1398. True
  1399. """
  1400. M = numpy.identity(4)
  1401. for i in matrices:
  1402. M = numpy.dot(M, i)
  1403. return M
  1404. def is_same_transform(matrix0, matrix1):
  1405. """Return True if two matrices perform same transformation.
  1406. >>> is_same_transform(numpy.identity(4), numpy.identity(4))
  1407. True
  1408. >>> is_same_transform(numpy.identity(4), random_rotation_matrix())
  1409. False
  1410. """
  1411. matrix0 = numpy.array(matrix0, dtype=numpy.float64, copy=True)
  1412. matrix0 /= matrix0[3, 3]
  1413. matrix1 = numpy.array(matrix1, dtype=numpy.float64, copy=True)
  1414. matrix1 /= matrix1[3, 3]
  1415. return numpy.allclose(matrix0, matrix1)
  1416. def _import_module(module_name, warn=True, prefix='_py_', ignore='_'):
  1417. """Try import all public attributes from module into global namespace.
  1418. Existing attributes with name clashes are renamed with prefix.
  1419. Attributes starting with underscore are ignored by default.
  1420. Return True on successful import.
  1421. """
  1422. try:
  1423. module = __import__(module_name)
  1424. except ImportError:
  1425. if warn:
  1426. warnings.warn("Failed to import module " + module_name)
  1427. else:
  1428. for attr in dir(module):
  1429. if ignore and attr.startswith(ignore):
  1430. continue
  1431. if prefix:
  1432. if attr in globals():
  1433. globals()[prefix + attr] = globals()[attr]
  1434. elif warn:
  1435. warnings.warn("No Python implementation of " + attr)
  1436. globals()[attr] = getattr(module, attr)
  1437. return True