{ "cells": [ { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Rigid-body transformations in three-dimensions\n", "\n", "> Marcos Duarte, Renato Naville Watanabe \n", "> [Laboratory of Biomechanics and Motor Control](https://bmclab.pesquisa.ufabc.edu.br) \n", "> Federal University of ABC, Brazil" ] }, { "cell_type": "markdown", "metadata": { "toc": true }, "source": [ "

Contents

\n", "
" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "The kinematics of a rigid body is completely described by its pose, i.e., its position and orientation in space (and the corresponding changes, translation and rotation). In a three-dimensional space, at least three coordinates and three angles are necessary to describe the pose of the rigid body, totalizing six degrees of freedom for a rigid body.\n", "\n", "In motion analysis, to describe a translation and rotation of a rigid body with respect to a coordinate system, typically we attach another coordinate system to the rigid body and determine a transformation between these two coordinate systems.\n", "\n", "A transformation is any function mapping a set to another set. For the description of the kinematics of rigid bodies, we are interested only in what is called rigid or Euclidean transformations (denoted as SE(3) for the three-dimensional space) because they preserve the distance between every pair of points of the body (which is considered rigid by definition). Translations and rotations are examples of rigid transformations (a reflection is also an example of rigid transformation but this changes the right-hand axis convention to a left hand, which usually is not of interest). In turn, rigid transformations are examples of [affine transformations](https://en.wikipedia.org/wiki/Affine_transformation). Examples of other affine transformations are shear and scaling transformations (which preserves angles but not lengths). \n", "\n", "We will follow the same rationale as in the notebook [Rigid-body transformations in a plane (2D)](https://nbviewer.org/github/BMClab/bmc/blob/master/notebooks/Transformation2D.ipynb) and we will skip the fundamental concepts already covered there. So, you if haven't done yet, you should read that notebook before continuing here." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Python setup" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:04.441332Z", "start_time": "2021-11-18T00:05:04.341026Z" }, "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "# Import the necessary libraries\n", "import numpy as np\n", "# suppress scientific notation for small numbers:\n", "np.set_printoptions(precision=4, suppress=True)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Translation\n", "\n", "A pure three-dimensional translation of a rigid body (or a coordinate system attached to it) in relation to other rigid body (with other coordinate system) is illustrated in the figure below. \n", "
\n", "
translation 3D
Figure. A point in three-dimensional space represented in two coordinate systems, with one coordinate system translated.
\n", "\n", "The position of point $\\mathbf{P}$ originally described in the $xyz$ (local) coordinate system but now described in the $\\mathbf{XYZ}$ (Global) coordinate system in vector form is: \n", "
\n", "\n", "\\begin{equation}\n", "\\mathbf{P_G} = \\mathbf{L_G} + \\mathbf{P_l}\n", "\\end{equation}\n", "\n", "\n", "Or in terms of its components: \n", "\n", "\n", "\\begin{equation}\n", "\\begin{array}{ll}\n", "\\mathbf{P_X} =& \\mathbf{L_X} + \\mathbf{P}_x \\\\\n", "\\mathbf{P_Y} =& \\mathbf{L_Y} + \\mathbf{P}_y \\\\\n", "\\mathbf{P_Z} =& \\mathbf{L_Z} + \\mathbf{P}_z \n", "\\end{array}\n", "\\end{equation}\n", "\n", "\n", "And in matrix form: \n", "\n", "\n", "\\begin{equation}\n", "\\begin{bmatrix}\n", "\\mathbf{P_X} \\\\\n", "\\mathbf{P_Y} \\\\\n", "\\mathbf{P_Z} \n", "\\end{bmatrix} =\n", "\\begin{bmatrix}\n", "\\mathbf{L_X} \\\\\n", "\\mathbf{L_Y} \\\\\n", "\\mathbf{L_Z} \n", "\\end{bmatrix} +\n", "\\begin{bmatrix}\n", "\\mathbf{P}_x \\\\\n", "\\mathbf{P}_y \\\\\n", "\\mathbf{P}_z \n", "\\end{bmatrix}\n", "\\end{equation}\n", "\n", "\n", "From classical mechanics, this is an example of [Galilean transformation](http://en.wikipedia.org/wiki/Galilean_transformation). \n", "\n", "Let's use Python to compute some numeric examples:" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "For example, if the local coordinate system is translated by $\\mathbf{L_G}=[1, 2, 3]$ in relation to the Global coordinate system, a point with coordinates $\\mathbf{P_l}=[4, 5, 6]$ at the local coordinate system will have the position $\\mathbf{P_G}=[5, 7, 9]$ at the Global coordinate system:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:05.241043Z", "start_time": "2021-11-18T00:05:05.234528Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "array([5, 7, 9])" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "LG = np.array([1, 2, 3]) # Numpy array\n", "Pl = np.array([4, 5, 6])\n", "PG = LG + Pl\n", "PG" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "This operation also works if we have more than one point (NumPy try to guess how to handle vectors with different dimensions):" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:05.627167Z", "start_time": "2021-11-18T00:05:05.623887Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "array([[ 2, 4, 6],\n", " [ 5, 7, 9],\n", " [ 8, 10, 12]])" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Pl = np.array([[1, 2, 3],\n", " [4, 5, 6],\n", " [7, 8, 9]]) # 2D array with 3 rows and 3 columns\n", "PG = LG + Pl\n", "PG" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Rotation\n", "\n", "A pure three-dimensional rotation of a $xyz$ (local) coordinate system in relation to other $\\mathbf{XYZ}$ (Global) coordinate system and the position of a point in these two coordinate systems are illustrated in the next figure (remember that this is equivalent to describing a rotation between two rigid bodies). \n", "
\n", "
rotation 3D
A point in three-dimensional space represented in two coordinate systems, with one system rotated.
\n", "\n", "An important characteristic of angles in the three-dimensional space is that angles cannot be treated as vectors: the result of a sequence of rotations of a rigid body around different axes depends on the order of the rotations, as illustrated in the next figure. \n", "
\n", "
\n", "rotations
Figure. The result of a sequence of rotations around different axes of a coordinate system depends on the order of the rotations. In the first example (first row), the rotations are around a Global (fixed) coordinate system. In the second example (second row), the rotations are around a local (rotating) coordinate system.
\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Determination of the rotation matrix using direction cosines\n", "\n", "In analogy to the rotation in two dimensions, we can calculate the rotation matrix that describes the rotation of the $xyz$ (local) coordinate system in relation to the $\\mathbf{XYZ}$ (Global) coordinate system using the direction cosines between the axes of the two coordinate systems: \n", "
\n", "\n", "\\begin{equation}\n", "\\mathbf{R_{Gl}} = \\begin{bmatrix}\n", "\\cos\\mathbf{X}x & \\cos\\mathbf{X}y & \\cos\\mathbf{X}z \\\\\n", "\\cos\\mathbf{Y}x & \\cos\\mathbf{Y}y & \\cos\\mathbf{Y}z \\\\\n", "\\cos\\mathbf{Z}x & \\cos\\mathbf{Z}y & \\cos\\mathbf{Z}z\n", "\\end{bmatrix}\n", "\\end{equation}\n", "\n", "\n", "Note however that for rotations around more than one axis, these angles will not lie in the main planes ($\\mathbf{XY, YZ, ZX}$) of the $\\mathbf{XYZ}$ coordinate system, as illustrated in the figure below for the direction angles of the $y$ axis only. Thus, the determination of these angles by simple inspection, as we have done for the two-dimensional case, would not be simple. \n", "
\n", "
\n", "direction angles 3D
Figure. Definition of direction angles for the $y$ axis of the local coordinate system in relation to the $\\mathbf{XYZ}$ Global coordinate system.
\n", "
\n", "\n", "Note that the nine angles shown in the matrix above for the direction cosines are obviously redundant since only three angles are necessary to describe the orientation of a rigid body in the three-dimensional space. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Determination of the rotation matrix using the basis method\n", "\n", "Analogue to what we have described in the notebook [Rigid-body transformations in a plane (2D)](https://nbviewer.org/github/BMClab/bmc/blob/master/notebooks/Transformation2D.ipynb), the columns (or rows) of the 3D rotation matrix $\\mathbf{R_{Gl}}$ (or $\\mathbf{R_{lG}}$) between two coordinate systems are the versors of the basis of the rotated coordinate system.\n", "\n", "For example, consider that we have measured the position of at least three non-collinear markers placed on the rigid body, let's calculate a basis with these positions and then the rotation matrix. \n", "\n", "If we have the position of three markers: **m1**, **m2**, **m3**, a basis (formed by three orthogonal versors) can be found as: \n", "\n", " - First axis, **v1**, the vector **m2-m1**; \n", " - Second axis, **v2**, the cross product between the vectors **v1** and **m3-m1**; \n", " - Third axis, **v3**, the cross product between the vectors **v1** and **v2**. \n", " \n", "Then, each of these vectors are normalized resulting in three orthogonal versors. \n", "\n", "For example, given the positions m1 = [1,0,0], m2 = [0,1,0], m3 = [0,0,1], a basis can be found:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Versors:\n", "v1 = [-0.7071 0.7071 0. ]\n", "v2 = [0.5774 0.5774 0.5774]\n", "v3 = [ 0.4082 0.4082 -0.8165]\n", "\n", "Norm of each versor:\n", " 1.0 1.0 1.0000000000000002\n", "\n", "Rotation matrix RGl:\n", " [[-0.7071 0.5774 0.4082]\n", " [ 0.7071 0.5774 0.4082]\n", " [ 0. 0.5774 -0.8165]]\n" ] } ], "source": [ "m1 = np.array([1, 0, 0])\n", "m2 = np.array([0, 1, 0])\n", "m3 = np.array([0, 0, 1])\n", "\n", "v1 = m2 - m1\n", "v2 = np.cross(v1, m3 - m1)\n", "v3 = np.cross(v1, v2)\n", "\n", "print('Versors:')\n", "v1 = v1/np.linalg.norm(v1)\n", "print('v1 =', v1)\n", "\n", "v2 = v2/np.linalg.norm(v2)\n", "print('v2 =', v2)\n", "\n", "v3 = v3/np.linalg.norm(v3)\n", "print('v3 =', v3)\n", "\n", "print('\\nNorm of each versor:\\n',\n", " np.linalg.norm(np.cross(v1, v2)),\n", " np.linalg.norm(np.cross(v1, v3)),\n", " np.linalg.norm(np.cross(v2, v3)))\n", "\n", "RGl = np.array([v1, v2, v3]).T # or np.vstack((v1, v2, v3)).T\n", "print('\\nRotation matrix RGl:\\n', RGl)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However, we have the same problem as with the rotation matrix deduced from the direction cosines; there is no simple way to determine/interpret the angles of rotation from this matrix!" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Euler angles\n", "\n", "There are different ways to describe a three-dimensional rotation of a rigid body (or of a coordinate system). The most straightforward solution would probably be to use a spherical coordinate system, but spherical coordinates would be difficult to give an anatomical or clinical interpretation. A solution that has been often employed in biomechanics to handle rotations in the three-dimensional space is to use Euler angles. Under certain conditions, Euler angles can have an anatomical interpretation, but this representation also has some caveats. Let's see the Euler angles now.\n", "\n", "[Leonhard Euler](https://en.wikipedia.org/wiki/Leonhard_Euler) in the XVIII century showed that two three-dimensional coordinate systems with a common origin can be related by a sequence of up to three elemental rotations about the axes of the local coordinate system, where no two successive rotations may be about the same axis, which now are known as [Euler (or Eulerian) angles](http://en.wikipedia.org/wiki/Euler_angles). \n", "\n", "
\n", "
translation and rotation 3D
Figure. Euler angles: a way to reach any orientation using a specific sequence of elemental rotations (image from Wikipedia).
\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Elemental rotations\n", "\n", "First, let's see rotations around a fixed Global coordinate system as we did for the two-dimensional case. The next figure illustrates elemental rotations of the local coordinate system around each axis of the fixed Global coordinate system. \n", "
\n", "
\n", "rotations
Figure. Elemental rotations of the $xyz$ coordinate system around each axis, $\\mathbf{X}$, $\\mathbf{Y}$, and $\\mathbf{Z}$, of the fixed $\\mathbf{XYZ}$ coordinate system. Note that for better clarity, the axis around where the rotation occurs is shown perpendicular to this page for each elemental rotation.
\n", "
" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Rotations around the fixed coordinate system\n", "\n", "The rotation matrices for the elemental rotations around each axis of the fixed $\\mathbf{XYZ}$ coordinate system (rotations of the local coordinate system in relation to the Global coordinate system) are shown next.\n", "\n", "Around $\\mathbf{X}$ axis: \n", "\n", "\n", "\\begin{equation}\n", "\\mathbf{R_{Gl,\\,X}} = \n", "\\begin{bmatrix}\n", "1 & 0 & 0 \\\\\n", "0 & \\cos\\alpha & -\\sin\\alpha \\\\\n", "0 & \\sin\\alpha & \\cos\\alpha\n", "\\end{bmatrix}\n", "\\end{equation}\n", "\n", "\n", "Around $\\mathbf{Y}$ axis: \n", "\n", "\n", "\\begin{equation}\n", "\\mathbf{R_{Gl,\\,Y}} = \n", "\\begin{bmatrix}\n", "\\cos\\beta & 0 & \\sin\\beta \\\\\n", "0 & 1 & 0 \\\\\n", "-\\sin\\beta & 0 & \\cos\\beta\n", "\\end{bmatrix}\n", "\\end{equation}\n", "\n", "\n", "Around $\\mathbf{Z}$ axis: \n", "\n", "\n", "\\begin{equation}\n", "\\mathbf{R_{Gl,\\,Z}} = \n", "\\begin{bmatrix}\n", "\\cos\\gamma & -\\sin\\gamma & 0\\\\\n", "\\sin\\gamma & \\cos\\gamma & 0 \\\\\n", "0 & 0 & 1\n", "\\end{bmatrix}\n", "\\end{equation}\n", "\n", "\n", "These matrices are the rotation matrices for the case of two-dimensional coordinate systems plus the corresponding terms for the third axes of the local and Global coordinate systems, which are parallel. \n", "To understand why the terms for the third axes are 1's or 0's, for instance, remember they represent the cosine directors. The cosines between $\\mathbf{X}x$, $\\mathbf{Y}y$, and $\\mathbf{Z}z$ for the elemental rotations around respectively the $\\mathbf{X}$, $\\mathbf{Y}$, and $\\mathbf{Z}$ axes are all 1 because $\\mathbf{X}x$, $\\mathbf{Y}y$, and $\\mathbf{Z}z$ are parallel ($\\cos 0^o$). The cosines of the other elements are zero because the axis around where each rotation occurs is perpendicular to the other axes of the coordinate systems ($\\cos 90^o$)." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Rotations around the local coordinate system\n", "\n", "The rotation matrices for the elemental rotations this time around each axis of the $xyz$ coordinate system (rotations of the Global coordinate system in relation to the local coordinate system), similarly to the two-dimensional case, are simply the transpose of the above matrices as shown next.\n", "\n", "Around $x$ axis: \n", "\n", "\n", "\\begin{equation}\n", "\\mathbf{R}_{\\mathbf{lG},\\,x} = \n", "\\begin{bmatrix}\n", "1 & 0 & 0 \\\\\n", "0 & \\cos\\alpha & \\sin\\alpha \\\\\n", "0 & -\\sin\\alpha & \\cos\\alpha\n", "\\end{bmatrix}\n", "\\end{equation}\n", "\n", "\n", "Around $y$ axis: \n", "\n", "\n", "\\begin{equation}\n", "\\mathbf{R}_{\\mathbf{lG},\\,y} = \n", "\\begin{bmatrix}\n", "\\cos\\beta & 0 & -\\sin\\beta \\\\\n", "0 & 1 & 0 \\\\\n", "\\sin\\beta & 0 & \\cos\\beta\n", "\\end{bmatrix}\n", "\\end{equation}\n", "\n", "\n", "Around $z$ axis: \n", "\n", "\n", "\\begin{equation}\n", "\\mathbf{R}_{\\mathbf{lG},\\,z} = \n", "\\begin{bmatrix}\n", "\\cos\\gamma & \\sin\\gamma & 0\\\\\n", "-\\sin\\gamma & \\cos\\gamma & 0 \\\\\n", "0 & 0 & 1\n", "\\end{bmatrix}\n", "\\end{equation}\n", "\n", "\n", "Notice this is equivalent to instead of rotating the local coordinate system by $\\alpha, \\beta, \\gamma$ in relation to axes of the Global coordinate system, to rotate the Global coordinate system by $-\\alpha, -\\beta, -\\gamma$ in relation to the axes of the local coordinate system; remember that \n", "$\\cos(-\\:\\cdot)=\\cos(\\cdot)$ and $\\sin(-\\:\\cdot)=-\\sin(\\cdot)$.\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Sequence of elemental rotations\n", "\n", "Consider now a sequence of elemental rotations around the $\\mathbf{X}$, $\\mathbf{Y}$, and $\\mathbf{Z}$ axes of the fixed $\\mathbf{XYZ}$ coordinate system illustrated in the next figure. \n", "
\n", "
rotations
Figure. Sequence of elemental rotations of the $xyz$ coordinate system around each axis, $\\mathbf{X}$, $\\mathbf{Y}$, $\\mathbf{Z}$, of the fixed $\\mathbf{XYZ}$ coordinate system.
\n", "\n", "This sequence of elemental rotations (each one of the local coordinate system with respect to the fixed Global coordinate system) is mathematically represented by a multiplication between the rotation matrices: \n", "
\n", "\n", "\\begin{equation}\n", "\\begin{array}{ll}\n", "\\mathbf{R_{Gl,\\;XYZ}} & = \\mathbf{R_{Z}} \\mathbf{R_{Y}} \\mathbf{R_{X}} \\\\\n", "\\\\ \n", "& = \\begin{bmatrix}\n", "\\cos\\gamma & -\\sin\\gamma & 0\\\\\n", "\\sin\\gamma & \\cos\\gamma & 0 \\\\\n", "0 & 0 & 1\n", "\\end{bmatrix}\n", "\\begin{bmatrix}\n", "\\cos\\beta & 0 & \\sin\\beta \\\\\n", "0 & 1 & 0 \\\\\n", "-\\sin\\beta & 0 & \\cos\\beta\n", "\\end{bmatrix}\n", "\\begin{bmatrix}\n", "1 & 0 & 0 \\\\\n", "0 & \\cos\\alpha & -\\sin\\alpha \\\\\n", "0 & \\sin\\alpha & \\cos\\alpha\n", "\\end{bmatrix} \\\\\n", "\\\\ \n", "& =\n", "\\begin{bmatrix}\n", "\\cos\\beta\\:\\cos\\gamma \\;&\\;\n", "\\sin\\alpha\\:\\sin\\beta\\:\\cos\\gamma-\\cos\\alpha\\:\\sin\\gamma \\;&\\;\n", "\\cos\\alpha\\:\\sin\\beta\\:cos\\gamma+\\sin\\alpha\\:\\sin\\gamma \\;\\;\\; \\\\\n", "\\cos\\beta\\:\\sin\\gamma \\;&\\;\n", "\\sin\\alpha\\:\\sin\\beta\\:\\sin\\gamma+\\cos\\alpha\\:\\cos\\gamma \\;&\\;\n", "\\cos\\alpha\\:\\sin\\beta\\:\\sin\\gamma-\\sin\\alpha\\:\\cos\\gamma \\;\\;\\; \\\\\n", "-\\sin\\beta \\;&\\; \\sin\\alpha\\:\\cos\\beta \\;&\\; \\cos\\alpha\\:\\cos\\beta \\;\\;\\;\n", "\\end{bmatrix} \n", "\\end{array}\n", "\\end{equation}\n", "\n", "\n", "Note the order of the matrices. \n", "\n", "We can check this matrix multiplication using [Sympy](http://sympy.org/en/index.html):" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:08.150746Z", "start_time": "2021-11-18T00:05:07.826045Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle \\mathbf{R_{Gl,\\,XYZ}}=\\left[\\begin{matrix}\\cos{\\left(\\beta \\right)} \\cos{\\left(\\gamma \\right)} & \\sin{\\left(\\alpha \\right)} \\sin{\\left(\\beta \\right)} \\cos{\\left(\\gamma \\right)} - \\sin{\\left(\\gamma \\right)} \\cos{\\left(\\alpha \\right)} & \\sin{\\left(\\alpha \\right)} \\sin{\\left(\\gamma \\right)} + \\sin{\\left(\\beta \\right)} \\cos{\\left(\\alpha \\right)} \\cos{\\left(\\gamma \\right)}\\\\\\sin{\\left(\\gamma \\right)} \\cos{\\left(\\beta \\right)} & \\sin{\\left(\\alpha \\right)} \\sin{\\left(\\beta \\right)} \\sin{\\left(\\gamma \\right)} + \\cos{\\left(\\alpha \\right)} \\cos{\\left(\\gamma \\right)} & - \\sin{\\left(\\alpha \\right)} \\cos{\\left(\\gamma \\right)} + \\sin{\\left(\\beta \\right)} \\sin{\\left(\\gamma \\right)} \\cos{\\left(\\alpha \\right)}\\\\- \\sin{\\left(\\beta \\right)} & \\sin{\\left(\\alpha \\right)} \\cos{\\left(\\beta \\right)} & \\cos{\\left(\\alpha \\right)} \\cos{\\left(\\beta \\right)}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "#import the necessary libraries\n", "from IPython.core.display import Math, display\n", "import sympy as sym\n", "cos, sin = sym.cos, sym.sin\n", "\n", "a, b, g = sym.symbols('alpha, beta, gamma')\n", "\n", "# Elemental rotation matrices of xyz in relation to XYZ:\n", "RX = sym.Matrix([[1, 0, 0],\n", " [0, cos(a), -sin(a)],\n", " [0, sin(a), cos(a)]])\n", "\n", "RY = sym.Matrix([[cos(b), 0, sin(b)],\n", " [0, 1, 0],\n", " [-sin(b), 0, cos(b)]])\n", "\n", "RZ = sym.Matrix([[cos(g), -sin(g), 0],\n", " [sin(g), cos(g), 0],\n", " [ 0, 0, 1]])\n", "\n", "# Rotation matrix of xyz in relation to XYZ:\n", "RXYZ = RZ @ RY @ RX\n", "\n", "display(Math(r'\\mathbf{R_{Gl,\\,XYZ}}=' + sym.latex(RXYZ,\n", " mat_str='matrix')))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "For instance, we can calculate the numerical rotation matrix for these sequential elemental rotations by $90^o$ around $\\mathbf{X,Y,Z}$:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:08.194913Z", "start_time": "2021-11-18T00:05:08.182472Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle \\mathbf{R_{Gl,\\,XYZ\\,}}(90^o, 90^o, 90^o) =\\left[\\begin{matrix}0 & 0 & 1.0\\\\0 & 1.0 & 0\\\\-1.0 & 0 & 0\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "R = sym.lambdify((a, b, g), RXYZ, 'numpy')\n", "R = R(np.pi/2, np.pi/2, np.pi/2)\n", "display(Math(r'\\mathbf{R_{Gl,\\,XYZ\\,}}(90^o, 90^o, 90^o) =' + \\\n", " sym.latex(sym.Matrix(R).n(3, chop=True))))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Below you can test any sequence of rotation around the global coordinates. Just change the matrix R, and the angles of the variables $\\alpha$, $\\beta$ and $\\gamma$. In the example below is the rotation around the global basis, in the sequence x,y,z, with the angles $\\alpha=\\pi/3$ rad, $\\beta=\\pi/4$ rad and $\\gamma=\\pi/6$ rad." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:08.724190Z", "start_time": "2021-11-18T00:05:08.524967Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "application/javascript": [ "/* Put everything inside the global mpl namespace */\n", "/* global mpl */\n", "window.mpl = {};\n", "\n", "mpl.get_websocket_type = function () {\n", " if (typeof WebSocket !== 'undefined') {\n", " return WebSocket;\n", " } else if (typeof MozWebSocket !== 'undefined') {\n", " return MozWebSocket;\n", " } else {\n", " alert(\n", " 'Your browser does not have WebSocket support. ' +\n", " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", " 'Firefox 4 and 5 are also supported but you ' +\n", " 'have to enable WebSockets in about:config.'\n", " );\n", " }\n", "};\n", "\n", "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", " this.id = figure_id;\n", "\n", " this.ws = websocket;\n", "\n", " this.supports_binary = this.ws.binaryType !== undefined;\n", "\n", " if (!this.supports_binary) {\n", " var warnings = document.getElementById('mpl-warnings');\n", " if (warnings) {\n", " warnings.style.display = 'block';\n", " warnings.textContent =\n", " 'This browser does not support binary websocket messages. ' +\n", " 'Performance may be slow.';\n", " }\n", " }\n", "\n", " this.imageObj = new Image();\n", "\n", " this.context = undefined;\n", " this.message = undefined;\n", " this.canvas = undefined;\n", " this.rubberband_canvas = undefined;\n", " this.rubberband_context = undefined;\n", " this.format_dropdown = undefined;\n", "\n", " this.image_mode = 'full';\n", "\n", " this.root = document.createElement('div');\n", " this.root.setAttribute('style', 'display: inline-block');\n", " this._root_extra_style(this.root);\n", "\n", " parent_element.appendChild(this.root);\n", "\n", " this._init_header(this);\n", " this._init_canvas(this);\n", " this._init_toolbar(this);\n", "\n", " var fig = this;\n", "\n", " this.waiting = false;\n", "\n", " this.ws.onopen = function () {\n", " fig.send_message('supports_binary', { value: fig.supports_binary });\n", " fig.send_message('send_image_mode', {});\n", " if (fig.ratio !== 1) {\n", " fig.send_message('set_device_pixel_ratio', {\n", " device_pixel_ratio: fig.ratio,\n", " });\n", " }\n", " fig.send_message('refresh', {});\n", " };\n", "\n", " this.imageObj.onload = function () {\n", " if (fig.image_mode === 'full') {\n", " // Full images could contain transparency (where diff images\n", " // almost always do), so we need to clear the canvas so that\n", " // there is no ghosting.\n", " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", " };\n", "\n", " this.imageObj.onunload = function () {\n", " fig.ws.close();\n", " };\n", "\n", " this.ws.onmessage = this._make_on_message_function(this);\n", "\n", " this.ondownload = ondownload;\n", "};\n", "\n", "mpl.figure.prototype._init_header = function () {\n", " var titlebar = document.createElement('div');\n", " titlebar.classList =\n", " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", " var titletext = document.createElement('div');\n", " titletext.classList = 'ui-dialog-title';\n", " titletext.setAttribute(\n", " 'style',\n", " 'width: 100%; text-align: center; padding: 3px;'\n", " );\n", " titlebar.appendChild(titletext);\n", " this.root.appendChild(titlebar);\n", " this.header = titletext;\n", "};\n", "\n", "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", "\n", "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", "\n", "mpl.figure.prototype._init_canvas = function () {\n", " var fig = this;\n", "\n", " var canvas_div = (this.canvas_div = document.createElement('div'));\n", " canvas_div.setAttribute('tabindex', '0');\n", " canvas_div.setAttribute(\n", " 'style',\n", " 'border: 1px solid #ddd;' +\n", " 'box-sizing: content-box;' +\n", " 'clear: both;' +\n", " 'min-height: 1px;' +\n", " 'min-width: 1px;' +\n", " 'outline: 0;' +\n", " 'overflow: hidden;' +\n", " 'position: relative;' +\n", " 'resize: both;' +\n", " 'z-index: 2;'\n", " );\n", "\n", " function on_keyboard_event_closure(name) {\n", " return function (event) {\n", " return fig.key_event(event, name);\n", " };\n", " }\n", "\n", " canvas_div.addEventListener(\n", " 'keydown',\n", " on_keyboard_event_closure('key_press')\n", " );\n", " canvas_div.addEventListener(\n", " 'keyup',\n", " on_keyboard_event_closure('key_release')\n", " );\n", "\n", " this._canvas_extra_style(canvas_div);\n", " this.root.appendChild(canvas_div);\n", "\n", " var canvas = (this.canvas = document.createElement('canvas'));\n", " canvas.classList.add('mpl-canvas');\n", " canvas.setAttribute(\n", " 'style',\n", " 'box-sizing: content-box;' +\n", " 'pointer-events: none;' +\n", " 'position: relative;' +\n", " 'z-index: 0;'\n", " );\n", "\n", " this.context = canvas.getContext('2d');\n", "\n", " var backingStore =\n", " this.context.backingStorePixelRatio ||\n", " this.context.webkitBackingStorePixelRatio ||\n", " this.context.mozBackingStorePixelRatio ||\n", " this.context.msBackingStorePixelRatio ||\n", " this.context.oBackingStorePixelRatio ||\n", " this.context.backingStorePixelRatio ||\n", " 1;\n", "\n", " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", "\n", " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", " 'canvas'\n", " ));\n", " rubberband_canvas.setAttribute(\n", " 'style',\n", " 'box-sizing: content-box;' +\n", " 'left: 0;' +\n", " 'pointer-events: none;' +\n", " 'position: absolute;' +\n", " 'top: 0;' +\n", " 'z-index: 1;'\n", " );\n", "\n", " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", " if (this.ResizeObserver === undefined) {\n", " if (window.ResizeObserver !== undefined) {\n", " this.ResizeObserver = window.ResizeObserver;\n", " } else {\n", " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", " this.ResizeObserver = obs.ResizeObserver;\n", " }\n", " }\n", "\n", " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", " var nentries = entries.length;\n", " for (var i = 0; i < nentries; i++) {\n", " var entry = entries[i];\n", " var width, height;\n", " if (entry.contentBoxSize) {\n", " if (entry.contentBoxSize instanceof Array) {\n", " // Chrome 84 implements new version of spec.\n", " width = entry.contentBoxSize[0].inlineSize;\n", " height = entry.contentBoxSize[0].blockSize;\n", " } else {\n", " // Firefox implements old version of spec.\n", " width = entry.contentBoxSize.inlineSize;\n", " height = entry.contentBoxSize.blockSize;\n", " }\n", " } else {\n", " // Chrome <84 implements even older version of spec.\n", " width = entry.contentRect.width;\n", " height = entry.contentRect.height;\n", " }\n", "\n", " // Keep the size of the canvas and rubber band canvas in sync with\n", " // the canvas container.\n", " if (entry.devicePixelContentBoxSize) {\n", " // Chrome 84 implements new version of spec.\n", " canvas.setAttribute(\n", " 'width',\n", " entry.devicePixelContentBoxSize[0].inlineSize\n", " );\n", " canvas.setAttribute(\n", " 'height',\n", " entry.devicePixelContentBoxSize[0].blockSize\n", " );\n", " } else {\n", " canvas.setAttribute('width', width * fig.ratio);\n", " canvas.setAttribute('height', height * fig.ratio);\n", " }\n", " /* This rescales the canvas back to display pixels, so that it\n", " * appears correct on HiDPI screens. */\n", " canvas.style.width = width + 'px';\n", " canvas.style.height = height + 'px';\n", "\n", " rubberband_canvas.setAttribute('width', width);\n", " rubberband_canvas.setAttribute('height', height);\n", "\n", " // And update the size in Python. We ignore the initial 0/0 size\n", " // that occurs as the element is placed into the DOM, which should\n", " // otherwise not happen due to the minimum size styling.\n", " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", " fig.request_resize(width, height);\n", " }\n", " }\n", " });\n", " this.resizeObserverInstance.observe(canvas_div);\n", "\n", " function on_mouse_event_closure(name) {\n", " /* User Agent sniffing is bad, but WebKit is busted:\n", " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", " * The worst that happens here is that they get an extra browser\n", " * selection when dragging, if this check fails to catch them.\n", " */\n", " var UA = navigator.userAgent;\n", " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", " if(isWebKit) {\n", " return function (event) {\n", " /* This prevents the web browser from automatically changing to\n", " * the text insertion cursor when the button is pressed. We\n", " * want to control all of the cursor setting manually through\n", " * the 'cursor' event from matplotlib */\n", " event.preventDefault()\n", " return fig.mouse_event(event, name);\n", " };\n", " } else {\n", " return function (event) {\n", " return fig.mouse_event(event, name);\n", " };\n", " }\n", " }\n", "\n", " canvas_div.addEventListener(\n", " 'mousedown',\n", " on_mouse_event_closure('button_press')\n", " );\n", " canvas_div.addEventListener(\n", " 'mouseup',\n", " on_mouse_event_closure('button_release')\n", " );\n", " canvas_div.addEventListener(\n", " 'dblclick',\n", " on_mouse_event_closure('dblclick')\n", " );\n", " // Throttle sequential mouse events to 1 every 20ms.\n", " canvas_div.addEventListener(\n", " 'mousemove',\n", " on_mouse_event_closure('motion_notify')\n", " );\n", "\n", " canvas_div.addEventListener(\n", " 'mouseenter',\n", " on_mouse_event_closure('figure_enter')\n", " );\n", " canvas_div.addEventListener(\n", " 'mouseleave',\n", " on_mouse_event_closure('figure_leave')\n", " );\n", "\n", " canvas_div.addEventListener('wheel', function (event) {\n", " if (event.deltaY < 0) {\n", " event.step = 1;\n", " } else {\n", " event.step = -1;\n", " }\n", " on_mouse_event_closure('scroll')(event);\n", " });\n", "\n", " canvas_div.appendChild(canvas);\n", " canvas_div.appendChild(rubberband_canvas);\n", "\n", " this.rubberband_context = rubberband_canvas.getContext('2d');\n", " this.rubberband_context.strokeStyle = '#000000';\n", "\n", " this._resize_canvas = function (width, height, forward) {\n", " if (forward) {\n", " canvas_div.style.width = width + 'px';\n", " canvas_div.style.height = height + 'px';\n", " }\n", " };\n", "\n", " // Disable right mouse context menu.\n", " canvas_div.addEventListener('contextmenu', function (_e) {\n", " event.preventDefault();\n", " return false;\n", " });\n", "\n", " function set_focus() {\n", " canvas.focus();\n", " canvas_div.focus();\n", " }\n", "\n", " window.setTimeout(set_focus, 100);\n", "};\n", "\n", "mpl.figure.prototype._init_toolbar = function () {\n", " var fig = this;\n", "\n", " var toolbar = document.createElement('div');\n", " toolbar.classList = 'mpl-toolbar';\n", " this.root.appendChild(toolbar);\n", "\n", " function on_click_closure(name) {\n", " return function (_event) {\n", " return fig.toolbar_button_onclick(name);\n", " };\n", " }\n", "\n", " function on_mouseover_closure(tooltip) {\n", " return function (event) {\n", " if (!event.currentTarget.disabled) {\n", " return fig.toolbar_button_onmouseover(tooltip);\n", " }\n", " };\n", " }\n", "\n", " fig.buttons = {};\n", " var buttonGroup = document.createElement('div');\n", " buttonGroup.classList = 'mpl-button-group';\n", " for (var toolbar_ind in mpl.toolbar_items) {\n", " var name = mpl.toolbar_items[toolbar_ind][0];\n", " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", " var image = mpl.toolbar_items[toolbar_ind][2];\n", " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", "\n", " if (!name) {\n", " /* Instead of a spacer, we start a new button group. */\n", " if (buttonGroup.hasChildNodes()) {\n", " toolbar.appendChild(buttonGroup);\n", " }\n", " buttonGroup = document.createElement('div');\n", " buttonGroup.classList = 'mpl-button-group';\n", " continue;\n", " }\n", "\n", " var button = (fig.buttons[name] = document.createElement('button'));\n", " button.classList = 'mpl-widget';\n", " button.setAttribute('role', 'button');\n", " button.setAttribute('aria-disabled', 'false');\n", " button.addEventListener('click', on_click_closure(method_name));\n", " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", "\n", " var icon_img = document.createElement('img');\n", " icon_img.src = '_images/' + image + '.png';\n", " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", " icon_img.alt = tooltip;\n", " button.appendChild(icon_img);\n", "\n", " buttonGroup.appendChild(button);\n", " }\n", "\n", " if (buttonGroup.hasChildNodes()) {\n", " toolbar.appendChild(buttonGroup);\n", " }\n", "\n", " var fmt_picker = document.createElement('select');\n", " fmt_picker.classList = 'mpl-widget';\n", " toolbar.appendChild(fmt_picker);\n", " this.format_dropdown = fmt_picker;\n", "\n", " for (var ind in mpl.extensions) {\n", " var fmt = mpl.extensions[ind];\n", " var option = document.createElement('option');\n", " option.selected = fmt === mpl.default_extension;\n", " option.innerHTML = fmt;\n", " fmt_picker.appendChild(option);\n", " }\n", "\n", " var status_bar = document.createElement('span');\n", " status_bar.classList = 'mpl-message';\n", " toolbar.appendChild(status_bar);\n", " this.message = status_bar;\n", "};\n", "\n", "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", " // which will in turn request a refresh of the image.\n", " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", "};\n", "\n", "mpl.figure.prototype.send_message = function (type, properties) {\n", " properties['type'] = type;\n", " properties['figure_id'] = this.id;\n", " this.ws.send(JSON.stringify(properties));\n", "};\n", "\n", "mpl.figure.prototype.send_draw_message = function () {\n", " if (!this.waiting) {\n", " this.waiting = true;\n", " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", " }\n", "};\n", "\n", "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", " var format_dropdown = fig.format_dropdown;\n", " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", " fig.ondownload(fig, format);\n", "};\n", "\n", "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", " var size = msg['size'];\n", " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", " fig._resize_canvas(size[0], size[1], msg['forward']);\n", " fig.send_message('refresh', {});\n", " }\n", "};\n", "\n", "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", " var x0 = msg['x0'] / fig.ratio;\n", " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", " var x1 = msg['x1'] / fig.ratio;\n", " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", " x0 = Math.floor(x0) + 0.5;\n", " y0 = Math.floor(y0) + 0.5;\n", " x1 = Math.floor(x1) + 0.5;\n", " y1 = Math.floor(y1) + 0.5;\n", " var min_x = Math.min(x0, x1);\n", " var min_y = Math.min(y0, y1);\n", " var width = Math.abs(x1 - x0);\n", " var height = Math.abs(y1 - y0);\n", "\n", " fig.rubberband_context.clearRect(\n", " 0,\n", " 0,\n", " fig.canvas.width / fig.ratio,\n", " fig.canvas.height / fig.ratio\n", " );\n", "\n", " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", "};\n", "\n", "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", " // Updates the figure title.\n", " fig.header.textContent = msg['label'];\n", "};\n", "\n", "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", " fig.canvas_div.style.cursor = msg['cursor'];\n", "};\n", "\n", "mpl.figure.prototype.handle_message = function (fig, msg) {\n", " fig.message.textContent = msg['message'];\n", "};\n", "\n", "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", " // Request the server to send over a new figure.\n", " fig.send_draw_message();\n", "};\n", "\n", "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", " fig.image_mode = msg['mode'];\n", "};\n", "\n", "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", " for (var key in msg) {\n", " if (!(key in fig.buttons)) {\n", " continue;\n", " }\n", " fig.buttons[key].disabled = !msg[key];\n", " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", " }\n", "};\n", "\n", "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", " if (msg['mode'] === 'PAN') {\n", " fig.buttons['Pan'].classList.add('active');\n", " fig.buttons['Zoom'].classList.remove('active');\n", " } else if (msg['mode'] === 'ZOOM') {\n", " fig.buttons['Pan'].classList.remove('active');\n", " fig.buttons['Zoom'].classList.add('active');\n", " } else {\n", " fig.buttons['Pan'].classList.remove('active');\n", " fig.buttons['Zoom'].classList.remove('active');\n", " }\n", "};\n", "\n", "mpl.figure.prototype.updated_canvas_event = function () {\n", " // Called whenever the canvas gets updated.\n", " this.send_message('ack', {});\n", "};\n", "\n", "// A function to construct a web socket function for onmessage handling.\n", "// Called in the figure constructor.\n", "mpl.figure.prototype._make_on_message_function = function (fig) {\n", " return function socket_on_message(evt) {\n", " if (evt.data instanceof Blob) {\n", " var img = evt.data;\n", " if (img.type !== 'image/png') {\n", " /* FIXME: We get \"Resource interpreted as Image but\n", " * transferred with MIME type text/plain:\" errors on\n", " * Chrome. But how to set the MIME type? It doesn't seem\n", " * to be part of the websocket stream */\n", " img.type = 'image/png';\n", " }\n", "\n", " /* Free the memory for the previous frames */\n", " if (fig.imageObj.src) {\n", " (window.URL || window.webkitURL).revokeObjectURL(\n", " fig.imageObj.src\n", " );\n", " }\n", "\n", " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", " img\n", " );\n", " fig.updated_canvas_event();\n", " fig.waiting = false;\n", " return;\n", " } else if (\n", " typeof evt.data === 'string' &&\n", " evt.data.slice(0, 21) === 'data:image/png;base64'\n", " ) {\n", " fig.imageObj.src = evt.data;\n", " fig.updated_canvas_event();\n", " fig.waiting = false;\n", " return;\n", " }\n", "\n", " var msg = JSON.parse(evt.data);\n", " var msg_type = msg['type'];\n", "\n", " // Call the \"handle_{type}\" callback, which takes\n", " // the figure and JSON message as its only arguments.\n", " try {\n", " var callback = fig['handle_' + msg_type];\n", " } catch (e) {\n", " console.log(\n", " \"No handler for the '\" + msg_type + \"' message type: \",\n", " msg\n", " );\n", " return;\n", " }\n", "\n", " if (callback) {\n", " try {\n", " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", " callback(fig, msg);\n", " } catch (e) {\n", " console.log(\n", " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", " e,\n", " e.stack,\n", " msg\n", " );\n", " }\n", " }\n", " };\n", "};\n", "\n", "\n", "/*\n", " * return a copy of an object with only non-object keys\n", " * we need this to avoid circular references\n", " * https://stackoverflow.com/a/24161582/3208463\n", " */\n", "function simpleKeys(original) {\n", " return Object.keys(original).reduce(function (obj, key) {\n", " if (typeof original[key] !== 'object') {\n", " obj[key] = original[key];\n", " }\n", " return obj;\n", " }, {});\n", "}\n", "\n", "mpl.figure.prototype.mouse_event = function (event, name) {\n", " if (name === 'button_press') {\n", " this.canvas.focus();\n", " this.canvas_div.focus();\n", " }\n", "\n", " // from https://stackoverflow.com/q/1114465\n", " var boundingRect = this.canvas.getBoundingClientRect();\n", " var x = (event.clientX - boundingRect.left) * this.ratio;\n", " var y = (event.clientY - boundingRect.top) * this.ratio;\n", "\n", " this.send_message(name, {\n", " x: x,\n", " y: y,\n", " button: event.button,\n", " step: event.step,\n", " guiEvent: simpleKeys(event),\n", " });\n", "\n", " return false;\n", "};\n", "\n", "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", " // Handle any extra behaviour associated with a key event\n", "};\n", "\n", "mpl.figure.prototype.key_event = function (event, name) {\n", " // Prevent repeat events\n", " if (name === 'key_press') {\n", " if (event.key === this._key) {\n", " return;\n", " } else {\n", " this._key = event.key;\n", " }\n", " }\n", " if (name === 'key_release') {\n", " this._key = null;\n", " }\n", "\n", " var value = '';\n", " if (event.ctrlKey && event.key !== 'Control') {\n", " value += 'ctrl+';\n", " }\n", " else if (event.altKey && event.key !== 'Alt') {\n", " value += 'alt+';\n", " }\n", " else if (event.shiftKey && event.key !== 'Shift') {\n", " value += 'shift+';\n", " }\n", "\n", " value += 'k' + event.key;\n", "\n", " this._key_event_extra(event, name);\n", "\n", " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", " return false;\n", "};\n", "\n", "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", " if (name === 'download') {\n", " this.handle_save(this, null);\n", " } else {\n", " this.send_message('toolbar_button', { name: name });\n", " }\n", "};\n", "\n", "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", " this.message.textContent = tooltip;\n", "};\n", "\n", "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", "// prettier-ignore\n", "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", "\n", "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", "\n", "mpl.default_extension = \"png\";/* global mpl */\n", "\n", "var comm_websocket_adapter = function (comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", " // object with the appropriate methods. Currently this is a non binary\n", " // socket, so there is still some room for performance tuning.\n", " var ws = {};\n", "\n", " ws.binaryType = comm.kernel.ws.binaryType;\n", " ws.readyState = comm.kernel.ws.readyState;\n", " function updateReadyState(_event) {\n", " if (comm.kernel.ws) {\n", " ws.readyState = comm.kernel.ws.readyState;\n", " } else {\n", " ws.readyState = 3; // Closed state.\n", " }\n", " }\n", " comm.kernel.ws.addEventListener('open', updateReadyState);\n", " comm.kernel.ws.addEventListener('close', updateReadyState);\n", " comm.kernel.ws.addEventListener('error', updateReadyState);\n", "\n", " ws.close = function () {\n", " comm.close();\n", " };\n", " ws.send = function (m) {\n", " //console.log('sending', m);\n", " comm.send(m);\n", " };\n", " // Register the callback with on_msg.\n", " comm.on_msg(function (msg) {\n", " //console.log('receiving', msg['content']['data'], msg);\n", " var data = msg['content']['data'];\n", " if (data['blob'] !== undefined) {\n", " data = {\n", " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", " };\n", " }\n", " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", " ws.onmessage(data);\n", " });\n", " return ws;\n", "};\n", "\n", "mpl.mpl_figure_comm = function (comm, msg) {\n", " // This is the function which gets called when the mpl process\n", " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", "\n", " var id = msg.content.data.id;\n", " // Get hold of the div created by the display call when the Comm\n", " // socket was opened in Python.\n", " var element = document.getElementById(id);\n", " var ws_proxy = comm_websocket_adapter(comm);\n", "\n", " function ondownload(figure, _format) {\n", " window.open(figure.canvas.toDataURL());\n", " }\n", "\n", " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", "\n", " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", " // web socket which is closed, not our websocket->open comm proxy.\n", " ws_proxy.onopen();\n", "\n", " fig.parent_element = element;\n", " fig.cell_info = mpl.find_output_cell(\"
\");\n", " if (!fig.cell_info) {\n", " console.error('Failed to find cell for figure', id, fig);\n", " return;\n", " }\n", " fig.cell_info[0].output_area.element.on(\n", " 'cleared',\n", " { fig: fig },\n", " fig._remove_fig_handler\n", " );\n", "};\n", "\n", "mpl.figure.prototype.handle_close = function (fig, msg) {\n", " var width = fig.canvas.width / fig.ratio;\n", " fig.cell_info[0].output_area.element.off(\n", " 'cleared',\n", " fig._remove_fig_handler\n", " );\n", " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", "\n", " // Update the output cell to use the data from the current canvas.\n", " fig.push_to_output();\n", " var dataURL = fig.canvas.toDataURL();\n", " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", " // the notebook keyboard shortcuts fail.\n", " IPython.keyboard_manager.enable();\n", " fig.parent_element.innerHTML =\n", " '';\n", " fig.close_ws(fig, msg);\n", "};\n", "\n", "mpl.figure.prototype.close_ws = function (fig, msg) {\n", " fig.send_message('closing', msg);\n", " // fig.ws.close()\n", "};\n", "\n", "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", " // Turn the data on the canvas into data in the output cell.\n", " var width = this.canvas.width / this.ratio;\n", " var dataURL = this.canvas.toDataURL();\n", " this.cell_info[1]['text/html'] =\n", " '';\n", "};\n", "\n", "mpl.figure.prototype.updated_canvas_event = function () {\n", " // Tell IPython that the notebook contents must change.\n", " IPython.notebook.set_dirty(true);\n", " this.send_message('ack', {});\n", " var fig = this;\n", " // Wait a second, then push the new image to the DOM so\n", " // that it is saved nicely (might be nice to debounce this).\n", " setTimeout(function () {\n", " fig.push_to_output();\n", " }, 1000);\n", "};\n", "\n", "mpl.figure.prototype._init_toolbar = function () {\n", " var fig = this;\n", "\n", " var toolbar = document.createElement('div');\n", " toolbar.classList = 'btn-toolbar';\n", " this.root.appendChild(toolbar);\n", "\n", " function on_click_closure(name) {\n", " return function (_event) {\n", " return fig.toolbar_button_onclick(name);\n", " };\n", " }\n", "\n", " function on_mouseover_closure(tooltip) {\n", " return function (event) {\n", " if (!event.currentTarget.disabled) {\n", " return fig.toolbar_button_onmouseover(tooltip);\n", " }\n", " };\n", " }\n", "\n", " fig.buttons = {};\n", " var buttonGroup = document.createElement('div');\n", " buttonGroup.classList = 'btn-group';\n", " var button;\n", " for (var toolbar_ind in mpl.toolbar_items) {\n", " var name = mpl.toolbar_items[toolbar_ind][0];\n", " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", " var image = mpl.toolbar_items[toolbar_ind][2];\n", " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", "\n", " if (!name) {\n", " /* Instead of a spacer, we start a new button group. */\n", " if (buttonGroup.hasChildNodes()) {\n", " toolbar.appendChild(buttonGroup);\n", " }\n", " buttonGroup = document.createElement('div');\n", " buttonGroup.classList = 'btn-group';\n", " continue;\n", " }\n", "\n", " button = fig.buttons[name] = document.createElement('button');\n", " button.classList = 'btn btn-default';\n", " button.href = '#';\n", " button.title = name;\n", " button.innerHTML = '';\n", " button.addEventListener('click', on_click_closure(method_name));\n", " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", " buttonGroup.appendChild(button);\n", " }\n", "\n", " if (buttonGroup.hasChildNodes()) {\n", " toolbar.appendChild(buttonGroup);\n", " }\n", "\n", " // Add the status bar.\n", " var status_bar = document.createElement('span');\n", " status_bar.classList = 'mpl-message pull-right';\n", " toolbar.appendChild(status_bar);\n", " this.message = status_bar;\n", "\n", " // Add the close button to the window.\n", " var buttongrp = document.createElement('div');\n", " buttongrp.classList = 'btn-group inline pull-right';\n", " button = document.createElement('button');\n", " button.classList = 'btn btn-mini btn-primary';\n", " button.href = '#';\n", " button.title = 'Stop Interaction';\n", " button.innerHTML = '';\n", " button.addEventListener('click', function (_evt) {\n", " fig.handle_close(fig, {});\n", " });\n", " button.addEventListener(\n", " 'mouseover',\n", " on_mouseover_closure('Stop Interaction')\n", " );\n", " buttongrp.appendChild(button);\n", " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", "};\n", "\n", "mpl.figure.prototype._remove_fig_handler = function (event) {\n", " var fig = event.data.fig;\n", " if (event.target !== this) {\n", " // Ignore bubbled events from children.\n", " return;\n", " }\n", " fig.close_ws(fig, {});\n", "};\n", "\n", "mpl.figure.prototype._root_extra_style = function (el) {\n", " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", "};\n", "\n", "mpl.figure.prototype._canvas_extra_style = function (el) {\n", " // this is important to make the div 'focusable\n", " el.setAttribute('tabindex', 0);\n", " // reach out to IPython and tell the keyboard manager to turn it's self\n", " // off when our div gets focus\n", "\n", " // location in version 3\n", " if (IPython.notebook.keyboard_manager) {\n", " IPython.notebook.keyboard_manager.register_events(el);\n", " } else {\n", " // location in version 2\n", " IPython.keyboard_manager.register_events(el);\n", " }\n", "};\n", "\n", "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", " // Check for shift+enter\n", " if (event.shiftKey && event.which === 13) {\n", " this.canvas_div.blur();\n", " // select the cell after this one\n", " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", " IPython.notebook.select(index + 1);\n", " }\n", "};\n", "\n", "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", " fig.ondownload(fig, null);\n", "};\n", "\n", "mpl.find_output_cell = function (html_output) {\n", " // Return the cell and output element which can be found *uniquely* in the notebook.\n", " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", " // IPython event is triggered only after the cells have been serialised, which for\n", " // our purposes (turning an active figure into a static one), is too late.\n", " var cells = IPython.notebook.get_cells();\n", " var ncells = cells.length;\n", " for (var i = 0; i < ncells; i++) {\n", " var cell = cells[i];\n", " if (cell.cell_type === 'code') {\n", " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", " var data = cell.output_area.outputs[j];\n", " if (data.data) {\n", " // IPython >= 3 moved mimebundle to data attribute of output\n", " data = data.data;\n", " }\n", " if (data['text/html'] === html_output) {\n", " return [cell, data, j];\n", " }\n", " }\n", " }\n", " }\n", "};\n", "\n", "// Register the function which deals with the matplotlib target/channel.\n", "// The kernel may be null if the page has been refreshed.\n", "if (IPython.notebook.kernel !== null) {\n", " IPython.notebook.kernel.comm_manager.register_target(\n", " 'matplotlib',\n", " mpl.mpl_figure_comm\n", " );\n", "}\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import sys\n", "sys.path.insert(1, r'./../functions') # add to pythonpath\n", "%matplotlib notebook\n", "import matplotlib.pyplot as plt\n", "from CCS import CCS\n", "\n", "R = RZ*RY*RX\n", "R = sym.lambdify((a, b, g), R, 'numpy')\n", "\n", "alpha = np.pi/2\n", "beta = np.pi/2\n", "gamma = np.pi/2\n", "\n", "R = R(alpha, beta, gamma)\n", "\n", "e1 = np.array([[1, 0, 0]])\n", "e2 = np.array([[0, 1, 0]])\n", "e3 = np.array([[0, 0, 1]])\n", "\n", "basis = np.vstack((e1, e2, e3))\n", "basisRot = R @ basis\n", "CCS(Oijk=np.array([0, 0, 0]), Oxyz=np.array([0, 0, 0]),\n", " ijk=basis.T, xyz=basisRot.T, vector=False)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Examining the matrix above and the correspondent previous figure, one can see they agree: the rotated $x$ axis (first column of the above matrix) has value -1 in the $\\mathbf{Z}$ direction $[0,0,-1]$, the rotated $y$ axis (second column) is at the $\\mathbf{Y}$ direction $[0,1,0]$, and the rotated $z$ axis (third column) is at the $\\mathbf{X}$ direction $[1,0,0]$.\n", "\n", "We also can calculate the sequence of elemental rotations around the $x$, $y$, $z$ axes of the rotating $xyz$ coordinate system illustrated in the next figure. \n", "
\n", "
\n", "rotations
Figure. Sequence of elemental rotations of a second $xyz$ local coordinate system around each axis, $x$, $y$, $z$, of the rotating $xyz$ coordinate system.
\n", "
\n", "\n", "Likewise, this sequence of elemental rotations (each one of the local coordinate system with respect to the rotating local coordinate system) is mathematically represented by a multiplication between the rotation matrices (which are the inverse of the matrices for the rotations around $\\mathbf{X,Y,Z}$ as we saw earlier): \n", "
\n", "\n", "\\begin{equation}\n", "\\begin{array}{ll}\n", "\\mathbf{R}_{\\mathbf{lG},\\,xyz} & = \\mathbf{R_{z}} \\mathbf{R_{y}} \\mathbf{R_{x}} \\\\\n", "\\\\\n", "& = \\begin{bmatrix}\n", "\\cos\\gamma & \\sin\\gamma & 0\\\\\n", "-\\sin\\gamma & \\cos\\gamma & 0 \\\\\n", "0 & 0 & 1\n", "\\end{bmatrix}\n", "\\begin{bmatrix}\n", "\\cos\\beta & 0 & -\\sin\\beta \\\\\n", "0 & 1 & 0 \\\\\n", "\\sin\\beta & 0 & \\cos\\beta\n", "\\end{bmatrix}\n", "\\begin{bmatrix}\n", "1 & 0 & 0 \\\\\n", "0 & \\cos\\alpha & \\sin\\alpha \\\\\n", "0 & -\\sin\\alpha & \\cos\\alpha\n", "\\end{bmatrix} \\\\\n", "\\\\\n", "& =\n", "\\begin{bmatrix}\n", "\\cos\\beta\\:\\cos\\gamma \\;&\\;\n", "\\sin\\alpha\\:\\sin\\beta\\:\\cos\\gamma+\\cos\\alpha\\:\\sin\\gamma \\;&\\;\n", "\\cos\\alpha\\:\\sin\\beta\\:\\cos\\gamma-\\sin\\alpha\\:\\sin\\gamma \\;\\;\\; \\\\\n", "-\\cos\\beta\\:\\sin\\gamma \\;&\\;\n", "-\\sin\\alpha\\:\\sin\\beta\\:\\sin\\gamma+\\cos\\alpha\\:\\cos\\gamma \\;&\\;\n", "\\cos\\alpha\\:\\sin\\beta\\:\\sin\\gamma+\\sin\\alpha\\:\\cos\\gamma \\;\\;\\; \\\\\n", "\\sin\\beta \\;&\\; -\\sin\\alpha\\:\\cos\\beta \\;&\\; \\cos\\alpha\\:\\cos\\beta \\;\\;\\;\n", "\\end{bmatrix} \n", "\\end{array}\n", "\\end{equation}\n", "\n", "\n", "As before, the order of the matrices is from right to left. \n", "\n", "Once again, we can check this matrix multiplication using [Sympy](http://sympy.org/en/index.html):" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:08.869048Z", "start_time": "2021-11-18T00:05:08.860266Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle \\mathbf{R}_{\\mathbf{lG},\\,xyz}=\\left[\\begin{matrix}\\cos{\\left(\\beta \\right)} \\cos{\\left(\\gamma \\right)} & \\sin{\\left(\\alpha \\right)} \\sin{\\left(\\beta \\right)} \\cos{\\left(\\gamma \\right)} + \\sin{\\left(\\gamma \\right)} \\cos{\\left(\\alpha \\right)} & \\sin{\\left(\\alpha \\right)} \\sin{\\left(\\gamma \\right)} - \\sin{\\left(\\beta \\right)} \\cos{\\left(\\alpha \\right)} \\cos{\\left(\\gamma \\right)}\\\\- \\sin{\\left(\\gamma \\right)} \\cos{\\left(\\beta \\right)} & - \\sin{\\left(\\alpha \\right)} \\sin{\\left(\\beta \\right)} \\sin{\\left(\\gamma \\right)} + \\cos{\\left(\\alpha \\right)} \\cos{\\left(\\gamma \\right)} & \\sin{\\left(\\alpha \\right)} \\cos{\\left(\\gamma \\right)} + \\sin{\\left(\\beta \\right)} \\sin{\\left(\\gamma \\right)} \\cos{\\left(\\alpha \\right)}\\\\\\sin{\\left(\\beta \\right)} & - \\sin{\\left(\\alpha \\right)} \\cos{\\left(\\beta \\right)} & \\cos{\\left(\\alpha \\right)} \\cos{\\left(\\beta \\right)}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a, b, g = sym.symbols('alpha, beta, gamma')\n", "# Elemental rotation matrices of xyz (local):\n", "Rx = sym.Matrix([[1, 0, 0],\n", " [0, cos(a), sin(a)],\n", " [0, -sin(a), cos(a)]])\n", "Ry = sym.Matrix([[cos(b), 0, -sin(b)],\n", " [0, 1, 0], [sin(b), 0, cos(b)]])\n", "Rz = sym.Matrix([[cos(g), sin(g), 0],\n", " [-sin(g), cos(g), 0],\n", " [0, 0, 1]])\n", "# Rotation matrix of xyz' in relation to xyz:\n", "Rxyz = Rz @ Ry @ Rx\n", "Math(r'\\mathbf{R}_{\\mathbf{lG},\\,xyz}=' + sym.latex(Rxyz, mat_str='matrix'))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "For instance, let's calculate the numerical rotation matrix for these sequential elemental rotations by $90^o$ around $x,y,z$:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:09.183585Z", "start_time": "2021-11-18T00:05:09.175449Z" }, "scrolled": true, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle \\mathbf{R}_{\\mathbf{lG},\\,xyz\\,}(90^o, 90^o, 90^o) =\\left[\\begin{matrix}0 & 0 & 1.0\\\\0 & -1.0 & 0\\\\1.0 & 0 & 0\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "R = sym.lambdify((a, b, g), Rxyz, 'numpy')\n", "R = R(np.pi/2, np.pi/2, np.pi/2)\n", "display(Math(r'\\mathbf{R}_{\\mathbf{lG},\\,xyz\\,}(90^o, 90^o, 90^o) =' + \\\n", " sym.latex(sym.Matrix(R).n(3, chop=True))))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Once again, let's compare the above matrix and the correspondent previous figure to see if it makes sense. But remember that this matrix is the Global-to-local rotation matrix, $\\mathbf{R}_{\\mathbf{lG},\\,xyz}$, where the coordinates of the local basis' versors are rows, not columns, in this matrix. With this detail in mind, one can see that the previous figure and matrix also agree: the rotated $x$ axis (first row of the above matrix) is at the $\\mathbf{Z}$ direction $[0,0,1]$, the rotated $y$ axis (second row) is at the $\\mathbf{-Y}$ direction $[0,-1,0]$, and the rotated $z$ axis (third row) is at the $\\mathbf{X}$ direction $[1,0,0]$.\n", "\n", "In fact, this example didn't serve to distinguish versors as rows or columns because the $\\mathbf{R}_{\\mathbf{lG},\\,xyz}$ matrix above is symmetric! \n", "Let's look on the resultant matrix for the example above after only the first two rotations, $\\mathbf{R}_{\\mathbf{lG},\\,xy}$ to understand this difference: " ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:09.491586Z", "start_time": "2021-11-18T00:05:09.484991Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle \\mathbf{R}_{\\mathbf{lG},\\,xy\\,}(90^o, 90^o) =\\left[\\begin{matrix}0 & 1.0 & 0\\\\0 & 0 & 1.0\\\\1.0 & 0 & 0\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "Rxy = Ry*Rx\n", "R = sym.lambdify((a, b), Rxy, 'numpy')\n", "R = R(np.pi/2, np.pi/2)\n", "display(Math(r'\\mathbf{R}_{\\mathbf{lG},\\,xy\\,}(90^o, 90^o) =' + \\\n", " sym.latex(sym.Matrix(R).n(3, chop=True))))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Comparing this matrix with the third plot in the figure, we see that the coordinates of versor $x$ in the Global coordinate system are $[0,1,0]$, i.e., local axis $x$ is aligned with Global axis $Y$, and this versor is indeed the first row, not first column, of the matrix above. Confer the other two rows. \n", "\n", "What are then in the columns of the local-to-Global rotation matrix? \n", "The columns are the coordinates of Global basis' versors in the local coordinate system! For example, the first column of the matrix above is the coordinates of $X$, which is aligned with $z$: $[0,0,1]$." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Below you can test any sequence of rotation, around the local coordinates. Just change the matrix R, and the angles of the variables $\\alpha$, $\\beta$ and $\\gamma$. In the example below is the rotation around the local basis, in the sequence x,y,z, with the angles $\\alpha=\\pi/3$ rad, $\\beta=\\pi/4$ rad and $\\gamma=\\pi/2$ rad." ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:10.145737Z", "start_time": "2021-11-18T00:05:10.084308Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "application/javascript": [ "/* Put everything inside the global mpl namespace */\n", "/* global mpl */\n", "window.mpl = {};\n", "\n", "mpl.get_websocket_type = function () {\n", " if (typeof WebSocket !== 'undefined') {\n", " return WebSocket;\n", " } else if (typeof MozWebSocket !== 'undefined') {\n", " return MozWebSocket;\n", " } else {\n", " alert(\n", " 'Your browser does not have WebSocket support. ' +\n", " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", " 'Firefox 4 and 5 are also supported but you ' +\n", " 'have to enable WebSockets in about:config.'\n", " );\n", " }\n", "};\n", "\n", "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", " this.id = figure_id;\n", "\n", " this.ws = websocket;\n", "\n", " this.supports_binary = this.ws.binaryType !== undefined;\n", "\n", " if (!this.supports_binary) {\n", " var warnings = document.getElementById('mpl-warnings');\n", " if (warnings) {\n", " warnings.style.display = 'block';\n", " warnings.textContent =\n", " 'This browser does not support binary websocket messages. ' +\n", " 'Performance may be slow.';\n", " }\n", " }\n", "\n", " this.imageObj = new Image();\n", "\n", " this.context = undefined;\n", " this.message = undefined;\n", " this.canvas = undefined;\n", " this.rubberband_canvas = undefined;\n", " this.rubberband_context = undefined;\n", " this.format_dropdown = undefined;\n", "\n", " this.image_mode = 'full';\n", "\n", " this.root = document.createElement('div');\n", " this.root.setAttribute('style', 'display: inline-block');\n", " this._root_extra_style(this.root);\n", "\n", " parent_element.appendChild(this.root);\n", "\n", " this._init_header(this);\n", " this._init_canvas(this);\n", " this._init_toolbar(this);\n", "\n", " var fig = this;\n", "\n", " this.waiting = false;\n", "\n", " this.ws.onopen = function () {\n", " fig.send_message('supports_binary', { value: fig.supports_binary });\n", " fig.send_message('send_image_mode', {});\n", " if (fig.ratio !== 1) {\n", " fig.send_message('set_device_pixel_ratio', {\n", " device_pixel_ratio: fig.ratio,\n", " });\n", " }\n", " fig.send_message('refresh', {});\n", " };\n", "\n", " this.imageObj.onload = function () {\n", " if (fig.image_mode === 'full') {\n", " // Full images could contain transparency (where diff images\n", " // almost always do), so we need to clear the canvas so that\n", " // there is no ghosting.\n", " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", " };\n", "\n", " this.imageObj.onunload = function () {\n", " fig.ws.close();\n", " };\n", "\n", " this.ws.onmessage = this._make_on_message_function(this);\n", "\n", " this.ondownload = ondownload;\n", "};\n", "\n", "mpl.figure.prototype._init_header = function () {\n", " var titlebar = document.createElement('div');\n", " titlebar.classList =\n", " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", " var titletext = document.createElement('div');\n", " titletext.classList = 'ui-dialog-title';\n", " titletext.setAttribute(\n", " 'style',\n", " 'width: 100%; text-align: center; padding: 3px;'\n", " );\n", " titlebar.appendChild(titletext);\n", " this.root.appendChild(titlebar);\n", " this.header = titletext;\n", "};\n", "\n", "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", "\n", "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", "\n", "mpl.figure.prototype._init_canvas = function () {\n", " var fig = this;\n", "\n", " var canvas_div = (this.canvas_div = document.createElement('div'));\n", " canvas_div.setAttribute('tabindex', '0');\n", " canvas_div.setAttribute(\n", " 'style',\n", " 'border: 1px solid #ddd;' +\n", " 'box-sizing: content-box;' +\n", " 'clear: both;' +\n", " 'min-height: 1px;' +\n", " 'min-width: 1px;' +\n", " 'outline: 0;' +\n", " 'overflow: hidden;' +\n", " 'position: relative;' +\n", " 'resize: both;' +\n", " 'z-index: 2;'\n", " );\n", "\n", " function on_keyboard_event_closure(name) {\n", " return function (event) {\n", " return fig.key_event(event, name);\n", " };\n", " }\n", "\n", " canvas_div.addEventListener(\n", " 'keydown',\n", " on_keyboard_event_closure('key_press')\n", " );\n", " canvas_div.addEventListener(\n", " 'keyup',\n", " on_keyboard_event_closure('key_release')\n", " );\n", "\n", " this._canvas_extra_style(canvas_div);\n", " this.root.appendChild(canvas_div);\n", "\n", " var canvas = (this.canvas = document.createElement('canvas'));\n", " canvas.classList.add('mpl-canvas');\n", " canvas.setAttribute(\n", " 'style',\n", " 'box-sizing: content-box;' +\n", " 'pointer-events: none;' +\n", " 'position: relative;' +\n", " 'z-index: 0;'\n", " );\n", "\n", " this.context = canvas.getContext('2d');\n", "\n", " var backingStore =\n", " this.context.backingStorePixelRatio ||\n", " this.context.webkitBackingStorePixelRatio ||\n", " this.context.mozBackingStorePixelRatio ||\n", " this.context.msBackingStorePixelRatio ||\n", " this.context.oBackingStorePixelRatio ||\n", " this.context.backingStorePixelRatio ||\n", " 1;\n", "\n", " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", "\n", " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", " 'canvas'\n", " ));\n", " rubberband_canvas.setAttribute(\n", " 'style',\n", " 'box-sizing: content-box;' +\n", " 'left: 0;' +\n", " 'pointer-events: none;' +\n", " 'position: absolute;' +\n", " 'top: 0;' +\n", " 'z-index: 1;'\n", " );\n", "\n", " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", " if (this.ResizeObserver === undefined) {\n", " if (window.ResizeObserver !== undefined) {\n", " this.ResizeObserver = window.ResizeObserver;\n", " } else {\n", " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", " this.ResizeObserver = obs.ResizeObserver;\n", " }\n", " }\n", "\n", " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", " var nentries = entries.length;\n", " for (var i = 0; i < nentries; i++) {\n", " var entry = entries[i];\n", " var width, height;\n", " if (entry.contentBoxSize) {\n", " if (entry.contentBoxSize instanceof Array) {\n", " // Chrome 84 implements new version of spec.\n", " width = entry.contentBoxSize[0].inlineSize;\n", " height = entry.contentBoxSize[0].blockSize;\n", " } else {\n", " // Firefox implements old version of spec.\n", " width = entry.contentBoxSize.inlineSize;\n", " height = entry.contentBoxSize.blockSize;\n", " }\n", " } else {\n", " // Chrome <84 implements even older version of spec.\n", " width = entry.contentRect.width;\n", " height = entry.contentRect.height;\n", " }\n", "\n", " // Keep the size of the canvas and rubber band canvas in sync with\n", " // the canvas container.\n", " if (entry.devicePixelContentBoxSize) {\n", " // Chrome 84 implements new version of spec.\n", " canvas.setAttribute(\n", " 'width',\n", " entry.devicePixelContentBoxSize[0].inlineSize\n", " );\n", " canvas.setAttribute(\n", " 'height',\n", " entry.devicePixelContentBoxSize[0].blockSize\n", " );\n", " } else {\n", " canvas.setAttribute('width', width * fig.ratio);\n", " canvas.setAttribute('height', height * fig.ratio);\n", " }\n", " /* This rescales the canvas back to display pixels, so that it\n", " * appears correct on HiDPI screens. */\n", " canvas.style.width = width + 'px';\n", " canvas.style.height = height + 'px';\n", "\n", " rubberband_canvas.setAttribute('width', width);\n", " rubberband_canvas.setAttribute('height', height);\n", "\n", " // And update the size in Python. We ignore the initial 0/0 size\n", " // that occurs as the element is placed into the DOM, which should\n", " // otherwise not happen due to the minimum size styling.\n", " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", " fig.request_resize(width, height);\n", " }\n", " }\n", " });\n", " this.resizeObserverInstance.observe(canvas_div);\n", "\n", " function on_mouse_event_closure(name) {\n", " /* User Agent sniffing is bad, but WebKit is busted:\n", " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", " * The worst that happens here is that they get an extra browser\n", " * selection when dragging, if this check fails to catch them.\n", " */\n", " var UA = navigator.userAgent;\n", " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", " if(isWebKit) {\n", " return function (event) {\n", " /* This prevents the web browser from automatically changing to\n", " * the text insertion cursor when the button is pressed. We\n", " * want to control all of the cursor setting manually through\n", " * the 'cursor' event from matplotlib */\n", " event.preventDefault()\n", " return fig.mouse_event(event, name);\n", " };\n", " } else {\n", " return function (event) {\n", " return fig.mouse_event(event, name);\n", " };\n", " }\n", " }\n", "\n", " canvas_div.addEventListener(\n", " 'mousedown',\n", " on_mouse_event_closure('button_press')\n", " );\n", " canvas_div.addEventListener(\n", " 'mouseup',\n", " on_mouse_event_closure('button_release')\n", " );\n", " canvas_div.addEventListener(\n", " 'dblclick',\n", " on_mouse_event_closure('dblclick')\n", " );\n", " // Throttle sequential mouse events to 1 every 20ms.\n", " canvas_div.addEventListener(\n", " 'mousemove',\n", " on_mouse_event_closure('motion_notify')\n", " );\n", "\n", " canvas_div.addEventListener(\n", " 'mouseenter',\n", " on_mouse_event_closure('figure_enter')\n", " );\n", " canvas_div.addEventListener(\n", " 'mouseleave',\n", " on_mouse_event_closure('figure_leave')\n", " );\n", "\n", " canvas_div.addEventListener('wheel', function (event) {\n", " if (event.deltaY < 0) {\n", " event.step = 1;\n", " } else {\n", " event.step = -1;\n", " }\n", " on_mouse_event_closure('scroll')(event);\n", " });\n", "\n", " canvas_div.appendChild(canvas);\n", " canvas_div.appendChild(rubberband_canvas);\n", "\n", " this.rubberband_context = rubberband_canvas.getContext('2d');\n", " this.rubberband_context.strokeStyle = '#000000';\n", "\n", " this._resize_canvas = function (width, height, forward) {\n", " if (forward) {\n", " canvas_div.style.width = width + 'px';\n", " canvas_div.style.height = height + 'px';\n", " }\n", " };\n", "\n", " // Disable right mouse context menu.\n", " canvas_div.addEventListener('contextmenu', function (_e) {\n", " event.preventDefault();\n", " return false;\n", " });\n", "\n", " function set_focus() {\n", " canvas.focus();\n", " canvas_div.focus();\n", " }\n", "\n", " window.setTimeout(set_focus, 100);\n", "};\n", "\n", "mpl.figure.prototype._init_toolbar = function () {\n", " var fig = this;\n", "\n", " var toolbar = document.createElement('div');\n", " toolbar.classList = 'mpl-toolbar';\n", " this.root.appendChild(toolbar);\n", "\n", " function on_click_closure(name) {\n", " return function (_event) {\n", " return fig.toolbar_button_onclick(name);\n", " };\n", " }\n", "\n", " function on_mouseover_closure(tooltip) {\n", " return function (event) {\n", " if (!event.currentTarget.disabled) {\n", " return fig.toolbar_button_onmouseover(tooltip);\n", " }\n", " };\n", " }\n", "\n", " fig.buttons = {};\n", " var buttonGroup = document.createElement('div');\n", " buttonGroup.classList = 'mpl-button-group';\n", " for (var toolbar_ind in mpl.toolbar_items) {\n", " var name = mpl.toolbar_items[toolbar_ind][0];\n", " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", " var image = mpl.toolbar_items[toolbar_ind][2];\n", " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", "\n", " if (!name) {\n", " /* Instead of a spacer, we start a new button group. */\n", " if (buttonGroup.hasChildNodes()) {\n", " toolbar.appendChild(buttonGroup);\n", " }\n", " buttonGroup = document.createElement('div');\n", " buttonGroup.classList = 'mpl-button-group';\n", " continue;\n", " }\n", "\n", " var button = (fig.buttons[name] = document.createElement('button'));\n", " button.classList = 'mpl-widget';\n", " button.setAttribute('role', 'button');\n", " button.setAttribute('aria-disabled', 'false');\n", " button.addEventListener('click', on_click_closure(method_name));\n", " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", "\n", " var icon_img = document.createElement('img');\n", " icon_img.src = '_images/' + image + '.png';\n", " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", " icon_img.alt = tooltip;\n", " button.appendChild(icon_img);\n", "\n", " buttonGroup.appendChild(button);\n", " }\n", "\n", " if (buttonGroup.hasChildNodes()) {\n", " toolbar.appendChild(buttonGroup);\n", " }\n", "\n", " var fmt_picker = document.createElement('select');\n", " fmt_picker.classList = 'mpl-widget';\n", " toolbar.appendChild(fmt_picker);\n", " this.format_dropdown = fmt_picker;\n", "\n", " for (var ind in mpl.extensions) {\n", " var fmt = mpl.extensions[ind];\n", " var option = document.createElement('option');\n", " option.selected = fmt === mpl.default_extension;\n", " option.innerHTML = fmt;\n", " fmt_picker.appendChild(option);\n", " }\n", "\n", " var status_bar = document.createElement('span');\n", " status_bar.classList = 'mpl-message';\n", " toolbar.appendChild(status_bar);\n", " this.message = status_bar;\n", "};\n", "\n", "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", " // which will in turn request a refresh of the image.\n", " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", "};\n", "\n", "mpl.figure.prototype.send_message = function (type, properties) {\n", " properties['type'] = type;\n", " properties['figure_id'] = this.id;\n", " this.ws.send(JSON.stringify(properties));\n", "};\n", "\n", "mpl.figure.prototype.send_draw_message = function () {\n", " if (!this.waiting) {\n", " this.waiting = true;\n", " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", " }\n", "};\n", "\n", "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", " var format_dropdown = fig.format_dropdown;\n", " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", " fig.ondownload(fig, format);\n", "};\n", "\n", "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", " var size = msg['size'];\n", " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", " fig._resize_canvas(size[0], size[1], msg['forward']);\n", " fig.send_message('refresh', {});\n", " }\n", "};\n", "\n", "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", " var x0 = msg['x0'] / fig.ratio;\n", " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", " var x1 = msg['x1'] / fig.ratio;\n", " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", " x0 = Math.floor(x0) + 0.5;\n", " y0 = Math.floor(y0) + 0.5;\n", " x1 = Math.floor(x1) + 0.5;\n", " y1 = Math.floor(y1) + 0.5;\n", " var min_x = Math.min(x0, x1);\n", " var min_y = Math.min(y0, y1);\n", " var width = Math.abs(x1 - x0);\n", " var height = Math.abs(y1 - y0);\n", "\n", " fig.rubberband_context.clearRect(\n", " 0,\n", " 0,\n", " fig.canvas.width / fig.ratio,\n", " fig.canvas.height / fig.ratio\n", " );\n", "\n", " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", "};\n", "\n", "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", " // Updates the figure title.\n", " fig.header.textContent = msg['label'];\n", "};\n", "\n", "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", " fig.canvas_div.style.cursor = msg['cursor'];\n", "};\n", "\n", "mpl.figure.prototype.handle_message = function (fig, msg) {\n", " fig.message.textContent = msg['message'];\n", "};\n", "\n", "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", " // Request the server to send over a new figure.\n", " fig.send_draw_message();\n", "};\n", "\n", "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", " fig.image_mode = msg['mode'];\n", "};\n", "\n", "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", " for (var key in msg) {\n", " if (!(key in fig.buttons)) {\n", " continue;\n", " }\n", " fig.buttons[key].disabled = !msg[key];\n", " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", " }\n", "};\n", "\n", "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", " if (msg['mode'] === 'PAN') {\n", " fig.buttons['Pan'].classList.add('active');\n", " fig.buttons['Zoom'].classList.remove('active');\n", " } else if (msg['mode'] === 'ZOOM') {\n", " fig.buttons['Pan'].classList.remove('active');\n", " fig.buttons['Zoom'].classList.add('active');\n", " } else {\n", " fig.buttons['Pan'].classList.remove('active');\n", " fig.buttons['Zoom'].classList.remove('active');\n", " }\n", "};\n", "\n", "mpl.figure.prototype.updated_canvas_event = function () {\n", " // Called whenever the canvas gets updated.\n", " this.send_message('ack', {});\n", "};\n", "\n", "// A function to construct a web socket function for onmessage handling.\n", "// Called in the figure constructor.\n", "mpl.figure.prototype._make_on_message_function = function (fig) {\n", " return function socket_on_message(evt) {\n", " if (evt.data instanceof Blob) {\n", " var img = evt.data;\n", " if (img.type !== 'image/png') {\n", " /* FIXME: We get \"Resource interpreted as Image but\n", " * transferred with MIME type text/plain:\" errors on\n", " * Chrome. But how to set the MIME type? It doesn't seem\n", " * to be part of the websocket stream */\n", " img.type = 'image/png';\n", " }\n", "\n", " /* Free the memory for the previous frames */\n", " if (fig.imageObj.src) {\n", " (window.URL || window.webkitURL).revokeObjectURL(\n", " fig.imageObj.src\n", " );\n", " }\n", "\n", " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", " img\n", " );\n", " fig.updated_canvas_event();\n", " fig.waiting = false;\n", " return;\n", " } else if (\n", " typeof evt.data === 'string' &&\n", " evt.data.slice(0, 21) === 'data:image/png;base64'\n", " ) {\n", " fig.imageObj.src = evt.data;\n", " fig.updated_canvas_event();\n", " fig.waiting = false;\n", " return;\n", " }\n", "\n", " var msg = JSON.parse(evt.data);\n", " var msg_type = msg['type'];\n", "\n", " // Call the \"handle_{type}\" callback, which takes\n", " // the figure and JSON message as its only arguments.\n", " try {\n", " var callback = fig['handle_' + msg_type];\n", " } catch (e) {\n", " console.log(\n", " \"No handler for the '\" + msg_type + \"' message type: \",\n", " msg\n", " );\n", " return;\n", " }\n", "\n", " if (callback) {\n", " try {\n", " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", " callback(fig, msg);\n", " } catch (e) {\n", " console.log(\n", " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", " e,\n", " e.stack,\n", " msg\n", " );\n", " }\n", " }\n", " };\n", "};\n", "\n", "\n", "/*\n", " * return a copy of an object with only non-object keys\n", " * we need this to avoid circular references\n", " * https://stackoverflow.com/a/24161582/3208463\n", " */\n", "function simpleKeys(original) {\n", " return Object.keys(original).reduce(function (obj, key) {\n", " if (typeof original[key] !== 'object') {\n", " obj[key] = original[key];\n", " }\n", " return obj;\n", " }, {});\n", "}\n", "\n", "mpl.figure.prototype.mouse_event = function (event, name) {\n", " if (name === 'button_press') {\n", " this.canvas.focus();\n", " this.canvas_div.focus();\n", " }\n", "\n", " // from https://stackoverflow.com/q/1114465\n", " var boundingRect = this.canvas.getBoundingClientRect();\n", " var x = (event.clientX - boundingRect.left) * this.ratio;\n", " var y = (event.clientY - boundingRect.top) * this.ratio;\n", "\n", " this.send_message(name, {\n", " x: x,\n", " y: y,\n", " button: event.button,\n", " step: event.step,\n", " guiEvent: simpleKeys(event),\n", " });\n", "\n", " return false;\n", "};\n", "\n", "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", " // Handle any extra behaviour associated with a key event\n", "};\n", "\n", "mpl.figure.prototype.key_event = function (event, name) {\n", " // Prevent repeat events\n", " if (name === 'key_press') {\n", " if (event.key === this._key) {\n", " return;\n", " } else {\n", " this._key = event.key;\n", " }\n", " }\n", " if (name === 'key_release') {\n", " this._key = null;\n", " }\n", "\n", " var value = '';\n", " if (event.ctrlKey && event.key !== 'Control') {\n", " value += 'ctrl+';\n", " }\n", " else if (event.altKey && event.key !== 'Alt') {\n", " value += 'alt+';\n", " }\n", " else if (event.shiftKey && event.key !== 'Shift') {\n", " value += 'shift+';\n", " }\n", "\n", " value += 'k' + event.key;\n", "\n", " this._key_event_extra(event, name);\n", "\n", " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", " return false;\n", "};\n", "\n", "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", " if (name === 'download') {\n", " this.handle_save(this, null);\n", " } else {\n", " this.send_message('toolbar_button', { name: name });\n", " }\n", "};\n", "\n", "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", " this.message.textContent = tooltip;\n", "};\n", "\n", "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", "// prettier-ignore\n", "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", "\n", "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", "\n", "mpl.default_extension = \"png\";/* global mpl */\n", "\n", "var comm_websocket_adapter = function (comm) {\n", " // Create a \"websocket\"-like object which calls the given IPython comm\n", " // object with the appropriate methods. Currently this is a non binary\n", " // socket, so there is still some room for performance tuning.\n", " var ws = {};\n", "\n", " ws.binaryType = comm.kernel.ws.binaryType;\n", " ws.readyState = comm.kernel.ws.readyState;\n", " function updateReadyState(_event) {\n", " if (comm.kernel.ws) {\n", " ws.readyState = comm.kernel.ws.readyState;\n", " } else {\n", " ws.readyState = 3; // Closed state.\n", " }\n", " }\n", " comm.kernel.ws.addEventListener('open', updateReadyState);\n", " comm.kernel.ws.addEventListener('close', updateReadyState);\n", " comm.kernel.ws.addEventListener('error', updateReadyState);\n", "\n", " ws.close = function () {\n", " comm.close();\n", " };\n", " ws.send = function (m) {\n", " //console.log('sending', m);\n", " comm.send(m);\n", " };\n", " // Register the callback with on_msg.\n", " comm.on_msg(function (msg) {\n", " //console.log('receiving', msg['content']['data'], msg);\n", " var data = msg['content']['data'];\n", " if (data['blob'] !== undefined) {\n", " data = {\n", " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", " };\n", " }\n", " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", " ws.onmessage(data);\n", " });\n", " return ws;\n", "};\n", "\n", "mpl.mpl_figure_comm = function (comm, msg) {\n", " // This is the function which gets called when the mpl process\n", " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", "\n", " var id = msg.content.data.id;\n", " // Get hold of the div created by the display call when the Comm\n", " // socket was opened in Python.\n", " var element = document.getElementById(id);\n", " var ws_proxy = comm_websocket_adapter(comm);\n", "\n", " function ondownload(figure, _format) {\n", " window.open(figure.canvas.toDataURL());\n", " }\n", "\n", " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", "\n", " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", " // web socket which is closed, not our websocket->open comm proxy.\n", " ws_proxy.onopen();\n", "\n", " fig.parent_element = element;\n", " fig.cell_info = mpl.find_output_cell(\"
\");\n", " if (!fig.cell_info) {\n", " console.error('Failed to find cell for figure', id, fig);\n", " return;\n", " }\n", " fig.cell_info[0].output_area.element.on(\n", " 'cleared',\n", " { fig: fig },\n", " fig._remove_fig_handler\n", " );\n", "};\n", "\n", "mpl.figure.prototype.handle_close = function (fig, msg) {\n", " var width = fig.canvas.width / fig.ratio;\n", " fig.cell_info[0].output_area.element.off(\n", " 'cleared',\n", " fig._remove_fig_handler\n", " );\n", " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", "\n", " // Update the output cell to use the data from the current canvas.\n", " fig.push_to_output();\n", " var dataURL = fig.canvas.toDataURL();\n", " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", " // the notebook keyboard shortcuts fail.\n", " IPython.keyboard_manager.enable();\n", " fig.parent_element.innerHTML =\n", " '';\n", " fig.close_ws(fig, msg);\n", "};\n", "\n", "mpl.figure.prototype.close_ws = function (fig, msg) {\n", " fig.send_message('closing', msg);\n", " // fig.ws.close()\n", "};\n", "\n", "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", " // Turn the data on the canvas into data in the output cell.\n", " var width = this.canvas.width / this.ratio;\n", " var dataURL = this.canvas.toDataURL();\n", " this.cell_info[1]['text/html'] =\n", " '';\n", "};\n", "\n", "mpl.figure.prototype.updated_canvas_event = function () {\n", " // Tell IPython that the notebook contents must change.\n", " IPython.notebook.set_dirty(true);\n", " this.send_message('ack', {});\n", " var fig = this;\n", " // Wait a second, then push the new image to the DOM so\n", " // that it is saved nicely (might be nice to debounce this).\n", " setTimeout(function () {\n", " fig.push_to_output();\n", " }, 1000);\n", "};\n", "\n", "mpl.figure.prototype._init_toolbar = function () {\n", " var fig = this;\n", "\n", " var toolbar = document.createElement('div');\n", " toolbar.classList = 'btn-toolbar';\n", " this.root.appendChild(toolbar);\n", "\n", " function on_click_closure(name) {\n", " return function (_event) {\n", " return fig.toolbar_button_onclick(name);\n", " };\n", " }\n", "\n", " function on_mouseover_closure(tooltip) {\n", " return function (event) {\n", " if (!event.currentTarget.disabled) {\n", " return fig.toolbar_button_onmouseover(tooltip);\n", " }\n", " };\n", " }\n", "\n", " fig.buttons = {};\n", " var buttonGroup = document.createElement('div');\n", " buttonGroup.classList = 'btn-group';\n", " var button;\n", " for (var toolbar_ind in mpl.toolbar_items) {\n", " var name = mpl.toolbar_items[toolbar_ind][0];\n", " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", " var image = mpl.toolbar_items[toolbar_ind][2];\n", " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", "\n", " if (!name) {\n", " /* Instead of a spacer, we start a new button group. */\n", " if (buttonGroup.hasChildNodes()) {\n", " toolbar.appendChild(buttonGroup);\n", " }\n", " buttonGroup = document.createElement('div');\n", " buttonGroup.classList = 'btn-group';\n", " continue;\n", " }\n", "\n", " button = fig.buttons[name] = document.createElement('button');\n", " button.classList = 'btn btn-default';\n", " button.href = '#';\n", " button.title = name;\n", " button.innerHTML = '';\n", " button.addEventListener('click', on_click_closure(method_name));\n", " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", " buttonGroup.appendChild(button);\n", " }\n", "\n", " if (buttonGroup.hasChildNodes()) {\n", " toolbar.appendChild(buttonGroup);\n", " }\n", "\n", " // Add the status bar.\n", " var status_bar = document.createElement('span');\n", " status_bar.classList = 'mpl-message pull-right';\n", " toolbar.appendChild(status_bar);\n", " this.message = status_bar;\n", "\n", " // Add the close button to the window.\n", " var buttongrp = document.createElement('div');\n", " buttongrp.classList = 'btn-group inline pull-right';\n", " button = document.createElement('button');\n", " button.classList = 'btn btn-mini btn-primary';\n", " button.href = '#';\n", " button.title = 'Stop Interaction';\n", " button.innerHTML = '';\n", " button.addEventListener('click', function (_evt) {\n", " fig.handle_close(fig, {});\n", " });\n", " button.addEventListener(\n", " 'mouseover',\n", " on_mouseover_closure('Stop Interaction')\n", " );\n", " buttongrp.appendChild(button);\n", " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", "};\n", "\n", "mpl.figure.prototype._remove_fig_handler = function (event) {\n", " var fig = event.data.fig;\n", " if (event.target !== this) {\n", " // Ignore bubbled events from children.\n", " return;\n", " }\n", " fig.close_ws(fig, {});\n", "};\n", "\n", "mpl.figure.prototype._root_extra_style = function (el) {\n", " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", "};\n", "\n", "mpl.figure.prototype._canvas_extra_style = function (el) {\n", " // this is important to make the div 'focusable\n", " el.setAttribute('tabindex', 0);\n", " // reach out to IPython and tell the keyboard manager to turn it's self\n", " // off when our div gets focus\n", "\n", " // location in version 3\n", " if (IPython.notebook.keyboard_manager) {\n", " IPython.notebook.keyboard_manager.register_events(el);\n", " } else {\n", " // location in version 2\n", " IPython.keyboard_manager.register_events(el);\n", " }\n", "};\n", "\n", "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", " // Check for shift+enter\n", " if (event.shiftKey && event.which === 13) {\n", " this.canvas_div.blur();\n", " // select the cell after this one\n", " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", " IPython.notebook.select(index + 1);\n", " }\n", "};\n", "\n", "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", " fig.ondownload(fig, null);\n", "};\n", "\n", "mpl.find_output_cell = function (html_output) {\n", " // Return the cell and output element which can be found *uniquely* in the notebook.\n", " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", " // IPython event is triggered only after the cells have been serialised, which for\n", " // our purposes (turning an active figure into a static one), is too late.\n", " var cells = IPython.notebook.get_cells();\n", " var ncells = cells.length;\n", " for (var i = 0; i < ncells; i++) {\n", " var cell = cells[i];\n", " if (cell.cell_type === 'code') {\n", " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", " var data = cell.output_area.outputs[j];\n", " if (data.data) {\n", " // IPython >= 3 moved mimebundle to data attribute of output\n", " data = data.data;\n", " }\n", " if (data['text/html'] === html_output) {\n", " return [cell, data, j];\n", " }\n", " }\n", " }\n", " }\n", "};\n", "\n", "// Register the function which deals with the matplotlib target/channel.\n", "// The kernel may be null if the page has been refreshed.\n", "if (IPython.notebook.kernel !== null) {\n", " IPython.notebook.kernel.comm_manager.register_target(\n", " 'matplotlib',\n", " mpl.mpl_figure_comm\n", " );\n", "}\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "R = Rz*Ry*Rx\n", "R = sym.lambdify((a, b, g), R, 'numpy')\n", "\n", "alpha = np.pi/3\n", "beta = np.pi/4\n", "gamma = np.pi/6\n", "\n", "R = R(alpha, beta, gamma)\n", "\n", "e1 = np.array([[1, 0, 0]])\n", "e2 = np.array([[0, 1, 0]])\n", "e3 = np.array([[0, 0, 1]])\n", "\n", "basis = np.vstack((e1, e2, e3))\n", "basisRot = R @ basis\n", "CCS(Oijk=np.array([0, 0, 0]), Oxyz=np.array([0, 0, 0]),\n", " ijk=basisRot.T, xyz=basis.T, vector=False)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Rotations in a coordinate system is equivalent to minus rotations in the other coordinate system\n", "\n", "Remember that we saw for the elemental rotations that it's equivalent to instead of rotating the local coordinate system, $xyz$, by $\\alpha, \\beta, \\gamma$ in relation to axes of the Global coordinate system, to rotate the Global coordinate system, $\\mathbf{XYZ}$, by $-\\alpha, -\\beta, -\\gamma$ in relation to the axes of the local coordinate system. The same property applies to a sequence of rotations: rotations of $xyz$ in relation to $\\mathbf{XYZ}$ by $\\alpha, \\beta, \\gamma$ result in the same matrix as rotations of $\\mathbf{XYZ}$ in relation to $xyz$ by $-\\alpha, -\\beta, -\\gamma$: \n", "
\n", "\n", "\\begin{equation}\n", "\\begin{array}{ll}\n", "\\mathbf{R_{Gl,\\,XYZ\\,}}(\\alpha,\\beta,\\gamma) & = \\mathbf{R_{Gl,\\,Z}}(\\gamma)\\, \\mathbf{R_{Gl,\\,Y}}(\\beta)\\, \\mathbf{R_{Gl,\\,X}}(\\alpha) \\\\\n", "& = \\mathbf{R}_{\\mathbf{lG},\\,z\\,}(-\\gamma)\\, \\mathbf{R}_{\\mathbf{lG},\\,y\\,}(-\\beta)\\, \\mathbf{R}_{\\mathbf{lG},\\,x\\,}(-\\alpha) \\\\\n", "& = \\mathbf{R}_{\\mathbf{lG},\\,xyz\\,}(-\\alpha,-\\beta,-\\gamma)\n", "\\end{array}\n", "\\end{equation}\n", "\n", "\n", "Confer that by examining the $\\mathbf{R_{Gl,\\,XYZ}}$ and $\\mathbf{R}_{\\mathbf{lG},\\,xyz}$ matrices above.\n", "\n", "Let's verify this property with Sympy:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:10.406315Z", "start_time": "2021-11-18T00:05:10.390102Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle \\mathbf{R_{Gl,\\,XYZ\\,}}(\\alpha,\\beta,\\gamma) =$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/latex": [ "$\\displaystyle \\left[\\begin{matrix}\\cos{\\left(\\beta \\right)} \\cos{\\left(\\gamma \\right)} & \\sin{\\left(\\alpha \\right)} \\sin{\\left(\\beta \\right)} \\cos{\\left(\\gamma \\right)} - \\sin{\\left(\\gamma \\right)} \\cos{\\left(\\alpha \\right)} & \\sin{\\left(\\alpha \\right)} \\sin{\\left(\\gamma \\right)} + \\sin{\\left(\\beta \\right)} \\cos{\\left(\\alpha \\right)} \\cos{\\left(\\gamma \\right)}\\\\\\sin{\\left(\\gamma \\right)} \\cos{\\left(\\beta \\right)} & \\sin{\\left(\\alpha \\right)} \\sin{\\left(\\beta \\right)} \\sin{\\left(\\gamma \\right)} + \\cos{\\left(\\alpha \\right)} \\cos{\\left(\\gamma \\right)} & - \\sin{\\left(\\alpha \\right)} \\cos{\\left(\\gamma \\right)} + \\sin{\\left(\\beta \\right)} \\sin{\\left(\\gamma \\right)} \\cos{\\left(\\alpha \\right)}\\\\- \\sin{\\left(\\beta \\right)} & \\sin{\\left(\\alpha \\right)} \\cos{\\left(\\beta \\right)} & \\cos{\\left(\\alpha \\right)} \\cos{\\left(\\beta \\right)}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/latex": [ "$\\displaystyle \\mathbf{R}_{\\mathbf{lG},\\,xyz\\,}(-\\alpha,-\\beta,-\\gamma) =$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/latex": [ "$\\displaystyle \\left[\\begin{matrix}\\cos{\\left(\\beta \\right)} \\cos{\\left(\\gamma \\right)} & \\sin{\\left(\\alpha \\right)} \\sin{\\left(\\beta \\right)} \\cos{\\left(\\gamma \\right)} - \\sin{\\left(\\gamma \\right)} \\cos{\\left(\\alpha \\right)} & \\sin{\\left(\\alpha \\right)} \\sin{\\left(\\gamma \\right)} + \\sin{\\left(\\beta \\right)} \\cos{\\left(\\alpha \\right)} \\cos{\\left(\\gamma \\right)}\\\\\\sin{\\left(\\gamma \\right)} \\cos{\\left(\\beta \\right)} & \\sin{\\left(\\alpha \\right)} \\sin{\\left(\\beta \\right)} \\sin{\\left(\\gamma \\right)} + \\cos{\\left(\\alpha \\right)} \\cos{\\left(\\gamma \\right)} & - \\sin{\\left(\\alpha \\right)} \\cos{\\left(\\gamma \\right)} + \\sin{\\left(\\beta \\right)} \\sin{\\left(\\gamma \\right)} \\cos{\\left(\\alpha \\right)}\\\\- \\sin{\\left(\\beta \\right)} & \\sin{\\left(\\alpha \\right)} \\cos{\\left(\\beta \\right)} & \\cos{\\left(\\alpha \\right)} \\cos{\\left(\\beta \\right)}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/latex": [ "$\\displaystyle \\mathbf{R_{Gl,\\,XYZ\\,}}(\\alpha,\\beta,\\gamma) \\;==\\;\\mathbf{R}_{\\mathbf{lG},\\,xyz\\,}(-\\alpha,-\\beta,-\\gamma)$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "True" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "RXYZ = RZ*RY*RX\n", "# Rotation matrix of xyz in relation to XYZ:\n", "display(Math(r'\\mathbf{R_{Gl,\\,XYZ\\,}}(\\alpha,\\beta,\\gamma) ='))\n", "display(Math(sym.latex(RXYZ, mat_str='matrix')))\n", "\n", "# Elemental rotation matrices of XYZ in relation to xyz and negate all angles:\n", "Rx_neg = sym.Matrix([[1, 0, 0], [0, cos(-a), -sin(-a)], [0, sin(-a), cos(-a)]]).T\n", "Ry_neg = sym.Matrix([[cos(-b), 0, sin(-b)], [0, 1, 0], [-sin(-b), 0, cos(-b)]]).T\n", "Rz_neg = sym.Matrix([[cos(-g), -sin(-g), 0], [sin(-g), cos(-g), 0], [0, 0, 1]]).T\n", "\n", "# Rotation matrix of XYZ in relation to xyz:\n", "Rxyz_neg = Rz_neg * Ry_neg * Rx_neg\n", "display(Math(r'\\mathbf{R}_{\\mathbf{lG},\\,xyz\\,}(-\\alpha,-\\beta,-\\gamma) ='))\n", "display(Math(sym.latex(Rxyz_neg, mat_str='matrix')))\n", "\n", "# Check that the two matrices are equal:\n", "display(Math(r'\\mathbf{R_{Gl,\\,XYZ\\,}}(\\alpha,\\beta,\\gamma) \\;==\\;' + \\\n", " r'\\mathbf{R}_{\\mathbf{lG},\\,xyz\\,}(-\\alpha,-\\beta,-\\gamma)'))\n", "RXYZ == Rxyz_neg" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Rotations in a coordinate system is the transpose of inverse order of rotations in the other coordinate system\n", "\n", "There is another property of the rotation matrices for the different coordinate systems: the rotation matrix, for example from the Global to the local coordinate system for the $xyz$ sequence, is just the transpose of the rotation matrix for the inverse operation (from the local to the Global coordinate system) of the inverse sequence ($\\mathbf{ZYX}$) and vice-versa: \n", "
\n", "\n", "\\begin{equation}\n", "\\begin{array}{ll}\n", "\\mathbf{R}_{\\mathbf{lG},\\,xyz}(\\alpha,\\beta,\\gamma) & = \\mathbf{R}_{\\mathbf{lG},\\,z\\,} \\mathbf{R}_{\\mathbf{lG},\\,y\\,} \\mathbf{R}_{\\mathbf{lG},\\,x} \\\\\n", "& = \\mathbf{R_{Gl,\\,Z\\,}^{-1}} \\mathbf{R_{Gl,\\,Y\\,}^{-1}} \\mathbf{R_{Gl,\\,X\\,}^{-1}} \\\\\n", "& = \\mathbf{R_{Gl,\\,Z\\,}^{T}} \\mathbf{R_{Gl,\\,Y\\,}^{T}} \\mathbf{R_{Gl,\\,X\\,}^{T}} \\\\\n", "& = (\\mathbf{R_{Gl,\\,X\\,}} \\mathbf{R_{Gl,\\,Y\\,}} \\mathbf{R_{Gl,\\,Z}})^\\mathbf{T} \\\\\n", "& = \\mathbf{R_{Gl,\\,ZYX\\,}^{T}}(\\gamma,\\beta,\\alpha)\n", "\\end{array}\n", "\\end{equation}\n", "\n", "\n", "Where we used the properties that the inverse of the rotation matrix (which is orthonormal) is its transpose and that the transpose of a product of matrices is equal to the product of their transposes in reverse order.\n", "\n", "Let's verify this property with Sympy:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:10.674865Z", "start_time": "2021-11-18T00:05:10.664851Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle \\mathbf{R_{Gl,\\,ZYX\\,}^T}=\\left[\\begin{matrix}\\cos{\\left(\\beta \\right)} \\cos{\\left(\\gamma \\right)} & \\sin{\\left(\\alpha \\right)} \\sin{\\left(\\beta \\right)} \\cos{\\left(\\gamma \\right)} + \\sin{\\left(\\gamma \\right)} \\cos{\\left(\\alpha \\right)} & \\sin{\\left(\\alpha \\right)} \\sin{\\left(\\gamma \\right)} - \\sin{\\left(\\beta \\right)} \\cos{\\left(\\alpha \\right)} \\cos{\\left(\\gamma \\right)}\\\\- \\sin{\\left(\\gamma \\right)} \\cos{\\left(\\beta \\right)} & - \\sin{\\left(\\alpha \\right)} \\sin{\\left(\\beta \\right)} \\sin{\\left(\\gamma \\right)} + \\cos{\\left(\\alpha \\right)} \\cos{\\left(\\gamma \\right)} & \\sin{\\left(\\alpha \\right)} \\cos{\\left(\\gamma \\right)} + \\sin{\\left(\\beta \\right)} \\sin{\\left(\\gamma \\right)} \\cos{\\left(\\alpha \\right)}\\\\\\sin{\\left(\\beta \\right)} & - \\sin{\\left(\\alpha \\right)} \\cos{\\left(\\beta \\right)} & \\cos{\\left(\\alpha \\right)} \\cos{\\left(\\beta \\right)}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/latex": [ "$\\displaystyle \\mathbf{R}_{\\mathbf{lG},\\,xyz\\,}(\\alpha,\\beta,\\gamma) \\,==\\,\\mathbf{R_{Gl,\\,ZYX\\,}^T}(\\gamma,\\beta,\\alpha)$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "True" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "RZYX = RX * RY * RZ\n", "Rxyz = Rz * Ry * Rx\n", "display(Math(r'\\mathbf{R_{Gl,\\,ZYX\\,}^T}=' + sym.latex(RZYX.T, mat_str='matrix')))\n", "display(Math(r'\\mathbf{R}_{\\mathbf{lG},\\,xyz\\,}(\\alpha,\\beta,\\gamma) \\,==\\,' + \\\n", " r'\\mathbf{R_{Gl,\\,ZYX\\,}^T}(\\gamma,\\beta,\\alpha)'))\n", "Rxyz == RZYX.T" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Sequence of rotations of a Vector\n", "\n", "We saw in the notebook [Rigid-body transformations in a plane (2D)](https://nbviewer.org/github/BMClab/bmc/blob/master/notebooks/Transformation2D.ipynb#Rotation-of-a-Vector) that the rotation matrix can also be used to rotate a vector (in fact, a point, image, solid, etc.) by a given angle around an axis of the coordinate system. Let's investigate that for the 3D case using the example earlier where a book was rotated in different orders and around the Global and local coordinate systems. \n", "\n", "Before any rotation, the point shown in that figure as a round black dot on the spine of the book has coordinates $\\mathbf{P}=[0, 1, 2]$ (the book has thickness 0, width 1, and height 2). \n", "\n", "After the first sequence of rotations shown in the figure (rotated around $X$ and $Y$ by $90^0$ each time), $\\mathbf{P}$ has coordinates $\\mathbf{P}=[1, -2, 0]$ in the global coordinate system. Let's verify that:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:10.935562Z", "start_time": "2021-11-18T00:05:10.929856Z" }, "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "P1 = [[ 1. -2. 0.]]\n" ] } ], "source": [ "P = np.array([[0, 1, 2]]).T\n", "\n", "RXY = RY*RX\n", "R = sym.lambdify((a, b), RXY, 'numpy')\n", "R = R(np.pi/2, np.pi/2)\n", "P1 = np.dot(R, P)\n", "print('P1 =', P1.T)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "As expected. \n", "The reader is invited to deduce the position of point $\\mathbf{P}$ after the inverse order of rotations, but still around the Global coordinate system.\n", "\n", "Although we are performing vector rotation, where we don't need the concept of transformation between coordinate systems, in the example above we used the local-to-Global rotation matrix, $\\mathbf{R_{Gl}}$. As we saw in the notebook for the 2D transformation, when we use this matrix, it performs a counter-clockwise (positive) rotation. \n", "If we want to rotate the vector in the clockwise (negative) direction, we can use the very same rotation matrix entering a negative angle or we can use the inverse rotation matrix, the Global-to-local rotation matrix, $\\mathbf{R_{lG}}$ and a positive (negative of negative) angle, because $\\mathbf{R_{Gl}}(\\alpha) = \\mathbf{R_{lG}}(-\\alpha)$, but bear in mind that even in this latter case we are rotating around the Global coordinate system! \n", "\n", "Consider now that we want to deduce algebraically the position of the point $\\mathbf{P}$ after the rotations around the local coordinate system as shown in the second set of examples in the figure with the sequence of book rotations. The point has the same initial position, $\\mathbf{P}=[0, 1, 2]$, and after the rotations around $x$ and $y$ by $90^0$ each time, what is the position of this point? \n", "It's implicit in this question that the new desired position is in the Global coordinate system because the local coordinate system rotates with the book and the point never changes its position in the local coordinate system. So, by inspection of the figure, the new position of the point is $\\mathbf{P1}=[2, 0, 1]$. \n", "Let's naively try to deduce this position by repeating the steps as before:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:11.197824Z", "start_time": "2021-11-18T00:05:11.192818Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "P1 = [[ 1. 2. -0.]]\n" ] } ], "source": [ "Rxy = Ry*Rx\n", "R = sym.lambdify((a, b), Rxy, 'numpy')\n", "R = R(np.pi/2, np.pi/2)\n", "P1 = np.dot(R, P)\n", "print('P1 =', P1.T)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "The wrong answer. \n", "The problem is that we defined the rotation of a vector using the local-to-Global rotation matrix. One correction solution for this problem is to continuing using the multiplication of the Global-to-local rotation matrices, $\\mathbf{R}_{xy} = \\mathbf{R}_y\\,\\mathbf{R}_x$, transpose $\\mathbf{R}_{xy}$ to get the Global-to-local coordinate system, $\\mathbf{R_{XY}}=\\mathbf{R^T}_{xy}$, and then rotate the vector using this matrix:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:11.456929Z", "start_time": "2021-11-18T00:05:11.451945Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "P1 = [[ 2. -0. 1.]]\n" ] } ], "source": [ "Rxy = Ry*Rx\n", "RXY = Rxy.T\n", "R = sym.lambdify((a, b), RXY, 'numpy')\n", "R = R(np.pi/2, np.pi/2)\n", "P1 = np.dot(R, P)\n", "print('P1 =', P1.T)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "The correct answer.\n", "\n", "Another solution is to understand that when using the Global-to-local rotation matrix, counter-clockwise rotations (as performed with the book the figure) are negative, not positive, and that when dealing with rotations with the Global-to-local rotation matrix the order of matrix multiplication is inverted, for example, it should be $\\mathbf{R\\_}_{xyz} = \\mathbf{R}_x\\,\\mathbf{R}_y\\,\\mathbf{R}_z$ (an added underscore to remind us this is not the convention adopted here)." ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:11.715694Z", "start_time": "2021-11-18T00:05:11.710480Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "P1 = [[ 2. -0. 1.]]\n" ] } ], "source": [ "R_xy = Rx*Ry\n", "R = sym.lambdify((a, b), R_xy, 'numpy')\n", "R = R(-np.pi/2, -np.pi/2)\n", "P1 = np.dot(R, P)\n", "print('P1 =', P1.T)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "The correct answer. \n", "The reader is invited to deduce the position of point $\\mathbf{P}$ after the inverse order of rotations, around the local coordinate system.\n", "\n", "In fact, you will find elsewhere texts about rotations in 3D adopting this latter convention as the standard, i.e., they introduce the Global-to-local rotation matrix and describe sequence of rotations algebraically as matrix multiplication in the direct order, $\\mathbf{R\\_}_{xyz} = \\mathbf{R}_x\\,\\mathbf{R}_y\\,\\mathbf{R}_z$, the inverse we have done in this text. It's all a matter of convention, just that." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### The 12 different sequences of Euler angles\n", "\n", "The Euler angles are defined in terms of rotations around a rotating local coordinate system. As we saw for the sequence of rotations around $x, y, z$, the axes of the local rotated coordinate system are not fixed in space because after the first elemental rotation, the other two axes rotate. \n", "\n", "Other sequences of rotations could be produced without combining axes of the two different coordinate systems (Global and local) for the definition of the rotation axes. There is a total of 12 different sequences of three elemental rotations that are valid and may be used for describing the rotation of a coordinate system with respect to another coordinate system: \n", "
\n", "\n", "\\begin{equation}\n", "\\begin{array}{ll}\n", "xyz \\quad xzy \\quad yzx \\quad yxz \\quad zxy \\quad zyx \\\\\n", "xyx \\quad xzx \\quad yzy \\quad yxy \\quad zxz \\quad zyz\n", "\\end{array}\n", "\\end{equation}\n", "\n", "\n", "The first six sequences (first row) are all around different axes, they are usually referred as Cardan or Tait–Bryan angles. The other six sequences (second row) have the first and third rotations around the same axis, but keep in mind that the axis for the third rotation is not at the same place anymore because it changed its orientation after the second rotation. The sequences with repeated axes are known as proper or classic Euler angles.\n", "\n", "Which order to use it is a matter of convention, but because the order affects the results, it's fundamental to follow a convention and report it. In Engineering Mechanics (including Biomechanics), the $xyz$ order is more common; in Physics the $zxz$ order is more common (but the letters chosen to refer to the axes are arbitrary, what matters is the directions they represent). In Biomechanics, the order for the Cardan angles is most often based on the angle of most interest or of most reliable measurement. Accordingly, the axis of flexion/extension is typically selected as the first axis, the axis for abduction/adduction is the second, and the axis for internal/external rotation is the last one. We will see about this order later. The $zyx$ order is commonly used to describe the orientation of a ship or aircraft and the rotations are known as the nautical angles: yaw, pitch and roll, respectively (see next figure). \n", "
\n", "
translation and rotation 3D
Figure. The principal axes of an aircraft and the names for the rotations around these axes (image from Wikipedia).
\n", "\n", "If instead of rotations around the rotating local coordinate system we perform rotations around the fixed Global coordinate system, we will have other 12 different sequences of three elemental rotations, these are called simply rotation angles. So, in total there are 24 possible different sequences of three elemental rotations, but the 24 orders are not independent; with the 12 different sequences of Euler angles at the local coordinate system we can obtain the other 12 sequences at the Global coordinate system.\n", "\n", "The Python function `euler_rotmat.py` (code at the end of this text) determines the rotation matrix in algebraic form for any of the 24 different sequences (and sequences with only one or two axes can be inputed). This function also determines the rotation matrix in numeric form if a list of up to three angles are inputed.\n", "\n", "For instance, the rotation matrix in algebraic form for the $zxz$ order of Euler angles at the local coordinate system and the correspondent rotation matrix in numeric form after three elemental rotations by $90^o$ each are:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:12.226882Z", "start_time": "2021-11-18T00:05:12.224139Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "from euler_rotmat import euler_rotmat" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:12.246987Z", "start_time": "2021-11-18T00:05:12.228927Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle \\mathbf{R}_{local}( z:\\alpha,x:\\beta,z:\\gamma) =\\left[\\begin{matrix}- \\sin{\\left(\\alpha \\right)} \\sin{\\left(\\gamma \\right)} \\cos{\\left(\\beta \\right)} + \\cos{\\left(\\alpha \\right)} \\cos{\\left(\\gamma \\right)} & \\sin{\\left(\\alpha \\right)} \\cos{\\left(\\gamma \\right)} + \\sin{\\left(\\gamma \\right)} \\cos{\\left(\\alpha \\right)} \\cos{\\left(\\beta \\right)} & \\sin{\\left(\\beta \\right)} \\sin{\\left(\\gamma \\right)}\\\\- \\sin{\\left(\\alpha \\right)} \\cos{\\left(\\beta \\right)} \\cos{\\left(\\gamma \\right)} - \\sin{\\left(\\gamma \\right)} \\cos{\\left(\\alpha \\right)} & - \\sin{\\left(\\alpha \\right)} \\sin{\\left(\\gamma \\right)} + \\cos{\\left(\\alpha \\right)} \\cos{\\left(\\beta \\right)} \\cos{\\left(\\gamma \\right)} & \\sin{\\left(\\beta \\right)} \\cos{\\left(\\gamma \\right)}\\\\\\sin{\\left(\\alpha \\right)} \\sin{\\left(\\beta \\right)} & - \\sin{\\left(\\beta \\right)} \\cos{\\left(\\alpha \\right)} & \\cos{\\left(\\beta \\right)}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/latex": [ "$\\displaystyle \\mathbf{R}_{local}(z:\\alpha=90^o,\\;x:\\beta=90^o,\\;z:\\gamma=90^o)=\\left[\\begin{matrix}0 & 0 & 1.0\\\\0 & -1.0 & 0\\\\1.0 & 0 & 0\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "Ra, Rn = euler_rotmat(order='zxz', frame='local', angles=[90, 90, 90])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Line of nodes\n", "\n", "The second axis of rotation in the rotating coordinate system is also referred as the nodal axis or line of nodes; this axis coincides with the intersection of two perpendicular planes, one from each Global (fixed) and local (rotating) coordinate systems. The figure below shows an example of rotations and the nodal axis for the $xyz$ sequence of the Cardan angles.\n", "\n", "
rotations
Figure. First row: example of rotations for the $xyz$ sequence of the Cardan angles. The Global (fixed) $XYZ$ coordinate system is shown in green, the local (rotating) $xyz$ coordinate system is shown in blue. The nodal axis (N, shown in red) is defined by the intersection of the $YZ$ and $xy$ planes and all rotations can be described in relation to this nodal axis or to a perpendicular axis to it. Second row: starting from no rotation, the local coordinate system is rotated by $\\alpha$ around the $x$ axis, then by $\\beta$ around the rotated $y$ axis, and finally by $\\gamma$ around the twice rotated $z$ axis. Note that the line of nodes coincides with the $y$ axis for the second rotation.
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Determination of the Euler angles\n", "\n", "Once a convention is adopted, the corresponding three Euler angles of rotation can be found. \n", "For example, for the $\\mathbf{R}_{xyz}$ rotation matrix:" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:13.019968Z", "start_time": "2021-11-18T00:05:12.719219Z" } }, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle \\mathbf{R}_{local}( x:\\alpha,y:\\beta,z:\\gamma) =\\left[\\begin{matrix}\\cos{\\left(\\beta \\right)} \\cos{\\left(\\gamma \\right)} & \\sin{\\left(\\alpha \\right)} \\sin{\\left(\\beta \\right)} \\cos{\\left(\\gamma \\right)} + \\sin{\\left(\\gamma \\right)} \\cos{\\left(\\alpha \\right)} & \\sin{\\left(\\alpha \\right)} \\sin{\\left(\\gamma \\right)} - \\sin{\\left(\\beta \\right)} \\cos{\\left(\\alpha \\right)} \\cos{\\left(\\gamma \\right)}\\\\- \\sin{\\left(\\gamma \\right)} \\cos{\\left(\\beta \\right)} & - \\sin{\\left(\\alpha \\right)} \\sin{\\left(\\beta \\right)} \\sin{\\left(\\gamma \\right)} + \\cos{\\left(\\alpha \\right)} \\cos{\\left(\\gamma \\right)} & \\sin{\\left(\\alpha \\right)} \\cos{\\left(\\gamma \\right)} + \\sin{\\left(\\beta \\right)} \\sin{\\left(\\gamma \\right)} \\cos{\\left(\\alpha \\right)}\\\\\\sin{\\left(\\beta \\right)} & - \\sin{\\left(\\alpha \\right)} \\cos{\\left(\\beta \\right)} & \\cos{\\left(\\alpha \\right)} \\cos{\\left(\\beta \\right)}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "R = euler_rotmat(order='xyz', frame='local')" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "The corresponding Cardan angles for the `xyz` sequence can be given by: \n", "
\n", "\n", "\\begin{equation}\n", "\\begin{array}{ll}\n", "\\alpha = \\arctan\\left(\\dfrac{\\sin(\\alpha)}{\\cos(\\alpha)}\\right) = \\arctan\\left(\\dfrac{-\\mathbf{R}_{21}}{\\;\\;\\;\\mathbf{R}_{22}}\\right) \\\\\n", "\\\\\n", "\\beta = \\arctan\\left(\\dfrac{\\sin(\\beta)}{\\cos(\\beta)}\\right) = \\arctan\\left(\\dfrac{\\mathbf{R}_{20}}{\\sqrt{\\mathbf{R}_{00}^2+\\mathbf{R}_{10}^2}}\\right) \\\\ \n", "\\\\\n", "\\gamma = \\arctan\\left(\\dfrac{\\sin(\\gamma)}{\\cos(\\gamma)}\\right) = \\arctan\\left(\\dfrac{-\\mathbf{R}_{10}}{\\;\\;\\;\\mathbf{R}_{00}}\\right)\n", "\\end{array}\n", "\\end{equation}\n", "\n", "\n", "Note that we prefer to use the mathematical function `arctan2` rather than simply `arcsin`, `arccos` or `arctan` because the latter cannot for example distinguish $45^o$ from $135^o$ and also for better numerical accuracy. See the text [Angular kinematics in a plane (2D)](https://nbviewer.org/github/BMClab/bmc/blob/master/notebooks/KinematicsAngular2D.ipynb) for more on these issues.\n", "\n", "And here is a Python function to compute the Euler angles of rotations from the Global to the local coordinate system for the $xyz$ Cardan sequence: " ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:13.024354Z", "start_time": "2021-11-18T00:05:13.021004Z" } }, "outputs": [], "source": [ "def euler_angles_from_rot_xyz(rot_matrix, unit='deg'):\n", " \"\"\" Compute Euler angles from rotation matrix in the xyz sequence.\"\"\"\n", " \n", " import numpy as np\n", "\n", " R = np.array(rot_matrix, copy=False).astype(np.float64)[:3, :3]\n", " angles = np.zeros(3)\n", " \n", " angles[0] = np.arctan2(-R[2, 1], R[2, 2])\n", " angles[1] = np.arctan2( R[2, 0], np.sqrt(R[0, 0]**2 + R[1, 0]**2))\n", " angles[2] = np.arctan2(-R[1, 0], R[0, 0])\n", "\n", " if unit[:3].lower() == 'deg': # convert from rad to degree\n", " angles = np.rad2deg(angles)\n", "\n", " return angles" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For instance, consider sequential rotations of 45$^o$ around $x,y,z$. The resultant rotation matrix is:" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:13.212834Z", "start_time": "2021-11-18T00:05:13.197928Z" } }, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle \\mathbf{R}_{local}(x:\\alpha=45^o,\\;y:\\beta=45^o,\\;z:\\gamma=45^o)=\\left[\\begin{matrix}0.5 & 0.854 & 0.146\\\\-0.5 & 0.146 & 0.854\\\\0.707 & -0.5 & 0.5\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "Ra, Rn = euler_rotmat(order='xyz', frame='local',\n", " angles=[45, 45, 45], showA=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's check that calculating back the Cardan angles from this rotation matrix using the `euler_angles_from_rot_xyz()` function:" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:13.430975Z", "start_time": "2021-11-18T00:05:13.427804Z" } }, "outputs": [ { "data": { "text/plain": [ "array([45., 45., 45.])" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "euler_angles_from_rot_xyz(Rn, unit='deg')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We could implement a function to calculate the Euler angles for any of the 12 sequences (in fact, plus another 12 sequences if we consider all the rotations from and to the two coordinate systems), but this is tedious. There is a smarter solution using the concept of [quaternion](http://en.wikipedia.org/wiki/Quaternion), but we will not see that now. \n", "\n", "Let's see a problem with using Euler angles known as gimbal lock." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Gimbal lock\n", "\n", "[Gimbal lock](http://en.wikipedia.org/wiki/Gimbal_lock) is the loss of one degree of freedom in a three-dimensional coordinate system that occurs when an axis of rotation is placed parallel with another previous axis of rotation and two of the three rotations will be around the same direction given a certain convention of the Euler angles. This \"locks\" the system into rotations in a degenerate two-dimensional space. The system is not really locked in the sense it can't be moved or reach the other degree of freedom, but it will need an extra rotation for that. \n", "For instance, let's look at the $zxz$ sequence of rotations by the angles $\\alpha, \\beta, \\gamma$: \n", "
\n", "\n", "\\begin{equation}\n", "\\begin{array}{ll}\n", "\\mathbf{R}_{zxz} & = \\mathbf{R_{z}} \\mathbf{R_{x}} \\mathbf{R_{z}} \\\\ \n", "\\\\\n", "& = \n", "\\begin{bmatrix}\n", "\\cos\\gamma & \\sin\\gamma & 0\\\\\n", "-\\sin\\gamma & \\cos\\gamma & 0 \\\\\n", "0 & 0 & 1\n", "\\end{bmatrix}\n", "\\begin{bmatrix}\n", "1 & 0 & 0 \\\\\n", "0 & \\cos\\beta & \\sin\\beta \\\\\n", "0 & -\\sin\\beta & \\cos\\beta\n", "\\end{bmatrix}\n", "\\begin{bmatrix}\n", "\\cos\\alpha & \\sin\\alpha & 0\\\\\n", "-\\sin\\alpha & \\cos\\alpha & 0 \\\\\n", "0 & 0 & 1\n", "\\end{bmatrix}\n", "\\end{array}\n", "\\end{equation}\n", "\n", "\n", "Which results in:" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:13.890575Z", "start_time": "2021-11-18T00:05:13.881743Z" } }, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle \\mathbf{R}_{zxz}=\\left[\\begin{matrix}- \\sin{\\left(\\alpha \\right)} \\sin{\\left(\\gamma \\right)} \\cos{\\left(\\beta \\right)} + \\cos{\\left(\\alpha \\right)} \\cos{\\left(\\gamma \\right)} & \\sin{\\left(\\alpha \\right)} \\cos{\\left(\\gamma \\right)} + \\sin{\\left(\\gamma \\right)} \\cos{\\left(\\alpha \\right)} \\cos{\\left(\\beta \\right)} & \\sin{\\left(\\beta \\right)} \\sin{\\left(\\gamma \\right)}\\\\- \\sin{\\left(\\alpha \\right)} \\cos{\\left(\\beta \\right)} \\cos{\\left(\\gamma \\right)} - \\sin{\\left(\\gamma \\right)} \\cos{\\left(\\alpha \\right)} & - \\sin{\\left(\\alpha \\right)} \\sin{\\left(\\gamma \\right)} + \\cos{\\left(\\alpha \\right)} \\cos{\\left(\\beta \\right)} \\cos{\\left(\\gamma \\right)} & \\sin{\\left(\\beta \\right)} \\cos{\\left(\\gamma \\right)}\\\\\\sin{\\left(\\alpha \\right)} \\sin{\\left(\\beta \\right)} & - \\sin{\\left(\\beta \\right)} \\cos{\\left(\\alpha \\right)} & \\cos{\\left(\\beta \\right)}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a, b, g = sym.symbols('alpha, beta, gamma')\n", "# Elemental rotation matrices of xyz (local):\n", "Rz = sym.Matrix([[cos(a), sin(a), 0],\n", " [-sin(a), cos(a), 0],\n", " [0, 0, 1]])\n", "Rx = sym.Matrix([[1, 0, 0],\n", " [0, cos(b), sin(b)],\n", " [0, -sin(b), cos(b)]])\n", "Rz2 = sym.Matrix([[cos(g), sin(g), 0],\n", " [-sin(g), cos(g), 0],\n", " [0, 0, 1]])\n", "# Rotation matrix for the zxz sequence:\n", "Rzxz = Rz2*Rx*Rz\n", "Math(r'\\mathbf{R}_{zxz}=' + sym.latex(Rzxz, mat_str='matrix'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "Let's examine what happens with this rotation matrix when the rotation around the second axis ($x$) by $\\beta$ is zero:\n", "\n", "\n", "\\begin{equation}\n", "\\begin{array}{l l}\n", "\\mathbf{R}_{zxz}(\\alpha, \\beta=0, \\gamma) = \n", "\\begin{bmatrix}\n", "\\cos\\gamma & \\sin\\gamma & 0\\\\\n", "-\\sin\\gamma & \\cos\\gamma & 0 \\\\\n", "0 & 0 & 1\n", "\\end{bmatrix}\n", "\\begin{bmatrix}\n", "1 & 0 & 0 \\\\\n", "0 & 1 & 0 \\\\\n", "0 & 0 & 1\n", "\\end{bmatrix}\n", "\\begin{bmatrix}\n", "\\cos\\alpha & \\sin\\alpha & 0\\\\\n", "-\\sin\\alpha & \\cos\\alpha & 0 \\\\\n", "0 & 0 & 1\n", "\\end{bmatrix}\n", "\\end{array}\n", "\\end{equation}\n", "\n", "\n", "The second matrix is the identity matrix and has no effect on the product of the matrices, which will be:" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:14.132888Z", "start_time": "2021-11-18T00:05:14.127780Z" } }, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle \\mathbf{R}_{xyz}(\\alpha, \\beta=0, \\gamma)=\\left[\\begin{matrix}- \\sin{\\left(\\alpha \\right)} \\sin{\\left(\\gamma \\right)} + \\cos{\\left(\\alpha \\right)} \\cos{\\left(\\gamma \\right)} & \\sin{\\left(\\alpha \\right)} \\cos{\\left(\\gamma \\right)} + \\sin{\\left(\\gamma \\right)} \\cos{\\left(\\alpha \\right)} & 0\\\\- \\sin{\\left(\\alpha \\right)} \\cos{\\left(\\gamma \\right)} - \\sin{\\left(\\gamma \\right)} \\cos{\\left(\\alpha \\right)} & - \\sin{\\left(\\alpha \\right)} \\sin{\\left(\\gamma \\right)} + \\cos{\\left(\\alpha \\right)} \\cos{\\left(\\gamma \\right)} & 0\\\\0 & 0 & 1\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Rzxz = Rz2*Rz\n", "Math(r'\\mathbf{R}_{xyz}(\\alpha, \\beta=0, \\gamma)=' + \\\n", " sym.latex(Rzxz, mat_str='matrix'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Which simplifies to:" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:14.448758Z", "start_time": "2021-11-18T00:05:14.336564Z" } }, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle \\mathbf{R}_{xyz}(\\alpha, \\beta=0, \\gamma)=\\left[\\begin{matrix}\\cos{\\left(\\alpha + \\gamma \\right)} & \\sin{\\left(\\alpha + \\gamma \\right)} & 0\\\\- \\sin{\\left(\\alpha + \\gamma \\right)} & \\cos{\\left(\\alpha + \\gamma \\right)} & 0\\\\0 & 0 & 1\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Rzxz = sym.simplify(Rzxz)\n", "Math(r'\\mathbf{R}_{xyz}(\\alpha, \\beta=0, \\gamma)=' + \\\n", " sym.latex(Rzxz, mat_str='matrix'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Despite different values of $\\alpha$ and $\\gamma$ the result is a single rotation around the $z$ axis given by the sum $\\alpha+\\gamma$. In this case, of the three degrees of freedom one was lost (the other degree of freedom was set by $\\beta=0$). For movement analysis, this means for example that one angle will be undetermined because everything we know is the sum of the two angles obtained from the rotation matrix. We can set the unknown angle to zero but this is arbitrary.\n", "\n", "In fact, we already dealt with another example of gimbal lock when we looked at the $xyz$ sequence with rotations by $90^o$. See the figure representing these rotations again and perceive that the first and third rotations were around the same axis because the second rotation was by $90^o$. Let's do the matrix multiplication replacing only the second angle by $90^o$ (and let's use the `euler_rotmat.py`:" ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:14.741628Z", "start_time": "2021-11-18T00:05:14.543153Z" } }, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle \\mathbf{R}_{local}(x:\\alpha,\\;y:\\beta=90^o,\\;z:\\gamma)=\\left[\\begin{matrix}0 & \\sin{\\left(\\alpha + \\gamma \\right)} & - \\cos{\\left(\\alpha + \\gamma \\right)}\\\\0 & \\cos{\\left(\\alpha + \\gamma \\right)} & \\sin{\\left(\\alpha + \\gamma \\right)}\\\\1 & 0 & 0\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "Ra, Rn = euler_rotmat(order='xyz', frame='local',\n", " angles=[None, 90., None], showA=False)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Once again, one degree of freedom was lost and we will not be able to uniquely determine the three angles for the given rotation matrix and sequence.\n", "\n", "Possible solutions to avoid the gimbal lock are: choose a different sequence; do not rotate the system by the angle that puts the system in gimbal lock (in the examples above, avoid $\\beta=90^o$); or add an extra fourth parameter in the description of the rotation angles. \n", "\n", "But if we have a physical system where we measure or specify exactly three Euler angles in a fixed sequence to describe or control it, and we can't avoid the system to assume certain angles, then we might have to say \"Houston, we have a problem\". \n", "A famous situation where such a problem occurred was during the Apollo 13 mission. This is an actual conversation between crew and mission control during the Apollo 13 mission (Corke, 2011):\n", "\n", ">`Mission clock: 02 08 12 47` \n", "**Flight**: *Go, Guidance.* \n", "**Guido**: *He’s getting close to gimbal lock there.* \n", "**Flight**: *Roger. CapCom, recommend he bring up C3, C4, B3, B4, C1 and C2 thrusters, and advise he’s getting close to gimbal lock.* \n", "**CapCom**: *Roger.* \n", "\n", "*Of note, it was not a gimbal lock that caused the accident with the the Apollo 13 mission, the problem was an oxygen tank explosion.*" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Determination of the rotation matrix\n", "\n", "Let's revise the example where we determined the rotation matrix by building a basis given at least three non-collinear points, but now we will find the Euler angles of rotation.\n", "\n", "Given the positions m1 = [1,0,0], m2 = [0,1,0], m3 = [0,0,1], a basis can be found:" ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:15.040999Z", "start_time": "2021-11-18T00:05:15.035725Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Versors:\n", "v1 = [-0.7071 0.7071 0. ]\n", "v2 = [0.5774 0.5774 0.5774]\n", "v3 = [ 0.4082 0.4082 -0.8165]\n" ] } ], "source": [ "m1 = np.array([1, 0, 0])\n", "m2 = np.array([0, 1, 0])\n", "m3 = np.array([0, 0, 1])\n", "\n", "v1 = m2 - m1\n", "v2 = np.cross(v1, m3 - m1)\n", "v3 = np.cross(v1, v2)\n", "\n", "print('Versors:')\n", "v1 = v1/np.linalg.norm(v1)\n", "print('v1 =', v1)\n", "v2 = v2/np.linalg.norm(v2)\n", "print('v2 =', v2)\n", "v3 = v3/np.linalg.norm(v3)\n", "print('v3 =', v3)" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:15.233530Z", "start_time": "2021-11-18T00:05:15.230491Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Rotation matrix from Global to local coordinate system:\n", " [[-0.7071 0.7071 0. ]\n", " [ 0.5774 0.5774 0.5774]\n", " [ 0.4082 0.4082 -0.8165]]\n" ] } ], "source": [ "RlG = np.array([v1, v2, v3])\n", "print('Rotation matrix from Global to local coordinate system:\\n', RlG)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "And the corresponding angles of rotation using the $xyz$ sequence are:" ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:15.429399Z", "start_time": "2021-11-18T00:05:15.426423Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "array([-153.4349, 24.0948, -140.7685])" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "euler_angles_from_rot_xyz(RlG)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "These angles don't mean anything now because they are angles of the axes of the arbitrary basis we computed. In biomechanics, if we want an anatomical interpretation of the coordinate system orientation, we define the versors of the basis oriented with anatomical axes (e.g., for the shoulder, one versor would be aligned with the long axis of the upper arm) as seen [in this notebook about reference frames](https://nbviewer.org/github/BMClab/bmc/blob/master/notebooks/ReferenceFrame.ipynb). " ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Determination of the rotation matrix between two local coordinate systems\n", "\n", "Similarly to the [bidimensional case](https://nbviewer.org/github/BMClab/bmc/blob/master/notebooks/Transformation2D.ipynb), to compute the rotation matrix between two local coordinate systems we can use the rotation matrices of both coordinate systems:\n", "\n", "\n", "\\begin{equation}\n", "R_{l_1l_2} = R_{Gl_1}^TR_{Gl_2}\n", "\\end{equation}\n", "\n", "\n", "After this, the Euler angles between both coordinate systems can be found using the `arctan2` function as shown previously. " ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Translation and Rotation\n", "\n", "Consider the case where the local coordinate system is translated and rotated in relation to the Global coordinate system as illustrated in the next figure. \n", "
\n", "
translation and rotation 3D
Figure. A point in three-dimensional space represented in two coordinate systems, with one system translated and rotated.
\n", "\n", "The position of point $\\mathbf{P}$ originally described in the local coordinate system, but now described in the Global coordinate system in vector form is: \n", "
\n", "\n", "\\begin{equation}\n", "\\mathbf{P_G} = \\mathbf{L_G} + \\mathbf{R_{Gl}}\\mathbf{P_l}\n", "\\end{equation}\n", "\n", "\n", "This means that we first *disrotate* the local coordinate system and then correct for the translation between the two coordinate systems. Note that we can't invert this order: the point position is expressed in the local coordinate system and we can't add this vector to another vector expressed in the Global coordinate system, first we have to convert the vectors to the same coordinate system.\n", "\n", "If now we want to find the position of a point at the local coordinate system given its position in the Global coordinate system, the rotation matrix and the translation vector, we have to invert the expression above: \n", "
\n", "\n", "\\begin{equation}\n", "\\begin{array}{ll}\n", "\\mathbf{P_G} = \\mathbf{L_G} + \\mathbf{R_{Gl}}\\mathbf{P_l} \\implies \\\\\n", "\\\\\n", "\\mathbf{R_{Gl}^{-1}}\\cdot\\mathbf{P_G} = \\mathbf{R_{Gl}^{-1}}\\left(\\mathbf{L_G} + \\mathbf{R_{Gl}}\\mathbf{P_l}\\right) \\implies \\\\\n", "\\\\\n", "\\mathbf{R_{Gl}^{-1}}\\mathbf{P_G} = \\mathbf{R_{Gl}^{-1}}\\mathbf{L_G} + \\mathbf{R_{Gl}^{-1}}\\mathbf{R_{Gl}}\\mathbf{P_l} \\implies \\\\\n", "\\\\\n", "\\mathbf{P_l} = \\mathbf{R_{Gl}^{-1}}\\left(\\mathbf{P_G}-\\mathbf{L_G}\\right) = \\mathbf{R_{Gl}^T}\\left(\\mathbf{P_G}-\\mathbf{L_G}\\right) \\;\\;\\;\\;\\; \\text{or} \\;\\;\\;\\;\\; \\mathbf{P_l} = \\mathbf{R_{lG}}\\left(\\mathbf{P_G}-\\mathbf{L_G}\\right) \n", "\\end{array}\n", "\\end{equation}\n", "\n", "\n", "The expression above indicates that to perform the inverse operation, to go from the Global to the local coordinate system, we first translate and then rotate the coordinate system." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Transformation matrix\n", "\n", "It is possible to combine the translation and rotation operations in only one matrix, called the transformation matrix: \n", "
\n", "\n", "\\begin{equation}\n", "\\begin{bmatrix}\n", "\\mathbf{P_X} \\\\\n", "\\mathbf{P_Y} \\\\\n", "\\mathbf{P_Z} \\\\\n", "1\n", "\\end{bmatrix} =\n", "\\begin{bmatrix}\n", ". & . & . & \\mathbf{L_{X}} \\\\\n", ". & \\mathbf{R_{Gl}} & . & \\mathbf{L_{Y}} \\\\\n", ". & . & . & \\mathbf{L_{Z}} \\\\\n", "0 & 0 & 0 & 1\n", "\\end{bmatrix}\n", "\\begin{bmatrix}\n", "\\mathbf{P}_x \\\\\n", "\\mathbf{P}_y \\\\\n", "\\mathbf{P}_z \\\\\n", "1\n", "\\end{bmatrix}\n", "\\end{equation}\n", "\n", "\n", "Or simply: \n", "
\n", "\n", "\\begin{equation}\n", "\\mathbf{P_G} = \\mathbf{T_{Gl}}\\mathbf{P_l}\n", "\\end{equation}\n", "\n", "\n", "Remember that in general the transformation matrix is not orthonormal, i.e., its inverse is not equal to its transpose.\n", "\n", "The inverse operation, to express the position at the local coordinate system in terms of the Global reference system, is: \n", "
\n", "\n", "\\begin{equation}\n", "\\mathbf{P_l} = \\mathbf{T_{Gl}^{-1}}\\mathbf{P_G}\n", "\\end{equation}\n", "\n", "
\n", "\n", "\\begin{equation}\n", "\\begin{bmatrix}\n", "\\mathbf{P_x} \\\\\n", "\\mathbf{P_y} \\\\\n", "\\mathbf{P_z} \\\\\n", "1\n", "\\end{bmatrix} =\n", "\\begin{bmatrix}\n", "\\cdot & \\cdot & \\cdot & \\cdot \\\\\n", "\\cdot & \\mathbf{R^{-1}_{Gl}} & \\cdot & -\\mathbf{R^{-1}_{Gl}}\\:\\mathbf{L_G} \\\\\n", "\\cdot & \\cdot & \\cdot & \\cdot \\\\\n", "0 & 0 & 0 & 1\n", "\\end{bmatrix}\n", "\\begin{bmatrix}\n", "\\mathbf{P_X} \\\\\n", "\\mathbf{P_Y} \\\\\n", "\\mathbf{P_Z} \\\\\n", "1\n", "\\end{bmatrix}\n", "\\end{equation}\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Example with actual motion analysis data \n", "\n", "*The data for this example is taken from page 183 of David Winter's book.* \n", "Consider the following marker positions placed on a leg (described in the laboratory coordinate system with coordinates $x, y, z$ in cm, the $x$ axis points forward and the $y$ axes points upward): lateral malleolus (**lm** = [2.92, 10.10, 18.85]), medial malleolus (**mm** = [2.71, 10.22, 26.52]), fibular head (**fh** = [5.05, 41.90, 15.41]), and medial condyle (**mc** = [8.29, 41.88, 26.52]). Define the ankle joint center as the centroid between the **lm** and **mm** markers and the knee joint center as the centroid between the **fh** and **mc** markers. An anatomical coordinate system for the leg can be defined as: the quasi-vertical axis ($y$) passes through the ankle and knee joint centers; a temporary medio-lateral axis ($z$) passes through the two markers on the malleolus, an anterior-posterior as the cross product between the two former calculated orthogonal axes, and the origin at the ankle joint center. \n", " a) Calculate the anatomical coordinate system for the leg as described above. \n", " b) Calculate the rotation matrix and the translation vector for the transformation from the anatomical to the laboratory coordinate system. \n", " c) Calculate the position of each marker and of each joint center at the anatomical coordinate system. \n", " d) Calculate the Cardan angles using the $zxy$ sequence for the orientation of the leg with respect to the laboratory (but remember that the letters chosen to refer to axes are arbitrary, what matters is the directions they represent)." ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:16.347571Z", "start_time": "2021-11-18T00:05:16.343235Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Poition of the ankle joint center: [ 2.815 10.16 22.685]\n", "Poition of the knee joint center: [ 6.67 41.89 20.965]\n" ] } ], "source": [ "# calculation of the joint centers\n", "mm = np.array([2.71, 10.22, 26.52])\n", "lm = np.array([2.92, 10.10, 18.85])\n", "fh = np.array([5.05, 41.90, 15.41])\n", "mc = np.array([8.29, 41.88, 26.52])\n", "ajc = (mm + lm)/2\n", "kjc = (fh + mc)/2\n", "print('Poition of the ankle joint center:', ajc)\n", "print('Poition of the knee joint center:', kjc)" ] }, { "cell_type": "code", "execution_count": 32, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:16.353559Z", "start_time": "2021-11-18T00:05:16.348570Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Versors:\n", "x = [ 0.9925 -0.119 0.029 ]\n", "y = [ 0.1204 0.9913 -0.0537]\n", "z = [-0.0224 0.0568 0.9981]\n", "\n", "Origin = [ 2.815 10.16 22.685]\n" ] } ], "source": [ "# calculation of the anatomical coordinate system axes (basis)\n", "y = kjc - ajc\n", "x = np.cross(y, mm - lm)\n", "z = np.cross(x, y)\n", "print('Versors:')\n", "x = x/np.linalg.norm(x)\n", "y = y/np.linalg.norm(y)\n", "z = z/np.linalg.norm(z)\n", "print('x =', x)\n", "print('y =', y)\n", "print('z =', z)\n", "Oleg = ajc\n", "print('\\nOrigin =', Oleg)" ] }, { "cell_type": "code", "execution_count": 33, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:16.357535Z", "start_time": "2021-11-18T00:05:16.354477Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Rotation matrix from the anatomical to the laboratory coordinate system:\n", " [[ 0.9925 0.1204 -0.0224]\n", " [-0.119 0.9913 0.0568]\n", " [ 0.029 -0.0537 0.9981]]\n", "\n", "Rotation matrix from the laboratory to the anatomical coordinate system:\n", " [[ 0.9925 -0.119 0.029 ]\n", " [ 0.1204 0.9913 -0.0537]\n", " [-0.0224 0.0568 0.9981]]\n" ] } ], "source": [ "# Rotation matrices\n", "RGl = np.array([x, y , z]).T\n", "print('Rotation matrix from the anatomical to the laboratory coordinate system:\\n', RGl)\n", "RlG = RGl.T\n", "print('\\nRotation matrix from the laboratory to the anatomical coordinate system:\\n', RlG)" ] }, { "cell_type": "code", "execution_count": 34, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:16.360892Z", "start_time": "2021-11-18T00:05:16.358343Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Translational vector from the anatomical to the laboratory coordinate system:\n", " [ 2.815 10.16 22.685]\n" ] } ], "source": [ "# Translational vector\n", "OG = np.array([0, 0, 0]) # Laboratory coordinate system origin\n", "LG = Oleg - OG\n", "print('Translational vector from the anatomical to the laboratory coordinate system:\\n', LG)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "To get the coordinates from the laboratory (global) coordinate system to the anatomical (local) coordinate system: \n", "
\n", "\n", "\\begin{equation}\n", "\\mathbf{P_l} = \\mathbf{R_{lG}}\\left(\\mathbf{P_G}-\\mathbf{L_G}\\right)\n", "\\end{equation}\n", "" ] }, { "cell_type": "code", "execution_count": 35, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:16.535145Z", "start_time": "2021-11-18T00:05:16.529662Z" }, "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Coordinates of mm in the anatomical system:\n", " [-0. -0.1592 3.8336]\n", "Coordinates of lm in the anatomical system:\n", " [-0. 0.1592 -3.8336]\n", "Coordinates of fh in the anatomical system:\n", " [-1.7703 32.1229 -5.5078]\n", "Coordinates of mc in the anatomical system:\n", " [ 1.7703 31.8963 5.5078]\n", "Coordinates of kjc in the anatomical system:\n", " [ 0. 32.0096 0. ]\n", "Coordinates of ajc in the anatomical system (origin):\n", " [0. 0. 0.]\n" ] } ], "source": [ "# position of each marker and of each joint center at the anatomical coordinate system\n", "mml = np.dot(RlG, (mm - LG)) # equivalent to the algebraic expression RlG*(mm - LG).T\n", "lml = np.dot(RlG, (lm - LG))\n", "fhl = np.dot(RlG, (fh - LG))\n", "mcl = np.dot(RlG, (mc - LG))\n", "ajcl = np.dot(RlG, (ajc - LG))\n", "kjcl = np.dot(RlG, (kjc - LG))\n", "print('Coordinates of mm in the anatomical system:\\n', mml)\n", "print('Coordinates of lm in the anatomical system:\\n', lml)\n", "print('Coordinates of fh in the anatomical system:\\n', fhl)\n", "print('Coordinates of mc in the anatomical system:\\n', mcl)\n", "print('Coordinates of kjc in the anatomical system:\\n', kjcl)\n", "print('Coordinates of ajc in the anatomical system (origin):\\n', ajcl)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Further reading\n", "\n", "- Read pages 1136-1164 of the 21th chapter of the [Ruina and Rudra's book](http://ruina.tam.cornell.edu/Book/index.html) about elementary introduction to 3D rigid-body dynamics \n", "- [Rotation matrix](https://en.wikipedia.org/wiki/Rotation_matrix) - Wikipedia \n", "- [Euler angles](https://en.wikipedia.org/wiki/Euler_angles) - Wikipedia" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Video lectures on the Internet\n", "\n", "- [Rotation in R3 around the x-axis](https://www.khanacademy.org/math/linear-algebra/matrix-transformations/lin-trans-examples/v/rotation-in-r3-around-the-x-axis) - Khan Academy\n", "- [Modern Robotics, Chapter 3.2.1: Rotation Matrices (Part 1 of 2)](https://youtu.be/OZucG1DY_sY) - Northwestern Robotics. \n", "- [Rotations in 3D](https://youtu.be/wg9bI8-Qx2Q)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Problems\n", "\n", "1. For the example about how the order of rotations of a rigid body affects the orientation shown in a figure above, deduce the rotation matrices for each of the 4 cases shown in the figure. For the first two cases, deduce the rotation matrices from the global to the local coordinate system and for the other two examples, deduce the rotation matrices from the local to the global coordinate system. \n", "\n", "2. Consider the data from problem 7 in the notebook [Frame of reference](https://nbviewer.org/github/BMClab/bmc/blob/master/notebooks/ReferenceFrame.ipynb) where the following anatomical landmark positions are given (units in meters): RASIS=[0.5, 0.8, 0.4], LASIS=[0.55, 0.78, 0.1], RPSIS=[0.3, 0.85, 0.2], and LPSIS=[0.29, 0.78, 0.3]. Deduce the rotation matrices for the global to anatomical coordinate system and for the anatomical to global coordinate system. \n", "\n", "3. For the data from the last example, calculate the Cardan angles using the $zxy$ sequence for the orientation of the leg with respect to the laboratory (but remember that the letters chosen to refer to axes are arbitrary, what matters is the directions they represent). \n", "\n", "4. Write down 4*4 matrices for each of the following (from http://www.eecs.qmul.ac.uk/~sgg/cg/Exers/transformations_ex.html): \n", " 1. To translate by the vector (1, 2, 3) \n", " 2. To scale with respect to the origin by the amount (2, 4, 6) \n", " 3. To rotate around the z-axis by 45 degrees (note sin 45 = cos 45 = 1/sqrt(2)) \n", " 4. To rotate around the x-axis by 45 degrees. \n", "\n", "5. Solve the first two problems from [https://rrg.utk.edu/resources/BME473/assignments/BME473_homework_3.pdf](https://rrg.utk.edu/resources/BME473/assignments/BME473_homework_3.pdf)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## References\n", "\n", "- Corke P (2011) [Robotics, Vision and Control: Fundamental Algorithms in MATLAB](http://www.petercorke.com/RVC/). Springer-Verlag Berlin. \n", "- Robertson G, Caldwell G, Hamill J, Kamen G (2013) [Research Methods in Biomechanics](http://books.google.com.br/books?id=gRn8AAAAQBAJ). 2nd Edition. Human Kinetics. \n", "- [Maths - Euler Angles](http://www.euclideanspace.com/maths/geometry/rotations/euler/). \n", "- Murray RM, Li Z, Sastry SS (1994) [A Mathematical Introduction to Robotic Manipulation](http://www.cds.caltech.edu/~murray/mlswiki/index.php/Main_Page). Boca Raton, CRC Press. \n", "- Rade D (2017) [Cinemática e Dinâmica para Engenharia](https://www.grupogen.com.br/e-book-cinematica-e-dinamica-para-engenharia). Grupo GEN. \n", "- Ruina A, Rudra P (2013) [Introduction to Statics and Dynamics](http://ruina.tam.cornell.edu/Book/index.html). Oxford University Press. \n", "- Siciliano B, Sciavicco L, Villani L, Oriolo G (2009) [Robotics - Modelling, Planning and Control](http://books.google.com.br/books/about/Robotics.html?hl=pt-BR&id=jPCAFmE-logC). Springer-Verlag London.\n", "- Winter DA (2009) [Biomechanics and motor control of human movement](http://books.google.com.br/books?id=_bFHL08IWfwC). 4 ed. Hoboken, USA: Wiley. \n", "- Zatsiorsky VM (1997) [Kinematics of Human Motion](http://books.google.com.br/books/about/Kinematics_of_Human_Motion.html?id=Pql_xXdbrMcC&redir_esc=y). Champaign, Human Kinetics." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Function `euler_rotmatrix.py`" ] }, { "cell_type": "code", "execution_count": 36, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:17.420360Z", "start_time": "2021-11-18T00:05:17.407913Z" } }, "outputs": [], "source": [ "# %load ./../functions/euler_rotmat.py\n", "#!/usr/bin/env python\n", "\n", "\"\"\"Euler rotation matrix given sequence, frame, and angles.\"\"\"\n", "\n", "from __future__ import division, print_function\n", "\n", "__author__ = 'Marcos Duarte, https://github.com/demotu/BMC'\n", "__version__ = 'euler_rotmat.py v.1 2014/03/10'\n", "\n", "\n", "def euler_rotmat(order='xyz', frame='local', angles=None, unit='deg',\n", " str_symbols=None, showA=True, showN=True):\n", " \"\"\"Euler rotation matrix given sequence, frame, and angles.\n", " \n", " This function calculates the algebraic rotation matrix (3x3) for a given\n", " sequence ('order' argument) of up to three elemental rotations of a given\n", " coordinate system ('frame' argument) around another coordinate system, the\n", " Euler (or Eulerian) angles [1]_.\n", "\n", " This function also calculates the numerical values of the rotation matrix\n", " when numerical values for the angles are inputed for each rotation axis.\n", " Use None as value if the rotation angle for the particular axis is unknown.\n", "\n", " The symbols for the angles are: alpha, beta, and gamma for the first,\n", " second, and third rotations, respectively.\n", " The matrix product is calulated from right to left and in the specified\n", " sequence for the Euler angles. The first letter will be the first rotation.\n", " \n", " The function will print and return the algebraic rotation matrix and the\n", " numerical rotation matrix if angles were inputed.\n", "\n", " Parameters\n", " ----------\n", " order : string, optional (default = 'xyz')\n", " Sequence for the Euler angles, any combination of the letters\n", " x, y, and z with 1 to 3 letters is accepted to denote the\n", " elemental rotations. The first letter will be the first rotation.\n", "\n", " frame : string, optional (default = 'local')\n", " Coordinate system for which the rotations are calculated.\n", " Valid values are 'local' or 'global'.\n", "\n", " angles : list, array, or bool, optional (default = None)\n", " Numeric values of the rotation angles ordered as the 'order'\n", " parameter. Enter None for a rotation whith unknown value.\n", "\n", " unit : str, optional (default = 'deg')\n", " Unit of the input angles.\n", " \n", " str_symbols : list of strings, optional (default = None)\n", " New symbols for the angles, for instance, ['theta', 'phi', 'psi']\n", " \n", " showA : bool, optional (default = True)\n", " True (1) displays the Algebraic rotation matrix in rich format.\n", " False (0) to not display.\n", "\n", " showN : bool, optional (default = True)\n", " True (1) displays the Numeric rotation matrix in rich format.\n", " False (0) to not display.\n", " \n", " Returns\n", " -------\n", " R : Matrix Sympy object\n", " Rotation matrix (3x3) in algebraic format.\n", "\n", " Rn : Numpy array or Matrix Sympy object (only if angles are inputed)\n", " Numeric rotation matrix (if values for all angles were inputed) or\n", " a algebraic matrix with some of the algebraic angles substituted\n", " by the corresponding inputed numeric values.\n", "\n", " Notes\n", " -----\n", " This code uses Sympy, the Python library for symbolic mathematics, to\n", " calculate the algebraic rotation matrix and shows this matrix in latex form\n", " possibly for using with the IPython Notebook, see [1]_.\n", "\n", " References\n", " ----------\n", " .. [1] http://nbviewer.ipython.org/github/duartexyz/BMC/blob/master/Transformation3D.ipynb\n", "\n", " Examples\n", " --------\n", " >>> # import function\n", " >>> from euler_rotmat import euler_rotmat\n", " >>> # Default options: xyz sequence, local frame and show matrix\n", " >>> R = euler_rotmat()\n", " >>> # XYZ sequence (around global (fixed) coordinate system)\n", " >>> R = euler_rotmat(frame='global')\n", " >>> # Enter numeric values for all angles and show both matrices\n", " >>> R, Rn = euler_rotmat(angles=[90, 90, 90])\n", " >>> # show what is returned\n", " >>> euler_rotmat(angles=[90, 90, 90])\n", " >>> # show only the rotation matrix for the elemental rotation at x axis\n", " >>> R = euler_rotmat(order='x')\n", " >>> # zxz sequence and numeric value for only one angle\n", " >>> R, Rn = euler_rotmat(order='zxz', angles=[None, 0, None])\n", " >>> # input values in radians:\n", " >>> import numpy as np\n", " >>> R, Rn = euler_rotmat(order='zxz', angles=[None, np.pi, None], unit='rad')\n", " >>> # shows only the numeric matrix\n", " >>> R, Rn = euler_rotmat(order='zxz', angles=[90, 0, None], showA='False')\n", " >>> # Change the angles' symbols\n", " >>> R = euler_rotmat(order='zxz', str_symbols=['theta', 'phi', 'psi'])\n", " >>> # Negativate the angles' symbols\n", " >>> R = euler_rotmat(order='zxz', str_symbols=['-theta', '-phi', '-psi'])\n", " >>> # all algebraic matrices for all possible sequences for the local frame\n", " >>> s=['xyz','xzy','yzx','yxz','zxy','zyx','xyx','xzx','yzy','yxy','zxz','zyz']\n", " >>> for seq in s: R = euler_rotmat(order=seq)\n", " >>> # all algebraic matrices for all possible sequences for the global frame\n", " >>> for seq in s: R = euler_rotmat(order=seq, frame='global')\n", " \"\"\"\n", "\n", " import numpy as np\n", " import sympy as sym\n", " try:\n", " from IPython.core.display import Math, display\n", " ipython = True\n", " except:\n", " ipython = False\n", "\n", " angles = np.asarray(np.atleast_1d(angles), dtype=np.float64)\n", " if ~np.isnan(angles).all(): \n", " if len(order) != angles.size:\n", " raise ValueError(\"Parameters 'order' and 'angles' (when \" + \n", " \"different from None) must have the same size.\")\n", "\n", " x, y, z = sym.symbols('x, y, z')\n", " sig = [1, 1, 1]\n", " if str_symbols is None:\n", " a, b, g = sym.symbols('alpha, beta, gamma')\n", " else:\n", " s = str_symbols\n", " if s[0][0] == '-': s[0] = s[0][1:]; sig[0] = -1\n", " if s[1][0] == '-': s[1] = s[1][1:]; sig[1] = -1\n", " if s[2][0] == '-': s[2] = s[2][1:]; sig[2] = -1 \n", " a, b, g = sym.symbols(s)\n", "\n", " var = {'x': x, 'y': y, 'z': z, 0: a, 1: b, 2: g}\n", " # Elemental rotation matrices for xyz (local)\n", " cos, sin = sym.cos, sym.sin\n", " Rx = sym.Matrix([[1, 0, 0], [0, cos(x), sin(x)], [0, -sin(x), cos(x)]])\n", " Ry = sym.Matrix([[cos(y), 0, -sin(y)], [0, 1, 0], [sin(y), 0, cos(y)]])\n", " Rz = sym.Matrix([[cos(z), sin(z), 0], [-sin(z), cos(z), 0], [0, 0, 1]])\n", "\n", " if frame.lower() == 'global':\n", " Rs = {'x': Rx.T, 'y': Ry.T, 'z': Rz.T}\n", " order = order.upper()\n", " else:\n", " Rs = {'x': Rx, 'y': Ry, 'z': Rz}\n", " order = order.lower()\n", "\n", " R = Rn = sym.Matrix(sym.Identity(3))\n", " str1 = r'\\mathbf{R}_{%s}( ' %frame # last space needed for order=''\n", " #str2 = [r'\\%s'%var[0], r'\\%s'%var[1], r'\\%s'%var[2]]\n", " str2 = [1, 1, 1] \n", " for i in range(len(order)):\n", " Ri = Rs[order[i].lower()].subs(var[order[i].lower()], sig[i] * var[i]) \n", " R = Ri * R\n", " if sig[i] > 0:\n", " str2[i] = '%s:%s' %(order[i], sym.latex(var[i]))\n", " else:\n", " str2[i] = '%s:-%s' %(order[i], sym.latex(var[i]))\n", " str1 = str1 + str2[i] + ','\n", " if ~np.isnan(angles).all() and ~np.isnan(angles[i]):\n", " if unit[:3].lower() == 'deg':\n", " angles[i] = np.deg2rad(angles[i])\n", " Rn = Ri.subs(var[i], angles[i]) * Rn\n", " #Rn = sym.lambdify(var[i], Ri, 'numpy')(angles[i]) * Rn\n", " str2[i] = str2[i] + '=%.0f^o' %np.around(np.rad2deg(angles[i]), 0)\n", " else:\n", " Rn = Ri * Rn\n", "\n", " Rn = sym.simplify(Rn) # for trigonometric relations\n", "\n", " try:\n", " # nsimplify only works if there are symbols\n", " Rn2 = sym.latex(sym.nsimplify(Rn, tolerance=1e-8).n(chop=True, prec=4))\n", " except:\n", " Rn2 = sym.latex(Rn.n(chop=True, prec=4))\n", " # there are no symbols, pass it as Numpy array\n", " Rn = np.asarray(Rn)\n", " \n", " if showA and ipython:\n", " display(Math(str1[:-1] + ') =' + sym.latex(R, mat_str='matrix')))\n", "\n", " if showN and ~np.isnan(angles).all() and ipython:\n", " str2 = ',\\;'.join(str2[:angles.size])\n", " display(Math(r'\\mathbf{R}_{%s}(%s)=%s' %(frame, str2, Rn2)))\n", "\n", " if np.isnan(angles).all():\n", " return R\n", " else:\n", " return R, Rn\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Appendix" ] }, { "cell_type": "markdown", "metadata": { "ExecuteTime": { "end_time": "2021-11-17T15:29:59.512691Z", "start_time": "2021-11-17T15:29:59.511016Z" } }, "source": [ "### How to load .trc files" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Using Pandas, to load a .trc file, we must specify the parameters: \n", "- 'sep': separator between columns \n", "- 'header': by default, Pandas will infer the header and read the first line as the header \n", "- 'skiprows': a .trc file has 6 columns of text file before the numerica data" ] }, { "cell_type": "code", "execution_count": 37, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:18.132182Z", "start_time": "2021-11-18T00:05:17.969945Z" } }, "outputs": [], "source": [ "import numpy as np\n", "import pandas as pd" ] }, { "cell_type": "code", "execution_count": 42, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:18.155971Z", "start_time": "2021-11-18T00:05:18.133316Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
0123456789...158159160161162163164165166167
010.000516.54236966.88000-306.10416531.67438981.34631-560.16077315.74045977.08398...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
120.007523.92200967.96594-308.23773539.83044982.78345-561.77612323.29425977.64166...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
230.013531.20807968.92493-310.12112547.60663984.00653-563.42725330.56866978.15283...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
340.020538.24219969.77612-311.72064555.40649985.09637-564.85162337.63867978.81207...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
450.027545.11420970.81128-313.07266563.14301986.00916-566.06659344.50589979.21619...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
..................................................................
1791801.1931875.04834958.35535-290.670041897.06970960.27222-546.900941678.10901977.31805...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
1801811.2001885.37988958.09222-291.674291907.52881960.81549-547.971441688.67249977.12646...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
1811821.2071895.50452957.80798-292.718751917.97290961.49707-548.997991699.17065977.02045...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
1821831.2131905.40540957.61029-293.842501928.34631962.31494-550.087041709.65186976.93237...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
1831841.2201916.91516958.57813-294.854431938.59363963.29272-551.263061720.05396976.89636...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
\n", "

184 rows × 168 columns

\n", "
" ], "text/plain": [ " 0 1 2 3 4 5 6 \\\n", "0 1 0.000 516.54236 966.88000 -306.10416 531.67438 981.34631 \n", "1 2 0.007 523.92200 967.96594 -308.23773 539.83044 982.78345 \n", "2 3 0.013 531.20807 968.92493 -310.12112 547.60663 984.00653 \n", "3 4 0.020 538.24219 969.77612 -311.72064 555.40649 985.09637 \n", "4 5 0.027 545.11420 970.81128 -313.07266 563.14301 986.00916 \n", ".. ... ... ... ... ... ... ... \n", "179 180 1.193 1875.04834 958.35535 -290.67004 1897.06970 960.27222 \n", "180 181 1.200 1885.37988 958.09222 -291.67429 1907.52881 960.81549 \n", "181 182 1.207 1895.50452 957.80798 -292.71875 1917.97290 961.49707 \n", "182 183 1.213 1905.40540 957.61029 -293.84250 1928.34631 962.31494 \n", "183 184 1.220 1916.91516 958.57813 -294.85443 1938.59363 963.29272 \n", "\n", " 7 8 9 ... 158 159 160 161 162 163 164 \\\n", "0 -560.16077 315.74045 977.08398 ... NaN NaN NaN NaN NaN NaN NaN \n", "1 -561.77612 323.29425 977.64166 ... NaN NaN NaN NaN NaN NaN NaN \n", "2 -563.42725 330.56866 978.15283 ... NaN NaN NaN NaN NaN NaN NaN \n", "3 -564.85162 337.63867 978.81207 ... NaN NaN NaN NaN NaN NaN NaN \n", "4 -566.06659 344.50589 979.21619 ... NaN NaN NaN NaN NaN NaN NaN \n", ".. ... ... ... ... ... ... ... ... ... ... ... \n", "179 -546.90094 1678.10901 977.31805 ... NaN NaN NaN NaN NaN NaN NaN \n", "180 -547.97144 1688.67249 977.12646 ... NaN NaN NaN NaN NaN NaN NaN \n", "181 -548.99799 1699.17065 977.02045 ... NaN NaN NaN NaN NaN NaN NaN \n", "182 -550.08704 1709.65186 976.93237 ... NaN NaN NaN NaN NaN NaN NaN \n", "183 -551.26306 1720.05396 976.89636 ... NaN NaN NaN NaN NaN NaN NaN \n", "\n", " 165 166 167 \n", "0 NaN NaN NaN \n", "1 NaN NaN NaN \n", "2 NaN NaN NaN \n", "3 NaN NaN NaN \n", "4 NaN NaN NaN \n", ".. ... ... ... \n", "179 NaN NaN NaN \n", "180 NaN NaN NaN \n", "181 NaN NaN NaN \n", "182 NaN NaN NaN \n", "183 NaN NaN NaN \n", "\n", "[184 rows x 168 columns]" ] }, "execution_count": 42, "metadata": {}, "output_type": "execute_result" } ], "source": [ "data = pd.read_csv('./../data/walk.trc', sep='\\t', header=None, skiprows=6)\n", "data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "But now the columns of the pandas dataframe don't have names and it will be easier if the columns have as names the marker's name (line 4 of the .trc file) and its direction (line 5). \n", "The solution is to first read only the header of the .trc file to get the markers' names and directions and read a second time only to get the numeric data. \n", "We wrote a function to do that, named 'read_trc.py' and it is stored in the functions directory of the BMC repository. \n", "Here is how to use this function:" ] }, { "cell_type": "code", "execution_count": 43, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:18.166697Z", "start_time": "2021-11-18T00:05:18.162849Z" } }, "outputs": [], "source": [ "import sys\n", "sys.path.insert(1, r'./../functions') # add to pythonpath\n", "\n", "from read_trc import read_trc" ] }, { "cell_type": "code", "execution_count": 44, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:18.222167Z", "start_time": "2021-11-18T00:05:18.167915Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Opening file \"./../data/walk.trc\" ... Number of markers changed from 28 to 55.\n", "done.\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Frame#TimeR.ASISxR.ASISyR.ASISzL.ASISxL.ASISyL.ASISzR.PSISxR.PSISy...V_R.TT_KJCzV_L.TT_KJCxV_L.TT_KJCyV_L.TT_KJCzV_R.MT2xV_R.MT2yV_R.MT2zV_L.MT2xV_L.MT2yV_L.MT2z
010.000516.54236966.88000-306.10416531.67438981.34631-560.16077315.74045977.08398...0.00.00.00.00.00.00.00.00.00.0
120.007523.92200967.96594-308.23773539.83044982.78345-561.77612323.29425977.64166...0.00.00.00.00.00.00.00.00.00.0
230.013531.20807968.92493-310.12112547.60663984.00653-563.42725330.56866978.15283...0.00.00.00.00.00.00.00.00.00.0
340.020538.24219969.77612-311.72064555.40649985.09637-564.85162337.63867978.81207...0.00.00.00.00.00.00.00.00.00.0
450.027545.11420970.81128-313.07266563.14301986.00916-566.06659344.50589979.21619...0.00.00.00.00.00.00.00.00.00.0
..................................................................
1791801.1931875.04834958.35535-290.670041897.06970960.27222-546.900941678.10901977.31805...0.00.00.00.00.00.00.00.00.00.0
1801811.2001885.37988958.09222-291.674291907.52881960.81549-547.971441688.67249977.12646...0.00.00.00.00.00.00.00.00.00.0
1811821.2071895.50452957.80798-292.718751917.97290961.49707-548.997991699.17065977.02045...0.00.00.00.00.00.00.00.00.00.0
1821831.2131905.40540957.61029-293.842501928.34631962.31494-550.087041709.65186976.93237...0.00.00.00.00.00.00.00.00.00.0
1831841.2201916.91516958.57813-294.854431938.59363963.29272-551.263061720.05396976.89636...0.00.00.00.00.00.00.00.00.00.0
\n", "

184 rows × 167 columns

\n", "
" ], "text/plain": [ " Frame# Time R.ASISx R.ASISy R.ASISz L.ASISx L.ASISy \\\n", "0 1 0.000 516.54236 966.88000 -306.10416 531.67438 981.34631 \n", "1 2 0.007 523.92200 967.96594 -308.23773 539.83044 982.78345 \n", "2 3 0.013 531.20807 968.92493 -310.12112 547.60663 984.00653 \n", "3 4 0.020 538.24219 969.77612 -311.72064 555.40649 985.09637 \n", "4 5 0.027 545.11420 970.81128 -313.07266 563.14301 986.00916 \n", ".. ... ... ... ... ... ... ... \n", "179 180 1.193 1875.04834 958.35535 -290.67004 1897.06970 960.27222 \n", "180 181 1.200 1885.37988 958.09222 -291.67429 1907.52881 960.81549 \n", "181 182 1.207 1895.50452 957.80798 -292.71875 1917.97290 961.49707 \n", "182 183 1.213 1905.40540 957.61029 -293.84250 1928.34631 962.31494 \n", "183 184 1.220 1916.91516 958.57813 -294.85443 1938.59363 963.29272 \n", "\n", " L.ASISz R.PSISx R.PSISy ... V_R.TT_KJCz V_L.TT_KJCx \\\n", "0 -560.16077 315.74045 977.08398 ... 0.0 0.0 \n", "1 -561.77612 323.29425 977.64166 ... 0.0 0.0 \n", "2 -563.42725 330.56866 978.15283 ... 0.0 0.0 \n", "3 -564.85162 337.63867 978.81207 ... 0.0 0.0 \n", "4 -566.06659 344.50589 979.21619 ... 0.0 0.0 \n", ".. ... ... ... ... ... ... \n", "179 -546.90094 1678.10901 977.31805 ... 0.0 0.0 \n", "180 -547.97144 1688.67249 977.12646 ... 0.0 0.0 \n", "181 -548.99799 1699.17065 977.02045 ... 0.0 0.0 \n", "182 -550.08704 1709.65186 976.93237 ... 0.0 0.0 \n", "183 -551.26306 1720.05396 976.89636 ... 0.0 0.0 \n", "\n", " V_L.TT_KJCy V_L.TT_KJCz V_R.MT2x V_R.MT2y V_R.MT2z V_L.MT2x \\\n", "0 0.0 0.0 0.0 0.0 0.0 0.0 \n", "1 0.0 0.0 0.0 0.0 0.0 0.0 \n", "2 0.0 0.0 0.0 0.0 0.0 0.0 \n", "3 0.0 0.0 0.0 0.0 0.0 0.0 \n", "4 0.0 0.0 0.0 0.0 0.0 0.0 \n", ".. ... ... ... ... ... ... \n", "179 0.0 0.0 0.0 0.0 0.0 0.0 \n", "180 0.0 0.0 0.0 0.0 0.0 0.0 \n", "181 0.0 0.0 0.0 0.0 0.0 0.0 \n", "182 0.0 0.0 0.0 0.0 0.0 0.0 \n", "183 0.0 0.0 0.0 0.0 0.0 0.0 \n", "\n", " V_L.MT2y V_L.MT2z \n", "0 0.0 0.0 \n", "1 0.0 0.0 \n", "2 0.0 0.0 \n", "3 0.0 0.0 \n", "4 0.0 0.0 \n", ".. ... ... \n", "179 0.0 0.0 \n", "180 0.0 0.0 \n", "181 0.0 0.0 \n", "182 0.0 0.0 \n", "183 0.0 0.0 \n", "\n", "[184 rows x 167 columns]" ] }, "execution_count": 44, "metadata": {}, "output_type": "execute_result" } ], "source": [ "h, data = read_trc('./../data/walk.trc', fname2='', dropna=False, na=0.0, fmt='uni')\n", "data" ] }, { "cell_type": "code", "execution_count": 45, "metadata": { "ExecuteTime": { "end_time": "2021-11-18T00:05:18.296442Z", "start_time": "2021-11-18T00:05:18.223094Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Opening file \"./../data/walk.trc\" ... Number of markers changed from 28 to 55.\n", "done.\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
MarkerR_ASISL_ASISR_PSISL_PSIS...V_R_TT_KJCV_L_TT_KJCV_R_MT2V_L_MT2
CoordinateXYZXYZXYZX...ZXYZXYZXYZ
XYZX1Y1Z1X2Y2Z2X3Y3Z3X4...Z52X53Y53Z53X54Y54Z54X55Y55Z55
Time
0.000516.54236966.88000-306.10416531.67438981.34631-560.16077315.74045977.08398-388.89532312.81592...0.00.00.00.00.00.00.00.00.00.0
0.007523.92200967.96594-308.23773539.83044982.78345-561.77612323.29425977.64166-391.11392320.88770...0.00.00.00.00.00.00.00.00.00.0
0.013531.20807968.92493-310.12112547.60663984.00653-563.42725330.56866978.15283-393.34290328.17276...0.00.00.00.00.00.00.00.00.00.0
0.020538.24219969.77612-311.72064555.40649985.09637-564.85162337.63867978.81207-395.60764335.90649...0.00.00.00.00.00.00.00.00.00.0
0.027545.11420970.81128-313.07266563.14301986.00916-566.06659344.50589979.21619-397.85052343.31287...0.00.00.00.00.00.00.00.00.00.0
..................................................................
1.1931875.04834958.35535-290.670041897.06970960.27222-546.900941678.10901977.31805-385.062291678.84143...0.00.00.00.00.00.00.00.00.00.0
1.2001885.37988958.09222-291.674291907.52881960.81549-547.971441688.67249977.12646-386.238681689.18640...0.00.00.00.00.00.00.00.00.00.0
1.2071895.50452957.80798-292.718751917.97290961.49707-548.997991699.17065977.02045-387.413641699.72668...0.00.00.00.00.00.00.00.00.00.0
1.2131905.40540957.61029-293.842501928.34631962.31494-550.087041709.65186976.93237-388.508031710.00281...0.00.00.00.00.00.00.00.00.00.0
1.2201916.91516958.57813-294.854431938.59363963.29272-551.263061720.05396976.89636-389.467961720.32813...0.00.00.00.00.00.00.00.00.00.0
\n", "

184 rows × 165 columns

\n", "
" ], "text/plain": [ "Marker R_ASIS L_ASIS \\\n", "Coordinate X Y Z X Y \n", "XYZ X1 Y1 Z1 X2 Y2 \n", "Time \n", "0.000 516.54236 966.88000 -306.10416 531.67438 981.34631 \n", "0.007 523.92200 967.96594 -308.23773 539.83044 982.78345 \n", "0.013 531.20807 968.92493 -310.12112 547.60663 984.00653 \n", "0.020 538.24219 969.77612 -311.72064 555.40649 985.09637 \n", "0.027 545.11420 970.81128 -313.07266 563.14301 986.00916 \n", "... ... ... ... ... ... \n", "1.193 1875.04834 958.35535 -290.67004 1897.06970 960.27222 \n", "1.200 1885.37988 958.09222 -291.67429 1907.52881 960.81549 \n", "1.207 1895.50452 957.80798 -292.71875 1917.97290 961.49707 \n", "1.213 1905.40540 957.61029 -293.84250 1928.34631 962.31494 \n", "1.220 1916.91516 958.57813 -294.85443 1938.59363 963.29272 \n", "\n", "Marker R_PSIS L_PSIS ... \\\n", "Coordinate Z X Y Z X ... \n", "XYZ Z2 X3 Y3 Z3 X4 ... \n", "Time ... \n", "0.000 -560.16077 315.74045 977.08398 -388.89532 312.81592 ... \n", "0.007 -561.77612 323.29425 977.64166 -391.11392 320.88770 ... \n", "0.013 -563.42725 330.56866 978.15283 -393.34290 328.17276 ... \n", "0.020 -564.85162 337.63867 978.81207 -395.60764 335.90649 ... \n", "0.027 -566.06659 344.50589 979.21619 -397.85052 343.31287 ... \n", "... ... ... ... ... ... ... \n", "1.193 -546.90094 1678.10901 977.31805 -385.06229 1678.84143 ... \n", "1.200 -547.97144 1688.67249 977.12646 -386.23868 1689.18640 ... \n", "1.207 -548.99799 1699.17065 977.02045 -387.41364 1699.72668 ... \n", "1.213 -550.08704 1709.65186 976.93237 -388.50803 1710.00281 ... \n", "1.220 -551.26306 1720.05396 976.89636 -389.46796 1720.32813 ... \n", "\n", "Marker V_R_TT_KJC V_L_TT_KJC V_R_MT2 V_L_MT2 \n", "Coordinate Z X Y Z X Y Z X Y Z \n", "XYZ Z52 X53 Y53 Z53 X54 Y54 Z54 X55 Y55 Z55 \n", "Time \n", "0.000 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n", "0.007 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n", "0.013 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n", "0.020 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n", "0.027 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n", "... ... ... ... ... ... ... ... ... ... ... \n", "1.193 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n", "1.200 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n", "1.207 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n", "1.213 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n", "1.220 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n", "\n", "[184 rows x 165 columns]" ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ "h, data = read_trc('./../data/walk.trc', fname2='', dropna=False, na=0.0, fmt='multi')\n", "data" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "anaconda-cloud": {}, "hide_input": false, "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.15" }, "latex_envs": { "LaTeX_envs_menu_present": true, "autoclose": false, "autocomplete": true, "bibliofile": "biblio.bib", "cite_by": "apalike", "current_citInitial": 1, "eqLabelWithNumbers": true, "eqNumInitial": 1, "hotkeys": { "equation": "Ctrl-E", "itemize": "Ctrl-I" }, "labels_anchors": false, "latex_user_defs": false, "report_style_numbering": false, "user_envs_cfg": false }, "nbTranslate": { "displayLangs": [ "*" ], "hotkey": "alt-t", "langInMainMenu": true, "sourceLang": "en", "targetLang": "fr", "useGoogleTranslate": true }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": true, "sideBar": true, "skip_h1_title": true, "title_cell": "Contents", "title_sidebar": "Contents", "toc_cell": true, "toc_position": { "height": "calc(100% - 180px)", "left": "10px", "top": "150px", "width": "256px" }, "toc_section_display": "block", "toc_window_display": false }, "varInspector": { "cols": { "lenName": 16, "lenType": 16, "lenVar": 40 }, "kernels_config": { "python": { "delete_cmd_postfix": "", "delete_cmd_prefix": "del ", "library": "var_list.py", "varRefreshCmd": "print(var_dic_list())" }, "r": { "delete_cmd_postfix": ") ", "delete_cmd_prefix": "rm(", "library": "var_list.r", "varRefreshCmd": "cat(var_dic_list()) " } }, "types_to_exclude": [ "module", "function", "builtin_function_or_method", "instance", "_Feature" ], "window_display": false } }, "nbformat": 4, "nbformat_minor": 4 }