rotation.py 61 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765
  1. from __future__ import division, print_function, absolute_import
  2. import re
  3. import warnings
  4. import numpy as np
  5. import scipy.linalg
  6. from scipy._lib._util import check_random_state
  7. _AXIS_TO_IND = {'x': 0, 'y': 1, 'z': 2}
  8. def _elementary_basis_vector(axis):
  9. b = np.zeros(3)
  10. b[_AXIS_TO_IND[axis]] = 1
  11. return b
  12. def _compute_euler_from_dcm(dcm, seq, extrinsic=False):
  13. # The algorithm assumes intrinsic frame transformations. For representation
  14. # the paper uses transformation matrices, which are transpose of the
  15. # direction cosine matrices used by our Rotation class.
  16. # Adapt the algorithm for our case by
  17. # 1. Instead of transposing our representation, use the transpose of the
  18. # O matrix as defined in the paper, and be careful to swap indices
  19. # 2. Reversing both axis sequence and angles for extrinsic rotations
  20. if extrinsic:
  21. seq = seq[::-1]
  22. if dcm.ndim == 2:
  23. dcm = dcm[None, :, :]
  24. num_rotations = dcm.shape[0]
  25. # Step 0
  26. # Algorithm assumes axes as column vectors, here we use 1D vectors
  27. n1 = _elementary_basis_vector(seq[0])
  28. n2 = _elementary_basis_vector(seq[1])
  29. n3 = _elementary_basis_vector(seq[2])
  30. # Step 2
  31. sl = np.dot(np.cross(n1, n2), n3)
  32. cl = np.dot(n1, n3)
  33. # angle offset is lambda from the paper referenced in [2] from docstring of
  34. # `as_euler` function
  35. offset = np.arctan2(sl, cl)
  36. c = np.vstack((n2, np.cross(n1, n2), n1))
  37. # Step 3
  38. rot = np.array([
  39. [1, 0, 0],
  40. [0, cl, sl],
  41. [0, -sl, cl],
  42. ])
  43. res = np.einsum('...ij,...jk->...ik', c, dcm)
  44. dcm_transformed = np.einsum('...ij,...jk->...ik', res, c.T.dot(rot))
  45. # Step 4
  46. angles = np.empty((num_rotations, 3))
  47. # Ensure less than unit norm
  48. positive_unity = dcm_transformed[:, 2, 2] > 1
  49. negative_unity = dcm_transformed[:, 2, 2] < -1
  50. dcm_transformed[positive_unity, 2, 2] = 1
  51. dcm_transformed[negative_unity, 2, 2] = -1
  52. angles[:, 1] = np.arccos(dcm_transformed[:, 2, 2])
  53. # Steps 5, 6
  54. eps = 1e-7
  55. safe1 = (np.abs(angles[:, 1]) >= eps)
  56. safe2 = (np.abs(angles[:, 1] - np.pi) >= eps)
  57. # Step 4 (Completion)
  58. angles[:, 1] += offset
  59. # 5b
  60. safe_mask = np.logical_and(safe1, safe2)
  61. angles[safe_mask, 0] = np.arctan2(dcm_transformed[safe_mask, 0, 2],
  62. -dcm_transformed[safe_mask, 1, 2])
  63. angles[safe_mask, 2] = np.arctan2(dcm_transformed[safe_mask, 2, 0],
  64. dcm_transformed[safe_mask, 2, 1])
  65. if extrinsic:
  66. # For extrinsic, set first angle to zero so that after reversal we
  67. # ensure that third angle is zero
  68. # 6a
  69. angles[~safe_mask, 0] = 0
  70. # 6b
  71. angles[~safe1, 2] = np.arctan2(
  72. dcm_transformed[~safe1, 1, 0] - dcm_transformed[~safe1, 0, 1],
  73. dcm_transformed[~safe1, 0, 0] + dcm_transformed[~safe1, 1, 1]
  74. )
  75. # 6c
  76. angles[~safe2, 2] = -np.arctan2(
  77. dcm_transformed[~safe2, 1, 0] + dcm_transformed[~safe2, 0, 1],
  78. dcm_transformed[~safe2, 0, 0] - dcm_transformed[~safe2, 1, 1]
  79. )
  80. else:
  81. # For instrinsic, set third angle to zero
  82. # 6a
  83. angles[~safe_mask, 2] = 0
  84. # 6b
  85. angles[~safe1, 0] = np.arctan2(
  86. dcm_transformed[~safe1, 1, 0] - dcm_transformed[~safe1, 0, 1],
  87. dcm_transformed[~safe1, 0, 0] + dcm_transformed[~safe1, 1, 1]
  88. )
  89. # 6c
  90. angles[~safe2, 0] = np.arctan2(
  91. dcm_transformed[~safe2, 1, 0] + dcm_transformed[~safe2, 0, 1],
  92. dcm_transformed[~safe2, 0, 0] - dcm_transformed[~safe2, 1, 1]
  93. )
  94. # Step 7
  95. if seq[0] == seq[2]:
  96. # lambda = 0, so we can only ensure angle2 -> [0, pi]
  97. adjust_mask = np.logical_or(angles[:, 1] < 0, angles[:, 1] > np.pi)
  98. else:
  99. # lambda = + or - pi/2, so we can ensure angle2 -> [-pi/2, pi/2]
  100. adjust_mask = np.logical_or(angles[:, 1] < -np.pi / 2,
  101. angles[:, 1] > np.pi / 2)
  102. # Dont adjust gimbal locked angle sequences
  103. adjust_mask = np.logical_and(adjust_mask, safe_mask)
  104. angles[adjust_mask, 0] += np.pi
  105. angles[adjust_mask, 1] = 2 * offset - angles[adjust_mask, 1]
  106. angles[adjust_mask, 2] -= np.pi
  107. angles[angles < -np.pi] += 2 * np.pi
  108. angles[angles > np.pi] -= 2 * np.pi
  109. # Step 8
  110. if not np.all(safe_mask):
  111. warnings.warn("Gimbal lock detected. Setting third angle to zero since"
  112. " it is not possible to uniquely determine all angles.")
  113. # Reverse role of extrinsic and intrinsic rotations, but let third angle be
  114. # zero for gimbal locked cases
  115. if extrinsic:
  116. angles = angles[:, ::-1]
  117. return angles
  118. def _make_elementary_quat(axis, angles):
  119. quat = np.zeros((angles.shape[0], 4))
  120. quat[:, 3] = np.cos(angles / 2)
  121. quat[:, _AXIS_TO_IND[axis]] = np.sin(angles / 2)
  122. return quat
  123. def _compose_quat(p, q):
  124. product = np.empty((max(p.shape[0], q.shape[0]), 4))
  125. product[:, 3] = p[:, 3] * q[:, 3] - np.sum(p[:, :3] * q[:, :3], axis=1)
  126. product[:, :3] = (p[:, None, 3] * q[:, :3] + q[:, None, 3] * p[:, :3] +
  127. np.cross(p[:, :3], q[:, :3]))
  128. return product
  129. def _elementary_quat_compose(seq, angles, intrinsic=False):
  130. result = _make_elementary_quat(seq[0], angles[:, 0])
  131. for idx, axis in enumerate(seq[1:], start=1):
  132. if intrinsic:
  133. result = _compose_quat(
  134. result,
  135. _make_elementary_quat(axis, angles[:, idx]))
  136. else:
  137. result = _compose_quat(
  138. _make_elementary_quat(axis, angles[:, idx]),
  139. result)
  140. return result
  141. class Rotation(object):
  142. """Rotation in 3 dimensions.
  143. This class provides an interface to initialize from and represent rotations
  144. with:
  145. - Quaternions
  146. - Direction Cosine Matrices
  147. - Rotation Vectors
  148. - Euler angles
  149. The following operations on rotations are supported:
  150. - Application on vectors
  151. - Rotation Composition
  152. - Rotation Inversion
  153. - Rotation Indexing
  154. Indexing within a rotation is supported since multiple rotation transforms
  155. can be stored within a single `Rotation` instance.
  156. To create `Rotation` objects use `from_...` classmethods, `__init__` is not
  157. supposed to be used directly.
  158. Methods
  159. -------
  160. __len__
  161. from_quat
  162. from_dcm
  163. from_rotvec
  164. from_euler
  165. as_quat
  166. as_dcm
  167. as_rotvec
  168. as_euler
  169. apply
  170. __mul__
  171. inv
  172. __getitem__
  173. random
  174. Examples
  175. --------
  176. >>> from scipy.spatial.transform import Rotation as R
  177. A `Rotation` instance can be initialized in any of the above formats and
  178. converted to any of the others. The underlying object is independent of the
  179. representation used for initialization.
  180. Consider a counter-clockwise rotation of 90 degrees about the z-axis. This
  181. corresponds to the following quaternion (in scalar-last format):
  182. >>> r = R.from_quat([0, 0, np.sin(np.pi/4), np.cos(np.pi/4)])
  183. The rotation can be expressed in any of the other formats:
  184. >>> r.as_dcm()
  185. array([[ 2.22044605e-16, -1.00000000e+00, 0.00000000e+00],
  186. [ 1.00000000e+00, 2.22044605e-16, 0.00000000e+00],
  187. [ 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]])
  188. >>> r.as_rotvec()
  189. array([0. , 0. , 1.57079633])
  190. >>> r.as_euler('zyx', degrees=True)
  191. array([90., 0., 0.])
  192. The same rotation can be initialized using a direction cosine matrix:
  193. >>> r = R.from_dcm(np.array([
  194. ... [0, -1, 0],
  195. ... [1, 0, 0],
  196. ... [0, 0, 1]]))
  197. Representation in other formats:
  198. >>> r.as_quat()
  199. array([0. , 0. , 0.70710678, 0.70710678])
  200. >>> r.as_rotvec()
  201. array([0. , 0. , 1.57079633])
  202. >>> r.as_euler('zyx', degrees=True)
  203. array([90., 0., 0.])
  204. The rotation vector corresponding to this rotation is given by:
  205. >>> r = R.from_rotvec(np.pi/2 * np.array([0, 0, 1]))
  206. Representation in other formats:
  207. >>> r.as_quat()
  208. array([0. , 0. , 0.70710678, 0.70710678])
  209. >>> r.as_dcm()
  210. array([[ 2.22044605e-16, -1.00000000e+00, 0.00000000e+00],
  211. [ 1.00000000e+00, 2.22044605e-16, 0.00000000e+00],
  212. [ 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]])
  213. >>> r.as_euler('zyx', degrees=True)
  214. array([90., 0., 0.])
  215. The `from_euler` function is quite flexible in the range of input formats
  216. it supports. Here we initialize a single rotation about a single axis:
  217. >>> r = R.from_euler('z', 90, degrees=True)
  218. Again, the object is representation independent and can be converted to any
  219. other format:
  220. >>> r.as_quat()
  221. array([0. , 0. , 0.70710678, 0.70710678])
  222. >>> r.as_dcm()
  223. array([[ 2.22044605e-16, -1.00000000e+00, 0.00000000e+00],
  224. [ 1.00000000e+00, 2.22044605e-16, 0.00000000e+00],
  225. [ 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]])
  226. >>> r.as_rotvec()
  227. array([0. , 0. , 1.57079633])
  228. It is also possible to initialize multiple rotations in a single instance
  229. using any of the `from_...` functions. Here we initialize a stack of 3
  230. rotations using the `from_euler` function:
  231. >>> r = R.from_euler('zyx', [
  232. ... [90, 0, 0],
  233. ... [0, 45, 0],
  234. ... [45, 60, 30]], degrees=True)
  235. The other representations also now return a stack of 3 rotations. For
  236. example:
  237. >>> r.as_quat()
  238. array([[0. , 0. , 0.70710678, 0.70710678],
  239. [0. , 0.38268343, 0. , 0.92387953],
  240. [0.39190384, 0.36042341, 0.43967974, 0.72331741]])
  241. Applying the above rotations onto a vector:
  242. >>> v = [1, 2, 3]
  243. >>> r.apply(v)
  244. array([[-2. , 1. , 3. ],
  245. [ 2.82842712, 2. , 1.41421356],
  246. [ 2.24452282, 0.78093109, 2.89002836]])
  247. A `Rotation` instance can be indexed and sliced as if it were a single
  248. 1D array or list:
  249. >>> r.as_quat()
  250. array([[0. , 0. , 0.70710678, 0.70710678],
  251. [0. , 0.38268343, 0. , 0.92387953],
  252. [0.39190384, 0.36042341, 0.43967974, 0.72331741]])
  253. >>> p = r[0]
  254. >>> p.as_dcm()
  255. array([[ 2.22044605e-16, -1.00000000e+00, 0.00000000e+00],
  256. [ 1.00000000e+00, 2.22044605e-16, 0.00000000e+00],
  257. [ 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]])
  258. >>> q = r[1:3]
  259. >>> q.as_quat()
  260. array([[0. , 0.38268343, 0. , 0.92387953],
  261. [0.39190384, 0.36042341, 0.43967974, 0.72331741]])
  262. Multiple rotations can be composed using the `*` operator:
  263. >>> r1 = R.from_euler('z', 90, degrees=True)
  264. >>> r2 = R.from_rotvec([np.pi/4, 0, 0])
  265. >>> v = [1, 2, 3]
  266. >>> r2.apply(r1.apply(v))
  267. array([-2. , -1.41421356, 2.82842712])
  268. >>> r3 = r2 * r1 # Note the order
  269. >>> r3.apply(v)
  270. array([-2. , -1.41421356, 2.82842712])
  271. Finally, it is also possible to invert rotations:
  272. >>> r1 = R.from_euler('z', [90, 45], degrees=True)
  273. >>> r2 = r1.inv()
  274. >>> r2.as_euler('zyx', degrees=True)
  275. array([[-90., 0., 0.],
  276. [-45., 0., 0.]])
  277. These examples serve as an overview into the `Rotation` class and highlight
  278. major functionalities. For more thorough examples of the range of input and
  279. output formats supported, consult the individual method's examples.
  280. """
  281. def __init__(self, quat, normalized=False, copy=True):
  282. self._single = False
  283. quat = np.asarray(quat, dtype=float)
  284. if quat.ndim not in [1, 2] or quat.shape[-1] != 4:
  285. raise ValueError("Expected `quat` to have shape (4,) or (N x 4), "
  286. "got {}.".format(quat.shape))
  287. # If a single quaternion is given, convert it to a 2D 1 x 4 matrix but
  288. # set self._single to True so that we can return appropriate objects
  289. # in the `to_...` methods
  290. if quat.shape == (4,):
  291. quat = quat[None, :]
  292. self._single = True
  293. if normalized:
  294. self._quat = quat.copy() if copy else quat
  295. else:
  296. self._quat = quat.copy()
  297. norms = scipy.linalg.norm(quat, axis=1)
  298. zero_norms = norms == 0
  299. if zero_norms.any():
  300. raise ValueError("Found zero norm quaternions in `quat`.")
  301. # Ensure norm is broadcasted along each column.
  302. self._quat[~zero_norms] /= norms[~zero_norms][:, None]
  303. def __len__(self):
  304. """Number of rotations contained in this object.
  305. Multiple rotations can be stored in a single instance.
  306. Returns
  307. -------
  308. length : int
  309. Number of rotations stored in object.
  310. """
  311. return self._quat.shape[0]
  312. @classmethod
  313. def from_quat(cls, quat, normalized=False):
  314. """Initialize from quaternions.
  315. 3D rotations can be represented using unit-norm quaternions [1]_.
  316. Parameters
  317. ----------
  318. quat : array_like, shape (N, 4) or (4,)
  319. Each row is a (possibly non-unit norm) quaternion in scalar-last
  320. (x, y, z, w) format.
  321. normalized : boolean, optional
  322. If `False`, input quaternions are normalized to unit norm before
  323. being stored. If `True`, quaternions are assumed to already have
  324. unit norm and are stored as given. Default is `False`.
  325. Returns
  326. -------
  327. rotation : `Rotation` instance
  328. Object containing the rotations represented by input quaternions.
  329. References
  330. ----------
  331. .. [1] `Quaternions and Spatial Rotation
  332. <https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation>`_
  333. Examples
  334. --------
  335. >>> from scipy.spatial.transform import Rotation as R
  336. Initialize a single rotation:
  337. >>> r = R.from_quat([1, 0, 0, 0])
  338. >>> r.as_quat()
  339. array([1., 0., 0., 0.])
  340. >>> r.as_quat().shape
  341. (4,)
  342. Initialize multiple rotations in a single object:
  343. >>> r = R.from_quat([
  344. ... [1, 0, 0, 0],
  345. ... [0, 0, 0, 1]
  346. ... ])
  347. >>> r.as_quat()
  348. array([[1., 0., 0., 0.],
  349. [0., 0., 0., 1.]])
  350. >>> r.as_quat().shape
  351. (2, 4)
  352. It is also possible to have a stack of a single rotation:
  353. >>> r = R.from_quat([[0, 0, 0, 1]])
  354. >>> r.as_quat()
  355. array([[0., 0., 0., 1.]])
  356. >>> r.as_quat().shape
  357. (1, 4)
  358. By default, quaternions are normalized before initialization.
  359. >>> r = R.from_quat([0, 0, 1, 1])
  360. >>> r.as_quat()
  361. array([0. , 0. , 0.70710678, 0.70710678])
  362. If unit norms are ensured, skip the normalization step.
  363. >>> r = R.from_quat([0, 0, 1, 0], normalized=True)
  364. >>> r.as_quat()
  365. array([0., 0., 1., 0.])
  366. """
  367. return cls(quat, normalized)
  368. @classmethod
  369. def from_dcm(cls, dcm):
  370. """Initialize from direction cosine matrices.
  371. Rotations in 3 dimensions can be represented using 3 x 3 proper
  372. orthogonal matrices [1]_. If the input is not proper orthogonal,
  373. an approximation is created using the method described in [2]_.
  374. Parameters
  375. ----------
  376. dcm : array_like, shape (N, 3, 3) or (3, 3)
  377. A single matrix or a stack of matrices, where `dcm[i]` is the i-th
  378. matrix.
  379. Returns
  380. -------
  381. rotation : `Rotation` instance
  382. Object containing the rotations represented by the input direction
  383. cosine matrices.
  384. References
  385. ----------
  386. .. [1] `Direction Cosine Matrix
  387. <https://en.wikipedia.org/wiki/Rotation_matrix#In_three_dimensions>`_
  388. .. [2] F. Landis Markley, `Unit Quaternion from Rotation Matrix
  389. <https://arc.aiaa.org/doi/abs/10.2514/1.31730>`_
  390. Examples
  391. --------
  392. >>> from scipy.spatial.transform import Rotation as R
  393. Initialize a single rotation:
  394. >>> r = R.from_dcm([
  395. ... [0, -1, 0],
  396. ... [1, 0, 0],
  397. ... [0, 0, 1]])
  398. >>> r.as_dcm().shape
  399. (3, 3)
  400. Initialize multiple rotations in a single object:
  401. >>> r = R.from_dcm([
  402. ... [
  403. ... [0, -1, 0],
  404. ... [1, 0, 0],
  405. ... [0, 0, 1],
  406. ... ],
  407. ... [
  408. ... [1, 0, 0],
  409. ... [0, 0, -1],
  410. ... [0, 1, 0],
  411. ... ]])
  412. >>> r.as_dcm().shape
  413. (2, 3, 3)
  414. If input matrices are not special orthogonal (orthogonal with
  415. determinant equal to +1), then a special orthogonal estimate is stored:
  416. >>> a = np.array([
  417. ... [0, -0.5, 0],
  418. ... [0.5, 0, 0],
  419. ... [0, 0, 0.5]])
  420. >>> np.linalg.det(a)
  421. 0.12500000000000003
  422. >>> r = R.from_dcm(a)
  423. >>> dcm = r.as_dcm()
  424. >>> dcm
  425. array([[-0.38461538, -0.92307692, 0. ],
  426. [ 0.92307692, -0.38461538, 0. ],
  427. [ 0. , 0. , 1. ]])
  428. >>> np.linalg.det(dcm)
  429. 1.0000000000000002
  430. It is also possible to have a stack containing a single rotation:
  431. >>> r = R.from_dcm([[
  432. ... [0, -1, 0],
  433. ... [1, 0, 0],
  434. ... [0, 0, 1]]])
  435. >>> r.as_dcm()
  436. array([[[ 0., -1., 0.],
  437. [ 1., 0., 0.],
  438. [ 0., 0., 1.]]])
  439. >>> r.as_dcm().shape
  440. (1, 3, 3)
  441. """
  442. is_single = False
  443. dcm = np.asarray(dcm, dtype=float)
  444. if dcm.ndim not in [2, 3] or dcm.shape[-2:] != (3, 3):
  445. raise ValueError("Expected `dcm` to have shape (3, 3) or "
  446. "(N, 3, 3), got {}".format(dcm.shape))
  447. # If a single dcm is given, convert it to 3D 1 x 3 x 3 matrix but set
  448. # self._single to True so that we can return appropriate objects in
  449. # the `to_...` methods
  450. if dcm.shape == (3, 3):
  451. dcm = dcm.reshape((1, 3, 3))
  452. is_single = True
  453. num_rotations = dcm.shape[0]
  454. decision_matrix = np.empty((num_rotations, 4))
  455. decision_matrix[:, :3] = dcm.diagonal(axis1=1, axis2=2)
  456. decision_matrix[:, -1] = decision_matrix[:, :3].sum(axis=1)
  457. choices = decision_matrix.argmax(axis=1)
  458. quat = np.empty((num_rotations, 4))
  459. ind = np.nonzero(choices != 3)[0]
  460. i = choices[ind]
  461. j = (i + 1) % 3
  462. k = (j + 1) % 3
  463. quat[ind, i] = 1 - decision_matrix[ind, -1] + 2 * dcm[ind, i, i]
  464. quat[ind, j] = dcm[ind, j, i] + dcm[ind, i, j]
  465. quat[ind, k] = dcm[ind, k, i] + dcm[ind, i, k]
  466. quat[ind, 3] = dcm[ind, k, j] - dcm[ind, j, k]
  467. ind = np.nonzero(choices == 3)[0]
  468. quat[ind, 0] = dcm[ind, 2, 1] - dcm[ind, 1, 2]
  469. quat[ind, 1] = dcm[ind, 0, 2] - dcm[ind, 2, 0]
  470. quat[ind, 2] = dcm[ind, 1, 0] - dcm[ind, 0, 1]
  471. quat[ind, 3] = 1 + decision_matrix[ind, -1]
  472. quat /= np.linalg.norm(quat, axis=1)[:, None]
  473. if is_single:
  474. return cls(quat[0], normalized=True, copy=False)
  475. else:
  476. return cls(quat, normalized=True, copy=False)
  477. @classmethod
  478. def from_rotvec(cls, rotvec):
  479. """Initialize from rotation vectors.
  480. A rotation vector is a 3 dimensional vector which is co-directional to
  481. the axis of rotation and whose norm gives the angle of rotation (in
  482. radians) [1]_.
  483. Parameters
  484. ----------
  485. rotvec : array_like, shape (N, 3) or (3,)
  486. A single vector or a stack of vectors, where `rot_vec[i]` gives
  487. the ith rotation vector.
  488. Returns
  489. -------
  490. rotation : `Rotation` instance
  491. Object containing the rotations represented by input rotation
  492. vectors.
  493. References
  494. ----------
  495. .. [1] `Rotation Vectors
  496. <https://en.wikipedia.org/wiki/Axis%E2%80%93angle_representation#Rotation_vector>`_
  497. Examples
  498. --------
  499. >>> from scipy.spatial.transform import Rotation as R
  500. Initialize a single rotation:
  501. >>> r = R.from_rotvec(np.pi/2 * np.array([0, 0, 1]))
  502. >>> r.as_rotvec()
  503. array([0. , 0. , 1.57079633])
  504. >>> r.as_rotvec().shape
  505. (3,)
  506. Initialize multiple rotations in one object:
  507. >>> r = R.from_rotvec([
  508. ... [0, 0, np.pi/2],
  509. ... [np.pi/2, 0, 0]])
  510. >>> r.as_rotvec()
  511. array([[0. , 0. , 1.57079633],
  512. [1.57079633, 0. , 0. ]])
  513. >>> r.as_rotvec().shape
  514. (2, 3)
  515. It is also possible to have a stack of a single rotaton:
  516. >>> r = R.from_rotvec([[0, 0, np.pi/2]])
  517. >>> r.as_rotvec().shape
  518. (1, 3)
  519. """
  520. is_single = False
  521. rotvec = np.asarray(rotvec, dtype=float)
  522. if rotvec.ndim not in [1, 2] or rotvec.shape[-1] != 3:
  523. raise ValueError("Expected `rot_vec` to have shape (3,) "
  524. "or (N, 3), got {}".format(rotvec.shape))
  525. # If a single vector is given, convert it to a 2D 1 x 3 matrix but
  526. # set self._single to True so that we can return appropriate objects
  527. # in the `as_...` methods
  528. if rotvec.shape == (3,):
  529. rotvec = rotvec[None, :]
  530. is_single = True
  531. num_rotations = rotvec.shape[0]
  532. norms = np.linalg.norm(rotvec, axis=1)
  533. small_angle = (norms <= 1e-3)
  534. large_angle = ~small_angle
  535. scale = np.empty(num_rotations)
  536. scale[small_angle] = (0.5 - norms[small_angle] ** 2 / 48 +
  537. norms[small_angle] ** 4 / 3840)
  538. scale[large_angle] = (np.sin(norms[large_angle] / 2) /
  539. norms[large_angle])
  540. quat = np.empty((num_rotations, 4))
  541. quat[:, :3] = scale[:, None] * rotvec
  542. quat[:, 3] = np.cos(norms / 2)
  543. if is_single:
  544. return cls(quat[0], normalized=True, copy=False)
  545. else:
  546. return cls(quat, normalized=True, copy=False)
  547. @classmethod
  548. def from_euler(cls, seq, angles, degrees=False):
  549. """Initialize from Euler angles.
  550. Rotations in 3 dimensions can be represented by a sequece of 3
  551. rotations around a sequence of axes. In theory, any three axes spanning
  552. the 3D Euclidean space are enough. In practice the axes of rotation are
  553. chosen to be the basis vectors.
  554. The three rotations can either be in a global frame of reference
  555. (extrinsic) or in a body centred frame of refernce (intrinsic), which
  556. is attached to, and moves with, the object under rotation [1]_.
  557. Parameters
  558. ----------
  559. seq : string
  560. Specifies sequence of axes for rotations. Up to 3 characters
  561. belonging to the set {'X', 'Y', 'Z'} for intrinsic rotations, or
  562. {'x', 'y', 'z'} for extrinsic rotations. Extrinsic and intrinsic
  563. rotations cannot be mixed in one function call.
  564. angles : float or array_like, shape (N,) or (N, [1 or 2 or 3])
  565. Euler angles specified in radians (`degrees` is False) or degrees
  566. (`degrees` is True).
  567. For a single character `seq`, `angles` can be:
  568. - a single value
  569. - array_like with shape (N,), where each `angle[i]`
  570. corresponds to a single rotation
  571. - array_like with shape (N, 1), where each `angle[i, 0]`
  572. corresponds to a single rotation
  573. For 2- and 3-character wide `seq`, `angles` can be:
  574. - array_like with shape (W,) where `W` is the width of
  575. `seq`, which corresponds to a single rotation with `W` axes
  576. - array_like with shape (N, W) where each `angle[i]`
  577. corresponds to a sequence of Euler angles describing a single
  578. rotation
  579. degrees : boolean, optional
  580. If True, then the given angles are assumed to be in degrees.
  581. Default is False.
  582. Returns
  583. -------
  584. rotation : `Rotation` instance
  585. Object containing the rotation represented by the sequence of
  586. rotations around given axes with given angles.
  587. References
  588. ----------
  589. .. [1] `Euler angle definitions
  590. <https://en.wikipedia.org/wiki/Euler_angles#Definition_by_intrinsic_rotations>`_
  591. Examples
  592. --------
  593. >>> from scipy.spatial.transform import Rotation as R
  594. Initialize a single rotation along a single axis:
  595. >>> r = R.from_euler('x', 90, degrees=True)
  596. >>> r.as_quat().shape
  597. (4,)
  598. Initialize a single rotation with a given axis sequence:
  599. >>> r = R.from_euler('zyx', [90, 45, 30], degrees=True)
  600. >>> r.as_quat().shape
  601. (4,)
  602. Initialize a stack with a single rotation around a single axis:
  603. >>> r = R.from_euler('x', [90], degrees=True)
  604. >>> r.as_quat().shape
  605. (1, 4)
  606. Initialize a stack with a single rotation with an axis sequence:
  607. >>> r = R.from_euler('zyx', [[90, 45, 30]], degrees=True)
  608. >>> r.as_quat().shape
  609. (1, 4)
  610. Initialize multiple elementary rotations in one object:
  611. >>> r = R.from_euler('x', [90, 45, 30], degrees=True)
  612. >>> r.as_quat().shape
  613. (3, 4)
  614. Initialize multiple rotations in one object:
  615. >>> r = R.from_euler('zyx', [[90, 45, 30], [35, 45, 90]], degrees=True)
  616. >>> r.as_quat().shape
  617. (2, 4)
  618. """
  619. num_axes = len(seq)
  620. if num_axes < 1 or num_axes > 3:
  621. raise ValueError("Expected axis specification to be a non-empty "
  622. "string of upto 3 characters, got {}".format(seq))
  623. intrinsic = (re.match(r'^[XYZ]{1,3}$', seq) is not None)
  624. extrinsic = (re.match(r'^[xyz]{1,3}$', seq) is not None)
  625. if not (intrinsic or extrinsic):
  626. raise ValueError("Expected axes from `seq` to be from ['x', 'y', "
  627. "'z'] or ['X', 'Y', 'Z'], got {}".format(seq))
  628. if any(seq[i] == seq[i+1] for i in range(num_axes - 1)):
  629. raise ValueError("Expected consecutive axes to be different, "
  630. "got {}".format(seq))
  631. seq = seq.lower()
  632. angles = np.asarray(angles, dtype=float)
  633. if degrees:
  634. angles = np.deg2rad(angles)
  635. is_single = False
  636. # Prepare angles to have shape (num_rot, num_axes)
  637. if num_axes == 1:
  638. if angles.ndim == 0:
  639. # (1, 1)
  640. angles = angles.reshape((1, 1))
  641. is_single = True
  642. elif angles.ndim == 1:
  643. # (N, 1)
  644. angles = angles[:, None]
  645. elif angles.ndim == 2 and angles.shape[-1] != 1:
  646. raise ValueError("Expected `angles` parameter to have shape "
  647. "(N, 1), got {}.".format(angles.shape))
  648. elif angles.ndim > 2:
  649. raise ValueError("Expected float, 1D array, or 2D array for "
  650. "parameter `angles` corresponding to `seq`, "
  651. "got shape {}.".format(angles.shape))
  652. else: # 2 or 3 axes
  653. if angles.ndim not in [1, 2] or angles.shape[-1] != num_axes:
  654. raise ValueError("Expected `angles` to be at most "
  655. "2-dimensional with width equal to number "
  656. "of axes specified, got {} for shape").format(
  657. angles.shape)
  658. if angles.ndim == 1:
  659. # (1, num_axes)
  660. angles = angles[None, :]
  661. is_single = True
  662. # By now angles should have shape (num_rot, num_axes)
  663. # sanity check
  664. if angles.ndim != 2 or angles.shape[-1] != num_axes:
  665. raise ValueError("Expected angles to have shape (num_rotations, "
  666. "num_axes), got {}.".format(angles.shape))
  667. quat = _elementary_quat_compose(seq, angles, intrinsic)
  668. return cls(quat[0] if is_single else quat, normalized=True, copy=False)
  669. def as_quat(self):
  670. """Represent as quaternions.
  671. Rotations in 3 dimensions can be represented using unit norm
  672. quaternions [1]_. The mapping from quaternions to rotations is
  673. two-to-one, i.e. quaternions `q` and `-q`, where `-q` simply reverses
  674. the sign of each component, represent the same spatial rotation.
  675. Returns
  676. -------
  677. quat : `numpy.ndarray`, shape (4,) or (N, 4)
  678. Shape depends on shape of inputs used for initialization.
  679. References
  680. ----------
  681. .. [1] `Quaternions and Spatial Rotation
  682. <https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation>`_
  683. Examples
  684. --------
  685. >>> from scipy.spatial.transform import Rotation as R
  686. Represent a single rotation:
  687. >>> r = R.from_dcm([
  688. ... [0, -1, 0],
  689. ... [1, 0, 0],
  690. ... [0, 0, 1]])
  691. >>> r.as_quat()
  692. array([0. , 0. , 0.70710678, 0.70710678])
  693. >>> r.as_quat().shape
  694. (4,)
  695. Represent a stack with a single rotation:
  696. >>> r = R.from_quat([[0, 0, 0, 1]])
  697. >>> r.as_quat().shape
  698. (1, 4)
  699. Represent multiple rotaions in a single object:
  700. >>> r = R.from_rotvec([[np.pi, 0, 0], [0, 0, np.pi/2]])
  701. >>> r.as_quat().shape
  702. (2, 4)
  703. """
  704. if self._single:
  705. return self._quat[0].copy()
  706. else:
  707. return self._quat.copy()
  708. def as_dcm(self):
  709. """Represent as direction cosine matrices.
  710. 3D rotations can be represented using direction cosine matrices, which
  711. are 3 x 3 real orthogonal matrices with determinant equal to +1 [1]_.
  712. Returns
  713. -------
  714. dcm : `numpy.ndarray`, shape (3, 3) or (N, 3, 3)
  715. Shape depends on shape of inputs used for initialization.
  716. References
  717. ----------
  718. .. [1] `Direction Cosine Matrix
  719. <https://en.wikipedia.org/wiki/Rotation_matrix#In_three_dimensions>`_
  720. Examples
  721. --------
  722. >>> from scipy.spatial.transform import Rotation as R
  723. Represent a single rotation:
  724. >>> r = R.from_rotvec([0, 0, np.pi/2])
  725. >>> r.as_dcm()
  726. array([[ 2.22044605e-16, -1.00000000e+00, 0.00000000e+00],
  727. [ 1.00000000e+00, 2.22044605e-16, 0.00000000e+00],
  728. [ 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]])
  729. >>> r.as_dcm().shape
  730. (3, 3)
  731. Represent a stack with a single rotation:
  732. >>> r = R.from_quat([[1, 1, 0, 0]])
  733. >>> r.as_dcm()
  734. array([[[ 0., 1., 0.],
  735. [ 1., 0., 0.],
  736. [ 0., 0., -1.]]])
  737. >>> r.as_dcm().shape
  738. (1, 3, 3)
  739. Represent multiple rotations:
  740. >>> r = R.from_rotvec([[np.pi/2, 0, 0], [0, 0, np.pi/2]])
  741. >>> r.as_dcm()
  742. array([[[ 1.00000000e+00, 0.00000000e+00, 0.00000000e+00],
  743. [ 0.00000000e+00, 2.22044605e-16, -1.00000000e+00],
  744. [ 0.00000000e+00, 1.00000000e+00, 2.22044605e-16]],
  745. [[ 2.22044605e-16, -1.00000000e+00, 0.00000000e+00],
  746. [ 1.00000000e+00, 2.22044605e-16, 0.00000000e+00],
  747. [ 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]]])
  748. >>> r.as_dcm().shape
  749. (2, 3, 3)
  750. """
  751. x = self._quat[:, 0]
  752. y = self._quat[:, 1]
  753. z = self._quat[:, 2]
  754. w = self._quat[:, 3]
  755. x2 = x * x
  756. y2 = y * y
  757. z2 = z * z
  758. w2 = w * w
  759. xy = x * y
  760. zw = z * w
  761. xz = x * z
  762. yw = y * w
  763. yz = y * z
  764. xw = x * w
  765. num_rotations = len(self)
  766. dcm = np.empty((num_rotations, 3, 3))
  767. dcm[:, 0, 0] = x2 - y2 - z2 + w2
  768. dcm[:, 1, 0] = 2 * (xy + zw)
  769. dcm[:, 2, 0] = 2 * (xz - yw)
  770. dcm[:, 0, 1] = 2 * (xy - zw)
  771. dcm[:, 1, 1] = - x2 + y2 - z2 + w2
  772. dcm[:, 2, 1] = 2 * (yz + xw)
  773. dcm[:, 0, 2] = 2 * (xz + yw)
  774. dcm[:, 1, 2] = 2 * (yz - xw)
  775. dcm[:, 2, 2] = - x2 - y2 + z2 + w2
  776. if self._single:
  777. return dcm[0]
  778. else:
  779. return dcm
  780. def as_rotvec(self):
  781. """Represent as rotation vectors.
  782. A rotation vector is a 3 dimensional vector which is co-directional to
  783. the axis of rotation and whose norm gives the angle of rotation (in
  784. radians) [1]_.
  785. Returns
  786. -------
  787. rotvec : `numpy.ndarray`, shape (3,) or (N, 3)
  788. Shape depends on shape of inputs used for initialization.
  789. References
  790. ----------
  791. .. [1] `Rotation Vectors
  792. <https://en.wikipedia.org/wiki/Axis%E2%80%93angle_representation#Rotation_vector>`_
  793. Examples
  794. --------
  795. >>> from scipy.spatial.transform import Rotation as R
  796. Represent a single rotation:
  797. >>> r = R.from_euler('z', 90, degrees=True)
  798. >>> r.as_rotvec()
  799. array([0. , 0. , 1.57079633])
  800. >>> r.as_rotvec().shape
  801. (3,)
  802. Represent a stack with a single rotation:
  803. >>> r = R.from_quat([[0, 0, 1, 1]])
  804. >>> r.as_rotvec()
  805. array([[0. , 0. , 1.57079633]])
  806. >>> r.as_rotvec().shape
  807. (1, 3)
  808. Represent multiple rotations in a single object:
  809. >>> r = R.from_quat([[0, 0, 1, 1], [1, 1, 0, 1]])
  810. >>> r.as_rotvec()
  811. array([[0. , 0. , 1.57079633],
  812. [1.35102172, 1.35102172, 0. ]])
  813. >>> r.as_rotvec().shape
  814. (2, 3)
  815. """
  816. quat = self._quat.copy()
  817. # w > 0 to ensure 0 <= angle <= pi
  818. quat[quat[:, 3] < 0] *= -1
  819. angle = 2 * np.arctan2(np.linalg.norm(quat[:, :3], axis=1), quat[:, 3])
  820. small_angle = (angle <= 1e-3)
  821. large_angle = ~small_angle
  822. num_rotations = len(self)
  823. scale = np.empty(num_rotations)
  824. scale[small_angle] = (2 + angle[small_angle] ** 2 / 12 +
  825. 7 * angle[small_angle] ** 4 / 2880)
  826. scale[large_angle] = (angle[large_angle] /
  827. np.sin(angle[large_angle] / 2))
  828. rotvec = scale[:, None] * quat[:, :3]
  829. if self._single:
  830. return rotvec[0]
  831. else:
  832. return rotvec
  833. def as_euler(self, seq, degrees=False):
  834. """Represent as Euler angles.
  835. Any orientation can be expressed as a composition of 3 elementary
  836. rotations. Once the axis sequence has been chosen, Euler angles define
  837. the angle of rotation around each respective axis [1]_.
  838. The algorithm from [2]_ has been used to calculate Euler angles for the
  839. rotation about a given sequence of axes.
  840. Euler angles suffer from the problem of gimbal lock [3]_, where the
  841. representation loses a degree of freedom and it is not possible to
  842. determine the first and third angles uniquely. In this case,
  843. a warning is raised, and the third angle is set to zero. Note however
  844. that the returned angles still represent the correct rotation.
  845. Parameters
  846. ----------
  847. seq : string, length 3
  848. 3 characters belonging to the set {'X', 'Y', 'Z'} for intrinsic
  849. rotations, or {'x', 'y', 'z'} for extrinsic rotations [1]_.
  850. Adjacent axes cannot be the same.
  851. Extrinsic and intrinsic rotations cannot be mixed in one function
  852. call.
  853. degrees : boolean, optional
  854. Returned angles are in degrees if this flag is True, else they are
  855. in radians. Default is False.
  856. Returns
  857. -------
  858. angles : `numpy.ndarray`, shape (3,) or (N, 3)
  859. Shape depends on shape of inputs used to initialize object.
  860. The returned angles are in the range:
  861. - First angle belongs to [-180, 180] degrees (both inclusive)
  862. - Third angle belongs to [-180, 180] degrees (both inclusive)
  863. - Second angle belongs to:
  864. - [-90, 90] degrees if all axes are different (like xyz)
  865. - [0, 180] degrees if first and third axes are the same
  866. (like zxz)
  867. References
  868. ----------
  869. .. [1] `Euler angle definitions
  870. <https://en.wikipedia.org/wiki/Euler_angles#Definition_by_intrinsic_rotations>`_
  871. .. [2] Malcolm D. Shuster, F. Landis Markley
  872. `General Formula for Euler Angles
  873. <https://arc.aiaa.org/doi/abs/10.2514/1.16622>`_
  874. .. [3] `Gimbal lock
  875. <https://en.wikipedia.org/wiki/Gimbal_lock#In_applied_mathematics>`_
  876. Examples
  877. --------
  878. >>> from scipy.spatial.transform import Rotation as R
  879. Represent a single rotation:
  880. >>> r = R.from_rotvec([0, 0, np.pi/2])
  881. >>> r.as_euler('zxy', degrees=True)
  882. array([90., 0., 0.])
  883. >>> r.as_euler('zxy', degrees=True).shape
  884. (3,)
  885. Represent a stack of single rotation:
  886. >>> r = R.from_rotvec([[0, 0, np.pi/2]])
  887. >>> r.as_euler('zxy', degrees=True)
  888. array([[90., 0., 0.]])
  889. >>> r.as_euler('zxy', degrees=True).shape
  890. (1, 3)
  891. Represent multiple rotations in a single object:
  892. >>> r = R.from_rotvec([
  893. ... [0, 0, np.pi/2],
  894. ... [0, -np.pi/3, 0],
  895. ... [np.pi/4, 0, 0]])
  896. >>> r.as_euler('zxy', degrees=True)
  897. array([[ 90., 0., 0.],
  898. [ 0., 0., -60.],
  899. [ 0., 45., 0.]])
  900. >>> r.as_euler('zxy', degrees=True).shape
  901. (3, 3)
  902. """
  903. if len(seq) != 3:
  904. raise ValueError("Expected 3 axes, got {}.".format(seq))
  905. intrinsic = (re.match(r'^[XYZ]{1,3}$', seq) is not None)
  906. extrinsic = (re.match(r'^[xyz]{1,3}$', seq) is not None)
  907. if not (intrinsic or extrinsic):
  908. raise ValueError("Expected axes from `seq` to be from "
  909. "['x', 'y', 'z'] or ['X', 'Y', 'Z'], "
  910. "got {}".format(seq))
  911. if any(seq[i] == seq[i+1] for i in range(2)):
  912. raise ValueError("Expected consecutive axes to be different, "
  913. "got {}".format(seq))
  914. seq = seq.lower()
  915. angles = _compute_euler_from_dcm(self.as_dcm(), seq, extrinsic)
  916. if degrees:
  917. angles = np.rad2deg(angles)
  918. return angles[0] if self._single else angles
  919. def apply(self, vectors, inverse=False):
  920. """Apply this rotation to a set of vectors.
  921. If the original frame rotates to the final frame by this rotation, then
  922. its application to a vector can be seen in two ways:
  923. - As a projection of vector components expressed in the final frame
  924. to the original frame.
  925. - As the physical rotation of a vector being glued to the original
  926. frame as it rotates. In this case the vector components are
  927. expressed in the original frame before and after the rotation.
  928. In terms of DCMs, this application is the same as
  929. `self.as_dcm().dot(vectors)`.
  930. Parameters
  931. ----------
  932. vectors : array_like, shape (3,) or (N, 3)
  933. Each `vectors[i]` represents a vector in 3D space. A single vector
  934. can either be specified with shape `(3, )` or `(1, 3)`. The number
  935. of rotations and number of vectors given must follow standard numpy
  936. broadcasting rules: either one of them equals unity or they both
  937. equal each other.
  938. inverse : boolean, optional
  939. If `inverse` is `True` then the inverse of the rotation(s) is
  940. applied to the input vectors. Default is `False`.
  941. Returns
  942. -------
  943. rotated_vectors : `numpy.ndarray`, shape (3,) or (N, 3)
  944. Result of applying rotation on input vectors.
  945. Shape depends on the following cases:
  946. - If object contains a single rotation (as opposed to a stack
  947. with a single rotation) and a single vector is specified with
  948. shape `(3,)`, then `output` has shape `(3,)`.
  949. - In all other cases, `output` has shape `(N, 3)`, where `N` is
  950. either the number of rotations or vectors.
  951. Examples
  952. --------
  953. >>> from scipy.spatial.transform import Rotation as R
  954. Single rotation applied on a single vector:
  955. >>> vector = np.array([1, 0, 0])
  956. >>> r = R.from_rotvec([0, 0, np.pi/2])
  957. >>> r.as_dcm()
  958. array([[ 2.22044605e-16, -1.00000000e+00, 0.00000000e+00],
  959. [ 1.00000000e+00, 2.22044605e-16, 0.00000000e+00],
  960. [ 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]])
  961. >>> r.apply(vector)
  962. array([2.22044605e-16, 1.00000000e+00, 0.00000000e+00])
  963. >>> r.apply(vector).shape
  964. (3,)
  965. Single rotation applied on multiple vectors:
  966. >>> vectors = np.array([
  967. ... [1, 0, 0],
  968. ... [1, 2, 3]])
  969. >>> r = R.from_rotvec([0, 0, np.pi/4])
  970. >>> r.as_dcm()
  971. array([[ 0.70710678, -0.70710678, 0. ],
  972. [ 0.70710678, 0.70710678, 0. ],
  973. [ 0. , 0. , 1. ]])
  974. >>> r.apply(vectors)
  975. array([[ 0.70710678, 0.70710678, 0. ],
  976. [-0.70710678, 2.12132034, 3. ]])
  977. >>> r.apply(vectors).shape
  978. (2, 3)
  979. Multiple rotations on a single vector:
  980. >>> r = R.from_rotvec([[0, 0, np.pi/4], [np.pi/2, 0, 0]])
  981. >>> vector = np.array([1,2,3])
  982. >>> r.as_dcm()
  983. array([[[ 7.07106781e-01, -7.07106781e-01, 0.00000000e+00],
  984. [ 7.07106781e-01, 7.07106781e-01, 0.00000000e+00],
  985. [ 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]],
  986. [[ 1.00000000e+00, 0.00000000e+00, 0.00000000e+00],
  987. [ 0.00000000e+00, 2.22044605e-16, -1.00000000e+00],
  988. [ 0.00000000e+00, 1.00000000e+00, 2.22044605e-16]]])
  989. >>> r.apply(vector)
  990. array([[-0.70710678, 2.12132034, 3. ],
  991. [ 1. , -3. , 2. ]])
  992. >>> r.apply(vector).shape
  993. (2, 3)
  994. Multiple rotations on multiple vectors. Each rotation is applied on the
  995. corresponding vector:
  996. >>> r = R.from_euler('zxy', [
  997. ... [0, 0, 90],
  998. ... [45, 30, 60]], degrees=True)
  999. >>> vectors = [
  1000. ... [1, 2, 3],
  1001. ... [1, 0, -1]]
  1002. >>> r.apply(vectors)
  1003. array([[ 3. , 2. , -1. ],
  1004. [-0.09026039, 1.11237244, -0.86860844]])
  1005. >>> r.apply(vectors).shape
  1006. (2, 3)
  1007. It is also possible to apply the inverse rotation:
  1008. >>> r = R.from_euler('zxy', [
  1009. ... [0, 0, 90],
  1010. ... [45, 30, 60]], degrees=True)
  1011. >>> vectors = [
  1012. ... [1, 2, 3],
  1013. ... [1, 0, -1]]
  1014. >>> r.apply(vectors, inverse=True)
  1015. array([[-3. , 2. , 1. ],
  1016. [ 1.09533535, -0.8365163 , 0.3169873 ]])
  1017. """
  1018. vectors = np.asarray(vectors)
  1019. if vectors.ndim > 2 or vectors.shape[-1] != 3:
  1020. raise ValueError("Expected input of shape (3,) or (P, 3), "
  1021. "got {}.".format(vectors.shape))
  1022. single_vector = False
  1023. if vectors.shape == (3,):
  1024. single_vector = True
  1025. vectors = vectors[None, :]
  1026. dcm = self.as_dcm()
  1027. if self._single:
  1028. dcm = dcm[None, :, :]
  1029. n_vectors = vectors.shape[0]
  1030. n_rotations = len(self)
  1031. if n_vectors != 1 and n_rotations != 1 and n_vectors != n_rotations:
  1032. raise ValueError("Expected equal numbers of rotations and vectors "
  1033. ", or a single rotation, or a single vector, got "
  1034. "{} rotations and {} vectors.".format(
  1035. n_rotations, n_vectors))
  1036. if inverse:
  1037. result = np.einsum('ikj,ik->ij', dcm, vectors)
  1038. else:
  1039. result = np.einsum('ijk,ik->ij', dcm, vectors)
  1040. if self._single and single_vector:
  1041. return result[0]
  1042. else:
  1043. return result
  1044. def __mul__(self, other):
  1045. """Compose this rotation with the other.
  1046. If `p` and `q` are two rotations, then the composition of 'q followed
  1047. by p' is equivalent to `p * q`. In terms of DCMs, the composition can
  1048. be expressed as `p.as_dcm().dot(q.as_dcm())`.
  1049. Parameters
  1050. ----------
  1051. other : `Rotation` instance
  1052. Object containing the rotaions to be composed with this one. Note
  1053. that rotation compositions are not commutative, so `p * q` is
  1054. different from `q * p`.
  1055. Returns
  1056. -------
  1057. composition : `Rotation` instance
  1058. This function supports composition of multiple rotations at a time.
  1059. The following cases are possible:
  1060. - Either `p` or `q` contains a single rotation. In this case
  1061. `output` contains the result of composing each rotation in the
  1062. other object with the single rotation.
  1063. - Both `p` and `q` contain `N` rotations. In this case each
  1064. rotation `p[i]` is composed with the corresponding rotation
  1065. `q[i]` and `output` contains `N` rotations.
  1066. Examples
  1067. --------
  1068. >>> from scipy.spatial.transform import Rotation as R
  1069. Composition of two single rotations:
  1070. >>> p = R.from_quat([0, 0, 1, 1])
  1071. >>> q = R.from_quat([1, 0, 0, 1])
  1072. >>> p.as_dcm()
  1073. array([[ 0., -1., 0.],
  1074. [ 1., 0., 0.],
  1075. [ 0., 0., 1.]])
  1076. >>> q.as_dcm()
  1077. array([[ 1., 0., 0.],
  1078. [ 0., 0., -1.],
  1079. [ 0., 1., 0.]])
  1080. >>> r = p * q
  1081. >>> r.as_dcm()
  1082. array([[0., 0., 1.],
  1083. [1., 0., 0.],
  1084. [0., 1., 0.]])
  1085. Composition of two objects containing equal number of rotations:
  1086. >>> p = R.from_quat([[0, 0, 1, 1], [1, 0, 0, 1]])
  1087. >>> q = R.from_rotvec([[np.pi/4, 0, 0], [-np.pi/4, 0, np.pi/4]])
  1088. >>> p.as_quat()
  1089. array([[0. , 0. , 0.70710678, 0.70710678],
  1090. [0.70710678, 0. , 0. , 0.70710678]])
  1091. >>> q.as_quat()
  1092. array([[ 0.38268343, 0. , 0. , 0.92387953],
  1093. [-0.37282173, 0. , 0.37282173, 0.84971049]])
  1094. >>> r = p * q
  1095. >>> r.as_quat()
  1096. array([[ 0.27059805, 0.27059805, 0.65328148, 0.65328148],
  1097. [ 0.33721128, -0.26362477, 0.26362477, 0.86446082]])
  1098. """
  1099. if not(len(self) == 1 or len(other) == 1 or len(self) == len(other)):
  1100. raise ValueError("Expected equal number of rotations in both "
  1101. "or a single rotation in either object, "
  1102. "got {} rotations in first and {} rotations in "
  1103. "second object.".format(
  1104. len(self), len(other)))
  1105. result = _compose_quat(self._quat, other._quat)
  1106. if self._single and other._single:
  1107. result = result[0]
  1108. return self.__class__(result, normalized=True, copy=False)
  1109. def inv(self):
  1110. """Invert this rotation.
  1111. Composition of a rotation with its inverse results in an identity
  1112. transformation.
  1113. Returns
  1114. -------
  1115. inverse : `Rotation` instance
  1116. Object containing inverse of the rotations in the current instance.
  1117. Examples
  1118. --------
  1119. >>> from scipy.spatial.transform import Rotation as R
  1120. Inverting a single rotation:
  1121. >>> p = R.from_euler('z', 45, degrees=True)
  1122. >>> q = p.inv()
  1123. >>> q.as_euler('zyx', degrees=True)
  1124. array([-45., 0., 0.])
  1125. Inverting multiple rotations:
  1126. >>> p = R.from_rotvec([[0, 0, np.pi/3], [-np.pi/4, 0, 0]])
  1127. >>> q = p.inv()
  1128. >>> q.as_rotvec()
  1129. array([[-0. , -0. , -1.04719755],
  1130. [ 0.78539816, -0. , -0. ]])
  1131. """
  1132. quat = self._quat.copy()
  1133. quat[:, -1] *= -1
  1134. if self._single:
  1135. quat = quat[0]
  1136. return self.__class__(quat, normalized=True, copy=False)
  1137. def __getitem__(self, indexer):
  1138. """Extract rotation(s) at given index(es) from object.
  1139. Create a new `Rotation` instance containing a subset of rotations
  1140. stored in this object.
  1141. Parameters
  1142. ----------
  1143. indexer : index, slice, or index array
  1144. Specifies which rotation(s) to extract. A single indexer must be
  1145. specified, i.e. as if indexing a 1 dimensional array or list.
  1146. Returns
  1147. -------
  1148. rotation : `Rotation` instance
  1149. Contains
  1150. - a single rotation, if `indexer` is a single index
  1151. - a stack of rotation(s), if `indexer` is a slice, or and index
  1152. array.
  1153. Examples
  1154. --------
  1155. >>> from scipy.spatial.transform import Rotation as R
  1156. >>> r = R.from_quat([
  1157. ... [1, 1, 0, 0],
  1158. ... [0, 1, 0, 1],
  1159. ... [1, 1, -1, 0]])
  1160. >>> r.as_quat()
  1161. array([[ 0.70710678, 0.70710678, 0. , 0. ],
  1162. [ 0. , 0.70710678, 0. , 0.70710678],
  1163. [ 0.57735027, 0.57735027, -0.57735027, 0. ]])
  1164. Indexing using a single index:
  1165. >>> p = r[0]
  1166. >>> p.as_quat()
  1167. array([0.70710678, 0.70710678, 0. , 0. ])
  1168. Array slicing:
  1169. >>> q = r[1:3]
  1170. >>> q.as_quat()
  1171. array([[ 0. , 0.70710678, 0. , 0.70710678],
  1172. [ 0.57735027, 0.57735027, -0.57735027, 0. ]])
  1173. """
  1174. return self.__class__(self._quat[indexer], normalized=True)
  1175. @classmethod
  1176. def random(cls, num=None, random_state=None):
  1177. """Generate uniformly distributed rotations.
  1178. Parameters
  1179. ----------
  1180. num : int or None, optional
  1181. Number of random rotations to generate. If None (default), then a
  1182. single rotation is generated.
  1183. random_state : int, RandomState instance or None, optional
  1184. Accepts an `int` as a seed for the random generator or a
  1185. RandomState object. If None (default), uses global `np.random`
  1186. random state.
  1187. Returns
  1188. -------
  1189. random_rotation : `Rotation` instance
  1190. Contains a single rotation if `num` is None. Otherwise contains a
  1191. stack of `num` rotations.
  1192. Examples
  1193. --------
  1194. >>> from scipy.spatial.transform import Rotation as R
  1195. Sample a single rotation:
  1196. >>> R.random(random_state=1234).as_euler('zxy', degrees=True)
  1197. array([-110.5976185 , 55.32758512, 76.3289269 ])
  1198. Sample a stack of rotations:
  1199. >>> R.random(5, random_state=1234).as_euler('zxy', degrees=True)
  1200. array([[-110.5976185 , 55.32758512, 76.3289269 ],
  1201. [ -91.59132005, -14.3629884 , -93.91933182],
  1202. [ 25.23835501, 45.02035145, -121.67867086],
  1203. [ -51.51414184, -15.29022692, -172.46870023],
  1204. [ -81.63376847, -27.39521579, 2.60408416]])
  1205. """
  1206. random_state = check_random_state(random_state)
  1207. if num is None:
  1208. sample = random_state.normal(size=4)
  1209. else:
  1210. sample = random_state.normal(size=(num, 4))
  1211. return Rotation.from_quat(sample)
  1212. @classmethod
  1213. def match_vectors(cls, a, b, weights=None, normalized=False):
  1214. """Estimate a rotation to match two sets of vectors.
  1215. Find a rotation between frames A and B which best matches a set of unit
  1216. vectors `a` and `b` observed in these frames. The following loss
  1217. function is minimized to solve for the direction cosine matrix
  1218. :math:`C`:
  1219. .. math::
  1220. L(C) = \\frac{1}{2} \\sum_{i = 1}^{n} w_i \\lVert \\mathbf{a}_i -
  1221. C \\mathbf{b}_i \\rVert^2 ,
  1222. where :math:`w_i`'s are the `weights` corresponding to each vector.
  1223. The rotation is estimated using Markley's SVD method [1]_.
  1224. Parameters
  1225. ----------
  1226. a : array_like, shape (N, 3)
  1227. Vector components observed in initial frame A. Each row of `a`
  1228. denotes a vector.
  1229. b : array_like, shape (N, 3)
  1230. Vector components observed in another frame B. Each row of `b`
  1231. denotes a vector.
  1232. weights : array_like shape (N,), optional
  1233. Weights describing the relative importance of the vectors in
  1234. `a`. If None (default), then all values in `weights` are assumed to
  1235. be equal.
  1236. normalized : boolean, optional
  1237. If True, assume input vectors `a` and `b` to have unit norm. If
  1238. False, normalize `a` and `b` before estimating rotation. Default
  1239. is False.
  1240. Returns
  1241. -------
  1242. estimated_rotation : `Rotation` instance
  1243. Best estimate of the rotation that transforms `b` to `a`.
  1244. sensitivity_matrix : `numpy.ndarray`, shape (3, 3)
  1245. Scaled covariance of the attitude errors expressed as the small
  1246. rotation vector of frame A. Multiply with harmonic mean [3]_ of
  1247. variance in each observation to get true covariance matrix. The
  1248. error model is detailed in [2]_.
  1249. References
  1250. ----------
  1251. .. [1] F. Landis Markley,
  1252. "Attitude determination using vector observations: a fast
  1253. optimal matrix algorithm", Journal of Astronautical Sciences,
  1254. Vol. 41, No.2, 1993, pp. 261-280.
  1255. .. [2] F. Landis Markley,
  1256. "Attitude determination using vector observations and the
  1257. Singular Value Decomposition", Journal of Astronautical
  1258. Sciences, Vol. 38, No.3, 1988, pp. 245-258.
  1259. .. [3] `Harmonic Mean <https://en.wikipedia.org/wiki/Harmonic_mean>`_
  1260. """
  1261. a = np.asarray(a)
  1262. if a.ndim != 2 or a.shape[-1] != 3:
  1263. raise ValueError("Expected input `a` to have shape (N, 3), "
  1264. "got {}".format(a.shape))
  1265. b = np.asarray(b)
  1266. if b.ndim != 2 or b.shape[-1] != 3:
  1267. raise ValueError("Expected input `b` to have shape (N, 3), "
  1268. "got {}.".format(b.shape))
  1269. if a.shape != b.shape:
  1270. raise ValueError("Expected inputs `a` and `b` to have same shapes"
  1271. ", got {} and {} respectively.".format(
  1272. a.shape, b.shape))
  1273. if b.shape[0] == 1:
  1274. raise ValueError("Rotation cannot be estimated using a single "
  1275. "vector.")
  1276. if weights is None:
  1277. weights = np.ones(b.shape[0])
  1278. else:
  1279. weights = np.asarray(weights)
  1280. if weights.ndim != 1:
  1281. raise ValueError("Expected `weights` to be 1 dimensional, got "
  1282. "shape {}.".format(weights.shape))
  1283. if weights.shape[0] != b.shape[0]:
  1284. raise ValueError("Expected `weights` to have number of values "
  1285. "equal to number of input vectors, got "
  1286. "{} values and {} vectors.".format(
  1287. weights.shape[0], b.shape[0]))
  1288. weights = weights / np.sum(weights)
  1289. if not normalized:
  1290. a = a / scipy.linalg.norm(a, axis=1)[:, None]
  1291. b = b / scipy.linalg.norm(b, axis=1)[:, None]
  1292. B = np.einsum('ji,jk->ik', weights[:, None] * a, b)
  1293. u, s, vh = np.linalg.svd(B)
  1294. C = np.dot(u, vh)
  1295. zeta = (s[0]+s[1]) * (s[1]+s[2]) * (s[2]+s[0])
  1296. if np.abs(zeta) <= 1e-16:
  1297. raise ValueError("Three component error vector has infinite "
  1298. "covariance. It is impossible to determine the "
  1299. "rotation uniquely.")
  1300. kappa = s[0]*s[1] + s[1]*s[2] + s[2]*s[0]
  1301. sensitivity = ((kappa * np.eye(3) + np.dot(B, B.T)) /
  1302. (zeta * a.shape[0]))
  1303. return cls.from_dcm(C), sensitivity
  1304. class Slerp(object):
  1305. """Spherical Linear Interpolation of Rotations.
  1306. The interpolation between consecutive rotations is performed as a rotation
  1307. around a fixed axis with a constant angular velocity [1]_. This ensures
  1308. that the interpolated rotations follow the shortest path between initial
  1309. and final orientations.
  1310. Parameters
  1311. ----------
  1312. times : array_like, shape (N,)
  1313. Times of the known rotations. At least 2 times must be specified.
  1314. rotations : `Rotation` instance
  1315. Rotations to perform the interpolation between. Must contain N
  1316. rotations.
  1317. Methods
  1318. -------
  1319. __call__
  1320. References
  1321. ----------
  1322. .. [1] `Quaternion Slerp
  1323. <https://en.wikipedia.org/wiki/Slerp#Quaternion_Slerp>`_
  1324. Examples
  1325. --------
  1326. >>> from scipy.spatial.transform import Rotation as R
  1327. >>> from scipy.spatial.transform import Slerp
  1328. Setup the fixed keyframe rotations and times:
  1329. >>> key_rots = R.random(5, random_state=2342345)
  1330. >>> key_times = [0, 1, 2, 3, 4]
  1331. Create the interpolator object:
  1332. >>> slerp = Slerp(key_times, key_rots)
  1333. Interpolate the rotations at the given times:
  1334. >>> times = [0, 0.5, 0.25, 1, 1.5, 2, 2.75, 3, 3.25, 3.60, 4]
  1335. >>> interp_rots = slerp(times)
  1336. The keyframe rotations expressed as Euler angles:
  1337. >>> key_rots.as_euler('xyz', degrees=True)
  1338. array([[ 14.31443779, -27.50095894, -3.7275787 ],
  1339. [ -1.79924227, -24.69421529, 164.57701743],
  1340. [146.15020772, 43.22849451, -31.34891088],
  1341. [ 46.39959442, 11.62126073, -45.99719267],
  1342. [-88.94647804, -49.64400082, -65.80546984]])
  1343. The interpolated rotations expressed as Euler angles. These agree with the
  1344. keyframe rotations at both endpoints of the range of keyframe times.
  1345. >>> interp_rots.as_euler('xyz', degrees=True)
  1346. array([[ 14.31443779, -27.50095894, -3.7275787 ],
  1347. [ 4.74588574, -32.44683966, 81.25139984],
  1348. [ 10.71094749, -31.56690154, 38.06896408],
  1349. [ -1.79924227, -24.69421529, 164.57701743],
  1350. [ 11.72796022, 51.64207311, -171.7374683 ],
  1351. [ 146.15020772, 43.22849451, -31.34891088],
  1352. [ 68.10921869, 20.67625074, -48.74886034],
  1353. [ 46.39959442, 11.62126073, -45.99719267],
  1354. [ 12.35552615, 4.21525086, -64.89288124],
  1355. [ -30.08117143, -19.90769513, -78.98121326],
  1356. [ -88.94647804, -49.64400082, -65.80546984]])
  1357. """
  1358. def __init__(self, times, rotations):
  1359. if len(rotations) == 1:
  1360. raise ValueError("`rotations` must contain at least 2 rotations.")
  1361. times = np.asarray(times)
  1362. if times.ndim != 1:
  1363. raise ValueError("Expected times to be specified in a 1 "
  1364. "dimensional array, got {} "
  1365. "dimensions.".format(times.ndim))
  1366. if times.shape[0] != len(rotations):
  1367. raise ValueError("Expected number of rotations to be equal to "
  1368. "number of timestamps given, got {} rotations "
  1369. "and {} timestamps.".format(
  1370. len(rotations), times.shape[0]))
  1371. self.times = times
  1372. self.timedelta = np.diff(times)
  1373. if np.any(self.timedelta <= 0):
  1374. raise ValueError("Times must be in strictly increasing order.")
  1375. self.rotations = rotations[:-1]
  1376. self.rotvecs = (self.rotations.inv() * rotations[1:]).as_rotvec()
  1377. def __call__(self, times):
  1378. """Interpolate rotations.
  1379. Compute the interpolated rotations at the given `times`.
  1380. Parameters
  1381. ----------
  1382. times : array_like, 1D
  1383. Times to compute the interpolations at.
  1384. Returns
  1385. -------
  1386. interpolated_rotation : `Rotation` instance
  1387. Object containing the rotations computed at given `times`.
  1388. """
  1389. # Clearly differentiate from self.times property
  1390. compute_times = np.asarray(times)
  1391. if compute_times.ndim != 1:
  1392. raise ValueError("Expected times to be specified in a 1 "
  1393. "dimensional array, got {} "
  1394. "dimensions.".format(compute_times.ndim))
  1395. # side = 'left' (default) excludes t_min.
  1396. ind = np.searchsorted(self.times, compute_times) - 1
  1397. # Include t_min. Without this step, index for t_min equals -1
  1398. ind[compute_times == self.times[0]] = 0
  1399. if np.any(np.logical_or(ind < 0, ind > len(self.rotations) - 1)):
  1400. raise ValueError("Interpolation times must be within the range "
  1401. "[{}, {}], both inclusive.".format(
  1402. self.times[0], self.times[-1]))
  1403. alpha = (compute_times - self.times[ind]) / self.timedelta[ind]
  1404. return (self.rotations[ind] *
  1405. Rotation.from_rotvec(self.rotvecs[ind] * alpha[:, None]))