test_rotation.py 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370
  1. import pytest
  2. import numpy as np
  3. from numpy.testing import assert_equal, assert_array_almost_equal
  4. from numpy.testing import assert_allclose
  5. from scipy.spatial.transform import Rotation, Slerp
  6. from scipy.stats import special_ortho_group
  7. from itertools import permutations
  8. import pickle
  9. import copy
  10. def test_generic_quat_matrix():
  11. x = np.array([[3, 4, 0, 0], [5, 12, 0, 0]])
  12. r = Rotation.from_quat(x)
  13. expected_quat = x / np.array([[5], [13]])
  14. assert_array_almost_equal(r.as_quat(), expected_quat)
  15. def test_from_single_1d_quaternion():
  16. x = np.array([3, 4, 0, 0])
  17. r = Rotation.from_quat(x)
  18. expected_quat = x / 5
  19. assert_array_almost_equal(r.as_quat(), expected_quat)
  20. def test_from_single_2d_quaternion():
  21. x = np.array([[3, 4, 0, 0]])
  22. r = Rotation.from_quat(x)
  23. expected_quat = x / 5
  24. assert_array_almost_equal(r.as_quat(), expected_quat)
  25. def test_from_square_quat_matrix():
  26. # Ensure proper norm array broadcasting
  27. x = np.array([
  28. [3, 0, 0, 4],
  29. [5, 0, 12, 0],
  30. [0, 0, 0, 1],
  31. [0, 0, 0, -1]
  32. ])
  33. r = Rotation.from_quat(x)
  34. expected_quat = x / np.array([[5], [13], [1], [1]])
  35. assert_array_almost_equal(r.as_quat(), expected_quat)
  36. def test_malformed_1d_from_quat():
  37. with pytest.raises(ValueError):
  38. Rotation.from_quat(np.array([1, 2, 3]))
  39. def test_malformed_2d_from_quat():
  40. with pytest.raises(ValueError):
  41. Rotation.from_quat(np.array([
  42. [1, 2, 3, 4, 5],
  43. [4, 5, 6, 7, 8]
  44. ]))
  45. def test_zero_norms_from_quat():
  46. x = np.array([
  47. [3, 4, 0, 0],
  48. [0, 0, 0, 0],
  49. [5, 0, 12, 0]
  50. ])
  51. with pytest.raises(ValueError):
  52. Rotation.from_quat(x)
  53. def test_as_matrix_single_1d_quaternion():
  54. quat = [0, 0, 0, 1]
  55. mat = Rotation.from_quat(quat).as_matrix()
  56. # mat.shape == (3,3) due to 1d input
  57. assert_array_almost_equal(mat, np.eye(3))
  58. def test_as_matrix_single_2d_quaternion():
  59. quat = [[0, 0, 1, 1]]
  60. mat = Rotation.from_quat(quat).as_matrix()
  61. assert_equal(mat.shape, (1, 3, 3))
  62. expected_mat = np.array([
  63. [0, -1, 0],
  64. [1, 0, 0],
  65. [0, 0, 1]
  66. ])
  67. assert_array_almost_equal(mat[0], expected_mat)
  68. def test_as_matrix_from_square_input():
  69. quats = [
  70. [0, 0, 1, 1],
  71. [0, 1, 0, 1],
  72. [0, 0, 0, 1],
  73. [0, 0, 0, -1]
  74. ]
  75. mat = Rotation.from_quat(quats).as_matrix()
  76. assert_equal(mat.shape, (4, 3, 3))
  77. expected0 = np.array([
  78. [0, -1, 0],
  79. [1, 0, 0],
  80. [0, 0, 1]
  81. ])
  82. assert_array_almost_equal(mat[0], expected0)
  83. expected1 = np.array([
  84. [0, 0, 1],
  85. [0, 1, 0],
  86. [-1, 0, 0]
  87. ])
  88. assert_array_almost_equal(mat[1], expected1)
  89. assert_array_almost_equal(mat[2], np.eye(3))
  90. assert_array_almost_equal(mat[3], np.eye(3))
  91. def test_as_matrix_from_generic_input():
  92. quats = [
  93. [0, 0, 1, 1],
  94. [0, 1, 0, 1],
  95. [1, 2, 3, 4]
  96. ]
  97. mat = Rotation.from_quat(quats).as_matrix()
  98. assert_equal(mat.shape, (3, 3, 3))
  99. expected0 = np.array([
  100. [0, -1, 0],
  101. [1, 0, 0],
  102. [0, 0, 1]
  103. ])
  104. assert_array_almost_equal(mat[0], expected0)
  105. expected1 = np.array([
  106. [0, 0, 1],
  107. [0, 1, 0],
  108. [-1, 0, 0]
  109. ])
  110. assert_array_almost_equal(mat[1], expected1)
  111. expected2 = np.array([
  112. [0.4, -2, 2.2],
  113. [2.8, 1, 0.4],
  114. [-1, 2, 2]
  115. ]) / 3
  116. assert_array_almost_equal(mat[2], expected2)
  117. def test_from_single_2d_matrix():
  118. mat = [
  119. [0, 0, 1],
  120. [1, 0, 0],
  121. [0, 1, 0]
  122. ]
  123. expected_quat = [0.5, 0.5, 0.5, 0.5]
  124. assert_array_almost_equal(
  125. Rotation.from_matrix(mat).as_quat(),
  126. expected_quat)
  127. def test_from_single_3d_matrix():
  128. mat = np.array([
  129. [0, 0, 1],
  130. [1, 0, 0],
  131. [0, 1, 0]
  132. ]).reshape((1, 3, 3))
  133. expected_quat = np.array([0.5, 0.5, 0.5, 0.5]).reshape((1, 4))
  134. assert_array_almost_equal(
  135. Rotation.from_matrix(mat).as_quat(),
  136. expected_quat)
  137. def test_from_matrix_calculation():
  138. expected_quat = np.array([1, 1, 6, 1]) / np.sqrt(39)
  139. mat = np.array([
  140. [-0.8974359, -0.2564103, 0.3589744],
  141. [0.3589744, -0.8974359, 0.2564103],
  142. [0.2564103, 0.3589744, 0.8974359]
  143. ])
  144. assert_array_almost_equal(
  145. Rotation.from_matrix(mat).as_quat(),
  146. expected_quat)
  147. assert_array_almost_equal(
  148. Rotation.from_matrix(mat.reshape((1, 3, 3))).as_quat(),
  149. expected_quat.reshape((1, 4)))
  150. def test_matrix_calculation_pipeline():
  151. mat = special_ortho_group.rvs(3, size=10, random_state=0)
  152. assert_array_almost_equal(Rotation.from_matrix(mat).as_matrix(), mat)
  153. def test_from_matrix_ortho_output():
  154. rnd = np.random.RandomState(0)
  155. mat = rnd.random_sample((100, 3, 3))
  156. ortho_mat = Rotation.from_matrix(mat).as_matrix()
  157. mult_result = np.einsum('...ij,...jk->...ik', ortho_mat,
  158. ortho_mat.transpose((0, 2, 1)))
  159. eye3d = np.zeros((100, 3, 3))
  160. for i in range(3):
  161. eye3d[:, i, i] = 1.0
  162. assert_array_almost_equal(mult_result, eye3d)
  163. def test_from_1d_single_rotvec():
  164. rotvec = [1, 0, 0]
  165. expected_quat = np.array([0.4794255, 0, 0, 0.8775826])
  166. result = Rotation.from_rotvec(rotvec)
  167. assert_array_almost_equal(result.as_quat(), expected_quat)
  168. def test_from_2d_single_rotvec():
  169. rotvec = [[1, 0, 0]]
  170. expected_quat = np.array([[0.4794255, 0, 0, 0.8775826]])
  171. result = Rotation.from_rotvec(rotvec)
  172. assert_array_almost_equal(result.as_quat(), expected_quat)
  173. def test_from_generic_rotvec():
  174. rotvec = [
  175. [1, 2, 2],
  176. [1, -1, 0.5],
  177. [0, 0, 0]
  178. ]
  179. expected_quat = np.array([
  180. [0.3324983, 0.6649967, 0.6649967, 0.0707372],
  181. [0.4544258, -0.4544258, 0.2272129, 0.7316889],
  182. [0, 0, 0, 1]
  183. ])
  184. assert_array_almost_equal(
  185. Rotation.from_rotvec(rotvec).as_quat(),
  186. expected_quat)
  187. def test_from_rotvec_small_angle():
  188. rotvec = np.array([
  189. [5e-4 / np.sqrt(3), -5e-4 / np.sqrt(3), 5e-4 / np.sqrt(3)],
  190. [0.2, 0.3, 0.4],
  191. [0, 0, 0]
  192. ])
  193. quat = Rotation.from_rotvec(rotvec).as_quat()
  194. # cos(theta/2) ~~ 1 for small theta
  195. assert_allclose(quat[0, 3], 1)
  196. # sin(theta/2) / theta ~~ 0.5 for small theta
  197. assert_allclose(quat[0, :3], rotvec[0] * 0.5)
  198. assert_allclose(quat[1, 3], 0.9639685)
  199. assert_allclose(
  200. quat[1, :3],
  201. np.array([
  202. 0.09879603932153465,
  203. 0.14819405898230198,
  204. 0.19759207864306931
  205. ]))
  206. assert_equal(quat[2], np.array([0, 0, 0, 1]))
  207. def test_degrees_from_rotvec():
  208. rotvec1 = [1.0 / np.cbrt(3), 1.0 / np.cbrt(3), 1.0 / np.cbrt(3)]
  209. rot1 = Rotation.from_rotvec(rotvec1, degrees=True)
  210. quat1 = rot1.as_quat()
  211. rotvec2 = np.deg2rad(rotvec1)
  212. rot2 = Rotation.from_rotvec(rotvec2)
  213. quat2 = rot2.as_quat()
  214. assert_allclose(quat1, quat2)
  215. def test_malformed_1d_from_rotvec():
  216. with pytest.raises(ValueError, match='Expected `rot_vec` to have shape'):
  217. Rotation.from_rotvec([1, 2])
  218. def test_malformed_2d_from_rotvec():
  219. with pytest.raises(ValueError, match='Expected `rot_vec` to have shape'):
  220. Rotation.from_rotvec([
  221. [1, 2, 3, 4],
  222. [5, 6, 7, 8]
  223. ])
  224. def test_as_generic_rotvec():
  225. quat = np.array([
  226. [1, 2, -1, 0.5],
  227. [1, -1, 1, 0.0003],
  228. [0, 0, 0, 1]
  229. ])
  230. quat /= np.linalg.norm(quat, axis=1)[:, None]
  231. rotvec = Rotation.from_quat(quat).as_rotvec()
  232. angle = np.linalg.norm(rotvec, axis=1)
  233. assert_allclose(quat[:, 3], np.cos(angle/2))
  234. assert_allclose(np.cross(rotvec, quat[:, :3]), np.zeros((3, 3)))
  235. def test_as_rotvec_single_1d_input():
  236. quat = np.array([1, 2, -3, 2])
  237. expected_rotvec = np.array([0.5772381, 1.1544763, -1.7317144])
  238. actual_rotvec = Rotation.from_quat(quat).as_rotvec()
  239. assert_equal(actual_rotvec.shape, (3,))
  240. assert_allclose(actual_rotvec, expected_rotvec)
  241. def test_as_rotvec_single_2d_input():
  242. quat = np.array([[1, 2, -3, 2]])
  243. expected_rotvec = np.array([[0.5772381, 1.1544763, -1.7317144]])
  244. actual_rotvec = Rotation.from_quat(quat).as_rotvec()
  245. assert_equal(actual_rotvec.shape, (1, 3))
  246. assert_allclose(actual_rotvec, expected_rotvec)
  247. def test_as_rotvec_degrees():
  248. # x->y, y->z, z->x
  249. mat = [[0, 0, 1], [1, 0, 0], [0, 1, 0]]
  250. rot = Rotation.from_matrix(mat)
  251. rotvec = rot.as_rotvec(degrees=True)
  252. angle = np.linalg.norm(rotvec)
  253. assert_allclose(angle, 120.0)
  254. assert_allclose(rotvec[0], rotvec[1])
  255. assert_allclose(rotvec[1], rotvec[2])
  256. def test_rotvec_calc_pipeline():
  257. # Include small angles
  258. rotvec = np.array([
  259. [0, 0, 0],
  260. [1, -1, 2],
  261. [-3e-4, 3.5e-4, 7.5e-5]
  262. ])
  263. assert_allclose(Rotation.from_rotvec(rotvec).as_rotvec(), rotvec)
  264. assert_allclose(Rotation.from_rotvec(rotvec, degrees=True).as_rotvec(degrees=True), rotvec)
  265. def test_from_1d_single_mrp():
  266. mrp = [0, 0, 1.0]
  267. expected_quat = np.array([0, 0, 1, 0])
  268. result = Rotation.from_mrp(mrp)
  269. assert_array_almost_equal(result.as_quat(), expected_quat)
  270. def test_from_2d_single_mrp():
  271. mrp = [[0, 0, 1.0]]
  272. expected_quat = np.array([[0, 0, 1, 0]])
  273. result = Rotation.from_mrp(mrp)
  274. assert_array_almost_equal(result.as_quat(), expected_quat)
  275. def test_from_generic_mrp():
  276. mrp = np.array([
  277. [1, 2, 2],
  278. [1, -1, 0.5],
  279. [0, 0, 0]])
  280. expected_quat = np.array([
  281. [0.2, 0.4, 0.4, -0.8],
  282. [0.61538462, -0.61538462, 0.30769231, -0.38461538],
  283. [0, 0, 0, 1]])
  284. assert_array_almost_equal(Rotation.from_mrp(mrp).as_quat(), expected_quat)
  285. def test_malformed_1d_from_mrp():
  286. with pytest.raises(ValueError, match='Expected `mrp` to have shape'):
  287. Rotation.from_mrp([1, 2])
  288. def test_malformed_2d_from_mrp():
  289. with pytest.raises(ValueError, match='Expected `mrp` to have shape'):
  290. Rotation.from_mrp([
  291. [1, 2, 3, 4],
  292. [5, 6, 7, 8]
  293. ])
  294. def test_as_generic_mrp():
  295. quat = np.array([
  296. [1, 2, -1, 0.5],
  297. [1, -1, 1, 0.0003],
  298. [0, 0, 0, 1]])
  299. quat /= np.linalg.norm(quat, axis=1)[:, None]
  300. expected_mrp = np.array([
  301. [0.33333333, 0.66666667, -0.33333333],
  302. [0.57725028, -0.57725028, 0.57725028],
  303. [0, 0, 0]])
  304. assert_array_almost_equal(Rotation.from_quat(quat).as_mrp(), expected_mrp)
  305. def test_past_180_degree_rotation():
  306. # ensure that a > 180 degree rotation is returned as a <180 rotation in MRPs
  307. # in this case 270 should be returned as -90
  308. expected_mrp = np.array([-np.tan(np.pi/2/4), 0.0, 0])
  309. assert_array_almost_equal(Rotation.from_euler('xyz', [270, 0, 0], degrees=True).as_mrp(), expected_mrp)
  310. def test_as_mrp_single_1d_input():
  311. quat = np.array([1, 2, -3, 2])
  312. expected_mrp = np.array([0.16018862, 0.32037724, -0.48056586])
  313. actual_mrp = Rotation.from_quat(quat).as_mrp()
  314. assert_equal(actual_mrp.shape, (3,))
  315. assert_allclose(actual_mrp, expected_mrp)
  316. def test_as_mrp_single_2d_input():
  317. quat = np.array([[1, 2, -3, 2]])
  318. expected_mrp = np.array([[0.16018862, 0.32037724, -0.48056586]])
  319. actual_mrp = Rotation.from_quat(quat).as_mrp()
  320. assert_equal(actual_mrp.shape, (1, 3))
  321. assert_allclose(actual_mrp, expected_mrp)
  322. def test_mrp_calc_pipeline():
  323. actual_mrp = np.array([
  324. [0, 0, 0],
  325. [1, -1, 2],
  326. [0.41421356, 0, 0],
  327. [0.1, 0.2, 0.1]])
  328. expected_mrp = np.array([
  329. [0, 0, 0],
  330. [-0.16666667, 0.16666667, -0.33333333],
  331. [0.41421356, 0, 0],
  332. [0.1, 0.2, 0.1]])
  333. assert_allclose(Rotation.from_mrp(actual_mrp).as_mrp(), expected_mrp)
  334. def test_from_euler_single_rotation():
  335. quat = Rotation.from_euler('z', 90, degrees=True).as_quat()
  336. expected_quat = np.array([0, 0, 1, 1]) / np.sqrt(2)
  337. assert_allclose(quat, expected_quat)
  338. def test_single_intrinsic_extrinsic_rotation():
  339. extrinsic = Rotation.from_euler('z', 90, degrees=True).as_matrix()
  340. intrinsic = Rotation.from_euler('Z', 90, degrees=True).as_matrix()
  341. assert_allclose(extrinsic, intrinsic)
  342. def test_from_euler_rotation_order():
  343. # Intrinsic rotation is same as extrinsic with order reversed
  344. rnd = np.random.RandomState(0)
  345. a = rnd.randint(low=0, high=180, size=(6, 3))
  346. b = a[:, ::-1]
  347. x = Rotation.from_euler('xyz', a, degrees=True).as_quat()
  348. y = Rotation.from_euler('ZYX', b, degrees=True).as_quat()
  349. assert_allclose(x, y)
  350. def test_from_euler_elementary_extrinsic_rotation():
  351. # Simple test to check if extrinsic rotations are implemented correctly
  352. mat = Rotation.from_euler('zx', [90, 90], degrees=True).as_matrix()
  353. expected_mat = np.array([
  354. [0, -1, 0],
  355. [0, 0, -1],
  356. [1, 0, 0]
  357. ])
  358. assert_array_almost_equal(mat, expected_mat)
  359. def test_from_euler_intrinsic_rotation_312():
  360. angles = [
  361. [30, 60, 45],
  362. [30, 60, 30],
  363. [45, 30, 60]
  364. ]
  365. mat = Rotation.from_euler('ZXY', angles, degrees=True).as_matrix()
  366. assert_array_almost_equal(mat[0], np.array([
  367. [0.3061862, -0.2500000, 0.9185587],
  368. [0.8838835, 0.4330127, -0.1767767],
  369. [-0.3535534, 0.8660254, 0.3535534]
  370. ]))
  371. assert_array_almost_equal(mat[1], np.array([
  372. [0.5334936, -0.2500000, 0.8080127],
  373. [0.8080127, 0.4330127, -0.3995191],
  374. [-0.2500000, 0.8660254, 0.4330127]
  375. ]))
  376. assert_array_almost_equal(mat[2], np.array([
  377. [0.0473672, -0.6123725, 0.7891491],
  378. [0.6597396, 0.6123725, 0.4355958],
  379. [-0.7500000, 0.5000000, 0.4330127]
  380. ]))
  381. def test_from_euler_intrinsic_rotation_313():
  382. angles = [
  383. [30, 60, 45],
  384. [30, 60, 30],
  385. [45, 30, 60]
  386. ]
  387. mat = Rotation.from_euler('ZXZ', angles, degrees=True).as_matrix()
  388. assert_array_almost_equal(mat[0], np.array([
  389. [0.43559574, -0.78914913, 0.4330127],
  390. [0.65973961, -0.04736717, -0.750000],
  391. [0.61237244, 0.61237244, 0.500000]
  392. ]))
  393. assert_array_almost_equal(mat[1], np.array([
  394. [0.6250000, -0.64951905, 0.4330127],
  395. [0.64951905, 0.1250000, -0.750000],
  396. [0.4330127, 0.750000, 0.500000]
  397. ]))
  398. assert_array_almost_equal(mat[2], np.array([
  399. [-0.1767767, -0.91855865, 0.35355339],
  400. [0.88388348, -0.30618622, -0.35355339],
  401. [0.4330127, 0.25000000, 0.8660254]
  402. ]))
  403. def test_from_euler_extrinsic_rotation_312():
  404. angles = [
  405. [30, 60, 45],
  406. [30, 60, 30],
  407. [45, 30, 60]
  408. ]
  409. mat = Rotation.from_euler('zxy', angles, degrees=True).as_matrix()
  410. assert_array_almost_equal(mat[0], np.array([
  411. [0.91855865, 0.1767767, 0.35355339],
  412. [0.25000000, 0.4330127, -0.8660254],
  413. [-0.30618622, 0.88388348, 0.35355339]
  414. ]))
  415. assert_array_almost_equal(mat[1], np.array([
  416. [0.96650635, -0.0580127, 0.2500000],
  417. [0.25000000, 0.4330127, -0.8660254],
  418. [-0.0580127, 0.89951905, 0.4330127]
  419. ]))
  420. assert_array_almost_equal(mat[2], np.array([
  421. [0.65973961, -0.04736717, 0.7500000],
  422. [0.61237244, 0.61237244, -0.5000000],
  423. [-0.43559574, 0.78914913, 0.4330127]
  424. ]))
  425. def test_from_euler_extrinsic_rotation_313():
  426. angles = [
  427. [30, 60, 45],
  428. [30, 60, 30],
  429. [45, 30, 60]
  430. ]
  431. mat = Rotation.from_euler('zxz', angles, degrees=True).as_matrix()
  432. assert_array_almost_equal(mat[0], np.array([
  433. [0.43559574, -0.65973961, 0.61237244],
  434. [0.78914913, -0.04736717, -0.61237244],
  435. [0.4330127, 0.75000000, 0.500000]
  436. ]))
  437. assert_array_almost_equal(mat[1], np.array([
  438. [0.62500000, -0.64951905, 0.4330127],
  439. [0.64951905, 0.12500000, -0.750000],
  440. [0.4330127, 0.75000000, 0.500000]
  441. ]))
  442. assert_array_almost_equal(mat[2], np.array([
  443. [-0.1767767, -0.88388348, 0.4330127],
  444. [0.91855865, -0.30618622, -0.250000],
  445. [0.35355339, 0.35355339, 0.8660254]
  446. ]))
  447. def test_as_euler_asymmetric_axes():
  448. rnd = np.random.RandomState(0)
  449. n = 10
  450. angles = np.empty((n, 3))
  451. angles[:, 0] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,))
  452. angles[:, 1] = rnd.uniform(low=-np.pi / 2, high=np.pi / 2, size=(n,))
  453. angles[:, 2] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,))
  454. for seq_tuple in permutations('xyz'):
  455. # Extrinsic rotations
  456. seq = ''.join(seq_tuple)
  457. assert_allclose(angles, Rotation.from_euler(seq, angles).as_euler(seq))
  458. # Intrinsic rotations
  459. seq = seq.upper()
  460. assert_allclose(angles, Rotation.from_euler(seq, angles).as_euler(seq))
  461. def test_as_euler_symmetric_axes():
  462. rnd = np.random.RandomState(0)
  463. n = 10
  464. angles = np.empty((n, 3))
  465. angles[:, 0] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,))
  466. angles[:, 1] = rnd.uniform(low=0, high=np.pi, size=(n,))
  467. angles[:, 2] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,))
  468. for axis1 in ['x', 'y', 'z']:
  469. for axis2 in ['x', 'y', 'z']:
  470. if axis1 == axis2:
  471. continue
  472. # Extrinsic rotations
  473. seq = axis1 + axis2 + axis1
  474. assert_allclose(
  475. angles, Rotation.from_euler(seq, angles).as_euler(seq))
  476. # Intrinsic rotations
  477. seq = seq.upper()
  478. assert_allclose(
  479. angles, Rotation.from_euler(seq, angles).as_euler(seq))
  480. def test_as_euler_degenerate_asymmetric_axes():
  481. # Since we cannot check for angle equality, we check for rotation matrix
  482. # equality
  483. angles = np.array([
  484. [45, 90, 35],
  485. [35, -90, 20],
  486. [35, 90, 25],
  487. [25, -90, 15]
  488. ])
  489. with pytest.warns(UserWarning, match="Gimbal lock"):
  490. for seq_tuple in permutations('xyz'):
  491. # Extrinsic rotations
  492. seq = ''.join(seq_tuple)
  493. rotation = Rotation.from_euler(seq, angles, degrees=True)
  494. mat_expected = rotation.as_matrix()
  495. angle_estimates = rotation.as_euler(seq, degrees=True)
  496. mat_estimated = Rotation.from_euler(
  497. seq, angle_estimates, degrees=True
  498. ).as_matrix()
  499. assert_array_almost_equal(mat_expected, mat_estimated)
  500. # Intrinsic rotations
  501. seq = seq.upper()
  502. rotation = Rotation.from_euler(seq, angles, degrees=True)
  503. mat_expected = rotation.as_matrix()
  504. angle_estimates = rotation.as_euler(seq, degrees=True)
  505. mat_estimated = Rotation.from_euler(
  506. seq, angle_estimates, degrees=True
  507. ).as_matrix()
  508. assert_array_almost_equal(mat_expected, mat_estimated)
  509. def test_as_euler_degenerate_symmetric_axes():
  510. # Since we cannot check for angle equality, we check for rotation matrix
  511. # equality
  512. angles = np.array([
  513. [15, 0, 60],
  514. [35, 0, 75],
  515. [60, 180, 35],
  516. [15, -180, 25],
  517. ])
  518. with pytest.warns(UserWarning, match="Gimbal lock"):
  519. for axis1 in ['x', 'y', 'z']:
  520. for axis2 in ['x', 'y', 'z']:
  521. if axis1 == axis2:
  522. continue
  523. # Extrinsic rotations
  524. seq = axis1 + axis2 + axis1
  525. rotation = Rotation.from_euler(seq, angles, degrees=True)
  526. mat_expected = rotation.as_matrix()
  527. angle_estimates = rotation.as_euler(seq, degrees=True)
  528. mat_estimated = Rotation.from_euler(
  529. seq, angle_estimates, degrees=True
  530. ).as_matrix()
  531. assert_array_almost_equal(mat_expected, mat_estimated)
  532. # Intrinsic rotations
  533. seq = seq.upper()
  534. rotation = Rotation.from_euler(seq, angles, degrees=True)
  535. mat_expected = rotation.as_matrix()
  536. angle_estimates = rotation.as_euler(seq, degrees=True)
  537. mat_estimated = Rotation.from_euler(
  538. seq, angle_estimates, degrees=True
  539. ).as_matrix()
  540. assert_array_almost_equal(mat_expected, mat_estimated)
  541. def test_inv():
  542. rnd = np.random.RandomState(0)
  543. n = 10
  544. p = Rotation.random(num=n, random_state=rnd)
  545. q = p.inv()
  546. p_mat = p.as_matrix()
  547. q_mat = q.as_matrix()
  548. result1 = np.einsum('...ij,...jk->...ik', p_mat, q_mat)
  549. result2 = np.einsum('...ij,...jk->...ik', q_mat, p_mat)
  550. eye3d = np.empty((n, 3, 3))
  551. eye3d[:] = np.eye(3)
  552. assert_array_almost_equal(result1, eye3d)
  553. assert_array_almost_equal(result2, eye3d)
  554. def test_inv_single_rotation():
  555. rnd = np.random.RandomState(0)
  556. p = Rotation.random(random_state=rnd)
  557. q = p.inv()
  558. p_mat = p.as_matrix()
  559. q_mat = q.as_matrix()
  560. res1 = np.dot(p_mat, q_mat)
  561. res2 = np.dot(q_mat, p_mat)
  562. eye = np.eye(3)
  563. assert_array_almost_equal(res1, eye)
  564. assert_array_almost_equal(res2, eye)
  565. x = Rotation.random(num=1, random_state=rnd)
  566. y = x.inv()
  567. x_matrix = x.as_matrix()
  568. y_matrix = y.as_matrix()
  569. result1 = np.einsum('...ij,...jk->...ik', x_matrix, y_matrix)
  570. result2 = np.einsum('...ij,...jk->...ik', y_matrix, x_matrix)
  571. eye3d = np.empty((1, 3, 3))
  572. eye3d[:] = np.eye(3)
  573. assert_array_almost_equal(result1, eye3d)
  574. assert_array_almost_equal(result2, eye3d)
  575. def test_identity_magnitude():
  576. n = 10
  577. assert_allclose(Rotation.identity(n).magnitude(), 0)
  578. assert_allclose(Rotation.identity(n).inv().magnitude(), 0)
  579. def test_single_identity_magnitude():
  580. assert Rotation.identity().magnitude() == 0
  581. assert Rotation.identity().inv().magnitude() == 0
  582. def test_identity_invariance():
  583. n = 10
  584. p = Rotation.random(n, random_state=0)
  585. result = p * Rotation.identity(n)
  586. assert_array_almost_equal(p.as_quat(), result.as_quat())
  587. result = result * p.inv()
  588. assert_array_almost_equal(result.magnitude(), np.zeros(n))
  589. def test_single_identity_invariance():
  590. n = 10
  591. p = Rotation.random(n, random_state=0)
  592. result = p * Rotation.identity()
  593. assert_array_almost_equal(p.as_quat(), result.as_quat())
  594. result = result * p.inv()
  595. assert_array_almost_equal(result.magnitude(), np.zeros(n))
  596. def test_magnitude():
  597. r = Rotation.from_quat(np.eye(4))
  598. result = r.magnitude()
  599. assert_array_almost_equal(result, [np.pi, np.pi, np.pi, 0])
  600. r = Rotation.from_quat(-np.eye(4))
  601. result = r.magnitude()
  602. assert_array_almost_equal(result, [np.pi, np.pi, np.pi, 0])
  603. def test_magnitude_single_rotation():
  604. r = Rotation.from_quat(np.eye(4))
  605. result1 = r[0].magnitude()
  606. assert_allclose(result1, np.pi)
  607. result2 = r[3].magnitude()
  608. assert_allclose(result2, 0)
  609. def test_mean():
  610. axes = np.concatenate((-np.eye(3), np.eye(3)))
  611. thetas = np.linspace(0, np.pi / 2, 100)
  612. for t in thetas:
  613. r = Rotation.from_rotvec(t * axes)
  614. assert_allclose(r.mean().magnitude(), 0, atol=1E-10)
  615. def test_weighted_mean():
  616. # test that doubling a weight is equivalent to including a rotation twice.
  617. axes = np.array([[0, 0, 0], [1, 0, 0], [1, 0, 0]])
  618. thetas = np.linspace(0, np.pi / 2, 100)
  619. for t in thetas:
  620. rw = Rotation.from_rotvec(t * axes[:2])
  621. mw = rw.mean(weights=[1, 2])
  622. r = Rotation.from_rotvec(t * axes)
  623. m = r.mean()
  624. assert_allclose((m * mw.inv()).magnitude(), 0, atol=1E-10)
  625. def test_mean_invalid_weights():
  626. with pytest.raises(ValueError, match="non-negative"):
  627. r = Rotation.from_quat(np.eye(4))
  628. r.mean(weights=-np.ones(4))
  629. def test_reduction_no_indices():
  630. result = Rotation.identity().reduce(return_indices=False)
  631. assert isinstance(result, Rotation)
  632. def test_reduction_none_indices():
  633. result = Rotation.identity().reduce(return_indices=True)
  634. assert type(result) == tuple
  635. assert len(result) == 3
  636. reduced, left_best, right_best = result
  637. assert left_best is None
  638. assert right_best is None
  639. def test_reduction_scalar_calculation():
  640. rng = np.random.RandomState(0)
  641. l = Rotation.random(5, random_state=rng)
  642. r = Rotation.random(10, random_state=rng)
  643. p = Rotation.random(7, random_state=rng)
  644. reduced, left_best, right_best = p.reduce(l, r, return_indices=True)
  645. # Loop implementation of the vectorized calculation in Rotation.reduce
  646. scalars = np.zeros((len(l), len(p), len(r)))
  647. for i, li in enumerate(l):
  648. for j, pj in enumerate(p):
  649. for k, rk in enumerate(r):
  650. scalars[i, j, k] = np.abs((li * pj * rk).as_quat()[3])
  651. scalars = np.reshape(np.moveaxis(scalars, 1, 0), (scalars.shape[1], -1))
  652. max_ind = np.argmax(np.reshape(scalars, (len(p), -1)), axis=1)
  653. left_best_check = max_ind // len(r)
  654. right_best_check = max_ind % len(r)
  655. assert (left_best == left_best_check).all()
  656. assert (right_best == right_best_check).all()
  657. reduced_check = l[left_best_check] * p * r[right_best_check]
  658. mag = (reduced.inv() * reduced_check).magnitude()
  659. assert_array_almost_equal(mag, np.zeros(len(p)))
  660. def test_apply_single_rotation_single_point():
  661. mat = np.array([
  662. [0, -1, 0],
  663. [1, 0, 0],
  664. [0, 0, 1]
  665. ])
  666. r_1d = Rotation.from_matrix(mat)
  667. r_2d = Rotation.from_matrix(np.expand_dims(mat, axis=0))
  668. v_1d = np.array([1, 2, 3])
  669. v_2d = np.expand_dims(v_1d, axis=0)
  670. v1d_rotated = np.array([-2, 1, 3])
  671. v2d_rotated = np.expand_dims(v1d_rotated, axis=0)
  672. assert_allclose(r_1d.apply(v_1d), v1d_rotated)
  673. assert_allclose(r_1d.apply(v_2d), v2d_rotated)
  674. assert_allclose(r_2d.apply(v_1d), v2d_rotated)
  675. assert_allclose(r_2d.apply(v_2d), v2d_rotated)
  676. v1d_inverse = np.array([2, -1, 3])
  677. v2d_inverse = np.expand_dims(v1d_inverse, axis=0)
  678. assert_allclose(r_1d.apply(v_1d, inverse=True), v1d_inverse)
  679. assert_allclose(r_1d.apply(v_2d, inverse=True), v2d_inverse)
  680. assert_allclose(r_2d.apply(v_1d, inverse=True), v2d_inverse)
  681. assert_allclose(r_2d.apply(v_2d, inverse=True), v2d_inverse)
  682. def test_apply_single_rotation_multiple_points():
  683. mat = np.array([
  684. [0, -1, 0],
  685. [1, 0, 0],
  686. [0, 0, 1]
  687. ])
  688. r1 = Rotation.from_matrix(mat)
  689. r2 = Rotation.from_matrix(np.expand_dims(mat, axis=0))
  690. v = np.array([[1, 2, 3], [4, 5, 6]])
  691. v_rotated = np.array([[-2, 1, 3], [-5, 4, 6]])
  692. assert_allclose(r1.apply(v), v_rotated)
  693. assert_allclose(r2.apply(v), v_rotated)
  694. v_inverse = np.array([[2, -1, 3], [5, -4, 6]])
  695. assert_allclose(r1.apply(v, inverse=True), v_inverse)
  696. assert_allclose(r2.apply(v, inverse=True), v_inverse)
  697. def test_apply_multiple_rotations_single_point():
  698. mat = np.empty((2, 3, 3))
  699. mat[0] = np.array([
  700. [0, -1, 0],
  701. [1, 0, 0],
  702. [0, 0, 1]
  703. ])
  704. mat[1] = np.array([
  705. [1, 0, 0],
  706. [0, 0, -1],
  707. [0, 1, 0]
  708. ])
  709. r = Rotation.from_matrix(mat)
  710. v1 = np.array([1, 2, 3])
  711. v2 = np.expand_dims(v1, axis=0)
  712. v_rotated = np.array([[-2, 1, 3], [1, -3, 2]])
  713. assert_allclose(r.apply(v1), v_rotated)
  714. assert_allclose(r.apply(v2), v_rotated)
  715. v_inverse = np.array([[2, -1, 3], [1, 3, -2]])
  716. assert_allclose(r.apply(v1, inverse=True), v_inverse)
  717. assert_allclose(r.apply(v2, inverse=True), v_inverse)
  718. def test_apply_multiple_rotations_multiple_points():
  719. mat = np.empty((2, 3, 3))
  720. mat[0] = np.array([
  721. [0, -1, 0],
  722. [1, 0, 0],
  723. [0, 0, 1]
  724. ])
  725. mat[1] = np.array([
  726. [1, 0, 0],
  727. [0, 0, -1],
  728. [0, 1, 0]
  729. ])
  730. r = Rotation.from_matrix(mat)
  731. v = np.array([[1, 2, 3], [4, 5, 6]])
  732. v_rotated = np.array([[-2, 1, 3], [4, -6, 5]])
  733. assert_allclose(r.apply(v), v_rotated)
  734. v_inverse = np.array([[2, -1, 3], [4, 6, -5]])
  735. assert_allclose(r.apply(v, inverse=True), v_inverse)
  736. def test_getitem():
  737. mat = np.empty((2, 3, 3))
  738. mat[0] = np.array([
  739. [0, -1, 0],
  740. [1, 0, 0],
  741. [0, 0, 1]
  742. ])
  743. mat[1] = np.array([
  744. [1, 0, 0],
  745. [0, 0, -1],
  746. [0, 1, 0]
  747. ])
  748. r = Rotation.from_matrix(mat)
  749. assert_allclose(r[0].as_matrix(), mat[0], atol=1e-15)
  750. assert_allclose(r[1].as_matrix(), mat[1], atol=1e-15)
  751. assert_allclose(r[:-1].as_matrix(), np.expand_dims(mat[0], axis=0), atol=1e-15)
  752. def test_getitem_single():
  753. with pytest.raises(TypeError, match='not subscriptable'):
  754. Rotation.identity()[0]
  755. def test_setitem_single():
  756. r = Rotation.identity()
  757. with pytest.raises(TypeError, match='not subscriptable'):
  758. r[0] = Rotation.identity()
  759. def test_setitem_slice():
  760. rng = np.random.RandomState(seed=0)
  761. r1 = Rotation.random(10, random_state=rng)
  762. r2 = Rotation.random(5, random_state=rng)
  763. r1[1:6] = r2
  764. assert_equal(r1[1:6].as_quat(), r2.as_quat())
  765. def test_setitem_integer():
  766. rng = np.random.RandomState(seed=0)
  767. r1 = Rotation.random(10, random_state=rng)
  768. r2 = Rotation.random(random_state=rng)
  769. r1[1] = r2
  770. assert_equal(r1[1].as_quat(), r2.as_quat())
  771. def test_setitem_wrong_type():
  772. r = Rotation.random(10, random_state=0)
  773. with pytest.raises(TypeError, match='Rotation object'):
  774. r[0] = 1
  775. def test_n_rotations():
  776. mat = np.empty((2, 3, 3))
  777. mat[0] = np.array([
  778. [0, -1, 0],
  779. [1, 0, 0],
  780. [0, 0, 1]
  781. ])
  782. mat[1] = np.array([
  783. [1, 0, 0],
  784. [0, 0, -1],
  785. [0, 1, 0]
  786. ])
  787. r = Rotation.from_matrix(mat)
  788. assert_equal(len(r), 2)
  789. assert_equal(len(r[:-1]), 1)
  790. def test_align_vectors_no_rotation():
  791. x = np.array([[1, 2, 3], [4, 5, 6]])
  792. y = x.copy()
  793. r, rmsd = Rotation.align_vectors(x, y)
  794. assert_array_almost_equal(r.as_matrix(), np.eye(3))
  795. assert_allclose(rmsd, 0, atol=1e-6)
  796. def test_align_vectors_no_noise():
  797. rnd = np.random.RandomState(0)
  798. c = Rotation.random(random_state=rnd)
  799. b = rnd.normal(size=(5, 3))
  800. a = c.apply(b)
  801. est, rmsd = Rotation.align_vectors(a, b)
  802. assert_allclose(c.as_quat(), est.as_quat())
  803. assert_allclose(rmsd, 0, atol=1e-7)
  804. def test_align_vectors_improper_rotation():
  805. # Tests correct logic for issue #10444
  806. x = np.array([[0.89299824, -0.44372674, 0.0752378],
  807. [0.60221789, -0.47564102, -0.6411702]])
  808. y = np.array([[0.02386536, -0.82176463, 0.5693271],
  809. [-0.27654929, -0.95191427, -0.1318321]])
  810. est, rmsd = Rotation.align_vectors(x, y)
  811. assert_allclose(x, est.apply(y), atol=1e-6)
  812. assert_allclose(rmsd, 0, atol=1e-7)
  813. def test_align_vectors_scaled_weights():
  814. rng = np.random.RandomState(0)
  815. c = Rotation.random(random_state=rng)
  816. b = rng.normal(size=(5, 3))
  817. a = c.apply(b)
  818. est1, rmsd1, cov1 = Rotation.align_vectors(a, b, np.ones(5), True)
  819. est2, rmsd2, cov2 = Rotation.align_vectors(a, b, 2 * np.ones(5), True)
  820. assert_allclose(est1.as_matrix(), est2.as_matrix())
  821. assert_allclose(np.sqrt(2) * rmsd1, rmsd2)
  822. assert_allclose(cov1, cov2)
  823. def test_align_vectors_noise():
  824. rnd = np.random.RandomState(0)
  825. n_vectors = 100
  826. rot = Rotation.random(random_state=rnd)
  827. vectors = rnd.normal(size=(n_vectors, 3))
  828. result = rot.apply(vectors)
  829. # The paper adds noise as independently distributed angular errors
  830. sigma = np.deg2rad(1)
  831. tolerance = 1.5 * sigma
  832. noise = Rotation.from_rotvec(
  833. rnd.normal(
  834. size=(n_vectors, 3),
  835. scale=sigma
  836. )
  837. )
  838. # Attitude errors must preserve norm. Hence apply individual random
  839. # rotations to each vector.
  840. noisy_result = noise.apply(result)
  841. est, rmsd, cov = Rotation.align_vectors(noisy_result, vectors,
  842. return_sensitivity=True)
  843. # Use rotation compositions to find out closeness
  844. error_vector = (rot * est.inv()).as_rotvec()
  845. assert_allclose(error_vector[0], 0, atol=tolerance)
  846. assert_allclose(error_vector[1], 0, atol=tolerance)
  847. assert_allclose(error_vector[2], 0, atol=tolerance)
  848. # Check error bounds using covariance matrix
  849. cov *= sigma
  850. assert_allclose(cov[0, 0], 0, atol=tolerance)
  851. assert_allclose(cov[1, 1], 0, atol=tolerance)
  852. assert_allclose(cov[2, 2], 0, atol=tolerance)
  853. assert_allclose(rmsd, np.sum((noisy_result - est.apply(vectors))**2)**0.5)
  854. def test_align_vectors_single_vector():
  855. with pytest.warns(UserWarning, match="Optimal rotation is not"):
  856. r_estimate, rmsd = Rotation.align_vectors([[1, -1, 1]], [[1, 1, -1]])
  857. assert_allclose(rmsd, 0, atol=1e-16)
  858. def test_align_vectors_invalid_input():
  859. with pytest.raises(ValueError, match="Expected input `a` to have shape"):
  860. Rotation.align_vectors([1, 2, 3], [[1, 2, 3]])
  861. with pytest.raises(ValueError, match="Expected input `b` to have shape"):
  862. Rotation.align_vectors([[1, 2, 3]], [1, 2, 3])
  863. with pytest.raises(ValueError, match="Expected inputs `a` and `b` "
  864. "to have same shapes"):
  865. Rotation.align_vectors([[1, 2, 3],[4, 5, 6]], [[1, 2, 3]])
  866. with pytest.raises(ValueError,
  867. match="Expected `weights` to be 1 dimensional"):
  868. Rotation.align_vectors([[1, 2, 3]], [[1, 2, 3]], weights=[[1]])
  869. with pytest.raises(ValueError,
  870. match="Expected `weights` to have number of values"):
  871. Rotation.align_vectors([[1, 2, 3]], [[1, 2, 3]], weights=[1, 2])
  872. with pytest.raises(ValueError,
  873. match="`weights` may not contain negative values"):
  874. Rotation.align_vectors([[1, 2, 3]], [[1, 2, 3]], weights=[-1])
  875. def test_random_rotation_shape():
  876. rnd = np.random.RandomState(0)
  877. assert_equal(Rotation.random(random_state=rnd).as_quat().shape, (4,))
  878. assert_equal(Rotation.random(None, random_state=rnd).as_quat().shape, (4,))
  879. assert_equal(Rotation.random(1, random_state=rnd).as_quat().shape, (1, 4))
  880. assert_equal(Rotation.random(5, random_state=rnd).as_quat().shape, (5, 4))
  881. def test_slerp():
  882. rnd = np.random.RandomState(0)
  883. key_rots = Rotation.from_quat(rnd.uniform(size=(5, 4)))
  884. key_quats = key_rots.as_quat()
  885. key_times = [0, 1, 2, 3, 4]
  886. interpolator = Slerp(key_times, key_rots)
  887. times = [0, 0.5, 0.25, 1, 1.5, 2, 2.75, 3, 3.25, 3.60, 4]
  888. interp_rots = interpolator(times)
  889. interp_quats = interp_rots.as_quat()
  890. # Dot products are affected by sign of quaternions
  891. interp_quats[interp_quats[:, -1] < 0] *= -1
  892. # Checking for quaternion equality, perform same operation
  893. key_quats[key_quats[:, -1] < 0] *= -1
  894. # Equality at keyframes, including both endpoints
  895. assert_allclose(interp_quats[0], key_quats[0])
  896. assert_allclose(interp_quats[3], key_quats[1])
  897. assert_allclose(interp_quats[5], key_quats[2])
  898. assert_allclose(interp_quats[7], key_quats[3])
  899. assert_allclose(interp_quats[10], key_quats[4])
  900. # Constant angular velocity between keyframes. Check by equating
  901. # cos(theta) between quaternion pairs with equal time difference.
  902. cos_theta1 = np.sum(interp_quats[0] * interp_quats[2])
  903. cos_theta2 = np.sum(interp_quats[2] * interp_quats[1])
  904. assert_allclose(cos_theta1, cos_theta2)
  905. cos_theta4 = np.sum(interp_quats[3] * interp_quats[4])
  906. cos_theta5 = np.sum(interp_quats[4] * interp_quats[5])
  907. assert_allclose(cos_theta4, cos_theta5)
  908. # theta1: 0 -> 0.25, theta3 : 0.5 -> 1
  909. # Use double angle formula for double the time difference
  910. cos_theta3 = np.sum(interp_quats[1] * interp_quats[3])
  911. assert_allclose(cos_theta3, 2 * (cos_theta1**2) - 1)
  912. # Miscellaneous checks
  913. assert_equal(len(interp_rots), len(times))
  914. def test_slerp_single_rot():
  915. with pytest.raises(ValueError, match="must be a sequence of rotations"):
  916. r = Rotation.from_quat([1, 2, 3, 4])
  917. Slerp([1], r)
  918. def test_slerp_time_dim_mismatch():
  919. with pytest.raises(ValueError,
  920. match="times to be specified in a 1 dimensional array"):
  921. rnd = np.random.RandomState(0)
  922. r = Rotation.from_quat(rnd.uniform(size=(2, 4)))
  923. t = np.array([[1],
  924. [2]])
  925. Slerp(t, r)
  926. def test_slerp_num_rotations_mismatch():
  927. with pytest.raises(ValueError, match="number of rotations to be equal to "
  928. "number of timestamps"):
  929. rnd = np.random.RandomState(0)
  930. r = Rotation.from_quat(rnd.uniform(size=(5, 4)))
  931. t = np.arange(7)
  932. Slerp(t, r)
  933. def test_slerp_equal_times():
  934. with pytest.raises(ValueError, match="strictly increasing order"):
  935. rnd = np.random.RandomState(0)
  936. r = Rotation.from_quat(rnd.uniform(size=(5, 4)))
  937. t = [0, 1, 2, 2, 4]
  938. Slerp(t, r)
  939. def test_slerp_decreasing_times():
  940. with pytest.raises(ValueError, match="strictly increasing order"):
  941. rnd = np.random.RandomState(0)
  942. r = Rotation.from_quat(rnd.uniform(size=(5, 4)))
  943. t = [0, 1, 3, 2, 4]
  944. Slerp(t, r)
  945. def test_slerp_call_time_dim_mismatch():
  946. rnd = np.random.RandomState(0)
  947. r = Rotation.from_quat(rnd.uniform(size=(5, 4)))
  948. t = np.arange(5)
  949. s = Slerp(t, r)
  950. with pytest.raises(ValueError,
  951. match="`times` must be at most 1-dimensional."):
  952. interp_times = np.array([[3.5],
  953. [4.2]])
  954. s(interp_times)
  955. def test_slerp_call_time_out_of_range():
  956. rnd = np.random.RandomState(0)
  957. r = Rotation.from_quat(rnd.uniform(size=(5, 4)))
  958. t = np.arange(5) + 1
  959. s = Slerp(t, r)
  960. with pytest.raises(ValueError, match="times must be within the range"):
  961. s([0, 1, 2])
  962. with pytest.raises(ValueError, match="times must be within the range"):
  963. s([1, 2, 6])
  964. def test_slerp_call_scalar_time():
  965. r = Rotation.from_euler('X', [0, 80], degrees=True)
  966. s = Slerp([0, 1], r)
  967. r_interpolated = s(0.25)
  968. r_interpolated_expected = Rotation.from_euler('X', 20, degrees=True)
  969. delta = r_interpolated * r_interpolated_expected.inv()
  970. assert_allclose(delta.magnitude(), 0, atol=1e-16)
  971. def test_multiplication_stability():
  972. qs = Rotation.random(50, random_state=0)
  973. rs = Rotation.random(1000, random_state=1)
  974. for q in qs:
  975. rs *= q * rs
  976. assert_allclose(np.linalg.norm(rs.as_quat(), axis=1), 1)
  977. def test_rotation_within_numpy_array():
  978. single = Rotation.random(random_state=0)
  979. multiple = Rotation.random(2, random_state=1)
  980. array = np.array(single)
  981. assert_equal(array.shape, ())
  982. array = np.array(multiple)
  983. assert_equal(array.shape, (2,))
  984. assert_allclose(array[0].as_matrix(), multiple[0].as_matrix())
  985. assert_allclose(array[1].as_matrix(), multiple[1].as_matrix())
  986. array = np.array([single])
  987. assert_equal(array.shape, (1,))
  988. assert_equal(array[0], single)
  989. array = np.array([multiple])
  990. assert_equal(array.shape, (1, 2))
  991. assert_allclose(array[0, 0].as_matrix(), multiple[0].as_matrix())
  992. assert_allclose(array[0, 1].as_matrix(), multiple[1].as_matrix())
  993. array = np.array([single, multiple], dtype=object)
  994. assert_equal(array.shape, (2,))
  995. assert_equal(array[0], single)
  996. assert_equal(array[1], multiple)
  997. array = np.array([multiple, multiple, multiple])
  998. assert_equal(array.shape, (3, 2))
  999. def test_pickling():
  1000. r = Rotation.from_quat([0, 0, np.sin(np.pi/4), np.cos(np.pi/4)])
  1001. pkl = pickle.dumps(r)
  1002. unpickled = pickle.loads(pkl)
  1003. assert_allclose(r.as_matrix(), unpickled.as_matrix(), atol=1e-15)
  1004. def test_deepcopy():
  1005. r = Rotation.from_quat([0, 0, np.sin(np.pi/4), np.cos(np.pi/4)])
  1006. r1 = copy.deepcopy(r)
  1007. assert_allclose(r.as_matrix(), r1.as_matrix(), atol=1e-15)
  1008. def test_as_euler_contiguous():
  1009. r = Rotation.from_quat([0, 0, 0, 1])
  1010. e1 = r.as_euler('xyz') # extrinsic euler rotation
  1011. e2 = r.as_euler('XYZ') # intrinsic
  1012. assert e1.flags['C_CONTIGUOUS'] is True
  1013. assert e2.flags['C_CONTIGUOUS'] is True
  1014. assert all(i >= 0 for i in e1.strides)
  1015. assert all(i >= 0 for i in e2.strides)
  1016. def test_concatenate():
  1017. rotation = Rotation.random(10, random_state=0)
  1018. sizes = [1, 2, 3, 1, 3]
  1019. starts = [0] + list(np.cumsum(sizes))
  1020. split = [rotation[i:i + n] for i, n in zip(starts, sizes)]
  1021. result = Rotation.concatenate(split)
  1022. assert_equal(rotation.as_quat(), result.as_quat())
  1023. def test_concatenate_wrong_type():
  1024. with pytest.raises(TypeError, match='Rotation objects only'):
  1025. Rotation.concatenate([Rotation.identity(), 1, None])
  1026. # Regression test for gh-16663
  1027. def test_len_and_bool():
  1028. rotation_multi_empty = Rotation(np.empty((0, 4)))
  1029. rotation_multi_one = Rotation([[0, 0, 0, 1]])
  1030. rotation_multi = Rotation([[0, 0, 0, 1], [0, 0, 0, 1]])
  1031. rotation_single = Rotation([0, 0, 0, 1])
  1032. assert len(rotation_multi_empty) == 0
  1033. assert len(rotation_multi_one) == 1
  1034. assert len(rotation_multi) == 2
  1035. with pytest.raises(TypeError, match="Single rotation has no len()."):
  1036. len(rotation_single)
  1037. # Rotation should always be truthy. See gh-16663
  1038. assert rotation_multi_empty
  1039. assert rotation_multi_one
  1040. assert rotation_multi
  1041. assert rotation_single