diff --git a/doc/source/tutorials/opticFlow_tutorial.ipynb b/doc/source/tutorials/opticFlow_tutorial.ipynb index 4cf6c8b2e7dfb0b05e9ae007a8f0ef622f613b0e..5cab175c8dd1aba4e06d29d19416956b84fa7b99 100644 --- a/doc/source/tutorials/opticFlow_tutorial.ipynb +++ b/doc/source/tutorials/opticFlow_tutorial.ipynb @@ -21,11 +21,23 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": { - "collapsed": true + "collapsed": false }, - "outputs": [], + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'optic_flow' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m<ipython-input-1-b9f43b3f8149>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0moptic_flow\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mscene\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mviewing_directions\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mvelocity\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mdistance_channel\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m3\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;31mNameError\u001b[0m: name 'optic_flow' is not defined" + ] + } + ], "source": [ "optic_flow(scene, viewing_directions, velocity, distance_channel=3)\n" ] @@ -54,7 +66,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 5, "metadata": { "collapsed": false }, @@ -73,7 +85,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 6, "metadata": { "collapsed": true }, @@ -103,7 +115,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 7, "metadata": { "collapsed": false }, @@ -161,7 +173,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 5, "metadata": { "collapsed": false }, @@ -182,7 +194,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 8, "metadata": { "collapsed": false }, @@ -190,10 +202,10 @@ { "data": { "text/plain": [ - "<matplotlib.image.AxesImage at 0x7f5282afeef0>" + "<matplotlib.image.AxesImage at 0x7fa2a919fe48>" ] }, - "execution_count": 6, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" }, @@ -201,7 +213,7 @@ "data": { "image/png": "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\n", "text/plain": [ - "<matplotlib.figure.Figure at 0x7f5282d8feb8>" + "<matplotlib.figure.Figure at 0x7fa3001030f0>" ] }, "metadata": {}, @@ -253,13 +265,330 @@ ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": { "collapsed": true }, + "source": [ + "# Example with a trajectory\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dict_keys(['__globals__', '__version__', 'trajectory', 'cylinders', 'nests', '__header__'])\n", + "(7661, 4)\n", + "conv rzyx\n" + ] + }, + { + "data": { + "text/html": [ + "<div>\n", + "<style scoped>\n", + " .dataframe tbody tr th:only-of-type {\n", + " vertical-align: middle;\n", + " }\n", + "\n", + " .dataframe tbody tr th {\n", + " vertical-align: top;\n", + " }\n", + "\n", + " .dataframe thead tr th {\n", + " text-align: left;\n", + " }\n", + "</style>\n", + "<table border=\"1\" class=\"dataframe\">\n", + " <thead>\n", + " <tr>\n", + " <th></th>\n", + " <th colspan=\"3\" halign=\"left\">location</th>\n", + " <th colspan=\"3\" halign=\"left\">rzyx</th>\n", + " </tr>\n", + " <tr>\n", + " <th></th>\n", + " <th>x</th>\n", + " <th>y</th>\n", + " <th>z</th>\n", + " <th>alpha_0</th>\n", + " <th>alpha_1</th>\n", + " <th>alpha_2</th>\n", + " </tr>\n", + " </thead>\n", + " <tbody>\n", + " <tr>\n", + " <th>0</th>\n", + " <td>3.056519</td>\n", + " <td>-214.990482</td>\n", + " <td>9.330593</td>\n", + " <td>2.79751</td>\n", + " <td>0</td>\n", + " <td>0</td>\n", + " </tr>\n", + " <tr>\n", + " <th>1</th>\n", + " <td>4.611665</td>\n", + " <td>-215.020314</td>\n", + " <td>8.424138</td>\n", + " <td>2.80863</td>\n", + " <td>0</td>\n", + " <td>0</td>\n", + " </tr>\n", + " <tr>\n", + " <th>2</th>\n", + " <td>4.556650</td>\n", + " <td>-214.593236</td>\n", + " <td>9.185016</td>\n", + " <td>2.81407</td>\n", + " <td>0</td>\n", + " <td>0</td>\n", + " </tr>\n", + " <tr>\n", + " <th>3</th>\n", + " <td>4.643091</td>\n", + " <td>-213.829769</td>\n", + " <td>10.542035</td>\n", + " <td>2.82704</td>\n", + " <td>0</td>\n", + " <td>0</td>\n", + " </tr>\n", + " <tr>\n", + " <th>4</th>\n", + " <td>4.647302</td>\n", + " <td>-214.431592</td>\n", + " <td>7.461187</td>\n", + " <td>2.82896</td>\n", + " <td>0</td>\n", + " <td>0</td>\n", + " </tr>\n", + " </tbody>\n", + "</table>\n", + "</div>" + ], + "text/plain": [ + " location rzyx \n", + " x y z alpha_0 alpha_1 alpha_2\n", + "0 3.056519 -214.990482 9.330593 2.79751 0 0\n", + "1 4.611665 -215.020314 8.424138 2.80863 0 0\n", + "2 4.556650 -214.593236 9.185016 2.81407 0 0\n", + "3 4.643091 -213.829769 10.542035 2.82704 0 0\n", + "4 4.647302 -214.431592 7.461187 2.82896 0 0" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from scipy.io import loadmat\n", + "import numpy as np\n", + "import os\n", + "from navipy.trajectories import Trajectory\n", + "import pkg_resources\n", + "# Use the trafile from the resources\n", + "# You can adapt this code, by changing trajfile \n", + "# with your own trajectory file\n", + "trajfile = pkg_resources.resource_filename(\n", + " 'navipy',\n", + " 'resources/sample_experiment/Lobecke_JEB_2018/Y101_OBFlight_0001.mat')\n", + "csvtrajfile, _ = os.path.splitext(trajfile)\n", + "csvtrajfile = csvtrajfile+'.csv'\n", + "mymat = loadmat(trajfile)\n", + "# matlab files are loaded in a dictionary\n", + "# we need to identify, under which key the trajectory has been saved\n", + "print(mymat.keys())\n", + "key = 'trajectory'\n", + "# Arrays are placed in a tupple. We need to access the level\n", + "# of the array itself.\n", + "mymat = mymat[key][0][0][0]\n", + "# The array should be a numpy array, and therefore as the .shape function\n", + "# to display the array size:\n", + "print(mymat.shape)\n", + "# In this example the array has 7661 rows and 4 columns\n", + "# the columns are: x,y,z, yaw. Therefore pitch and roll were assumed to be constant (here null)\n", + "# We can therefore init a trajectory with the convention for rotation\n", + "# often yaw-pitch-roll (i.e. 'rzyx') and with indeces the number of sampling points we have \n", + "# in the trajectory\n", + "rotconvention = 'rzyx'\n", + "indeces = np.arange(0,mymat.shape[0])\n", + "mytraj = Trajectory(rotconv = rotconvention, indeces=indeces)\n", + "# We now can assign the values\n", + "mytraj.x = mymat[:,0]\n", + "mytraj.y = mymat[:,1]\n", + "mytraj.z = mymat[:,2]\n", + "mytraj.alpha_0 = mymat[:,3]\n", + "mytraj.alpha_1 = 0\n", + "mytraj.alpha_2 = 0\n", + "# We can then save mytraj as csv file for example\n", + "mytraj.to_csv(csvtrajfile)\n", + "mytraj.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Then we need the velocity to calculate the optic flow" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/html": [ + "<div>\n", + "<style scoped>\n", + " .dataframe tbody tr th:only-of-type {\n", + " vertical-align: middle;\n", + " }\n", + "\n", + " .dataframe tbody tr th {\n", + " vertical-align: top;\n", + " }\n", + "\n", + " .dataframe thead th {\n", + " text-align: right;\n", + " }\n", + "</style>\n", + "<table border=\"1\" class=\"dataframe\">\n", + " <thead>\n", + " <tr style=\"text-align: right;\">\n", + " <th></th>\n", + " <th>dx</th>\n", + " <th>dy</th>\n", + " <th>dz</th>\n", + " <th>dalpha_0</th>\n", + " <th>dalpha_1</th>\n", + " <th>dalpha_2</th>\n", + " </tr>\n", + " </thead>\n", + " <tbody>\n", + " <tr>\n", + " <th>0</th>\n", + " <td>NaN</td>\n", + " <td>NaN</td>\n", + " <td>NaN</td>\n", + " <td>NaN</td>\n", + " <td>NaN</td>\n", + " <td>NaN</td>\n", + " </tr>\n", + " <tr>\n", + " <th>1</th>\n", + " <td>1.555146</td>\n", + " <td>-0.029831</td>\n", + " <td>-0.906455</td>\n", + " <td>0.01112</td>\n", + " <td>0.0</td>\n", + " <td>0.0</td>\n", + " </tr>\n", + " <tr>\n", + " <th>2</th>\n", + " <td>-0.055015</td>\n", + " <td>0.427078</td>\n", + " <td>0.760878</td>\n", + " <td>0.00544</td>\n", + " <td>0.0</td>\n", + " <td>0.0</td>\n", + " </tr>\n", + " <tr>\n", + " <th>3</th>\n", + " <td>0.086441</td>\n", + " <td>0.763467</td>\n", + " <td>1.357019</td>\n", + " <td>0.01297</td>\n", + " <td>0.0</td>\n", + " <td>0.0</td>\n", + " </tr>\n", + " <tr>\n", + " <th>4</th>\n", + " <td>0.004211</td>\n", + " <td>-0.601823</td>\n", + " <td>-3.080847</td>\n", + " <td>0.00192</td>\n", + " <td>0.0</td>\n", + " <td>0.0</td>\n", + " </tr>\n", + " </tbody>\n", + "</table>\n", + "</div>" + ], + "text/plain": [ + " dx dy dz dalpha_0 dalpha_1 dalpha_2\n", + "0 NaN NaN NaN NaN NaN NaN\n", + "1 1.555146 -0.029831 -0.906455 0.01112 0.0 0.0\n", + "2 -0.055015 0.427078 0.760878 0.00544 0.0 0.0\n", + "3 0.086441 0.763467 1.357019 0.01297 0.0 0.0\n", + "4 0.004211 -0.601823 -3.080847 0.00192 0.0 0.0" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "vel = mytraj.velocity()\n", + "vel.head()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Iterate through the trajectory to get the optic flow of each scene" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": { + "collapsed": false + }, "outputs": [], - "source": [] + "source": [ + "tuples = [('location', 'x'), ('location', 'y'),\n", + " ('location', 'z'), ('location', 'dx'),\n", + " ('location', 'dy'), ('location', 'dz'),\n", + " ('rxyz', 'alpha_0'), ('rxyz', 'alpha_1'),\n", + " ('rxyz', 'alpha_2'), ('rxyz', 'dalpha_0'),\n", + " ('rxyz', 'dalpha_1'), ('rxyz', 'dalpha_2')]\n", + "index = pd.MultiIndex.from_tuples(tuples,\n", + " names=['position', 'orientation'])\n", + "\n", + "for i in range(1,10): \n", + " velocity = pd.Series(index=index)\n", + " velocity['location']['x'] = mytraj.x[i]\n", + " velocity['location']['y'] = mytraj.y[i]\n", + " velocity['location']['z'] = mytraj.z[i]\n", + " velocity['rxyz']['alpha_0'] = mytraj.alpha_0[i]\n", + " velocity['rxyz']['alpha_1'] = mytraj.alpha_1[i]\n", + " velocity['rxyz']['alpha_2'] = mytraj.alpha_2[i]\n", + " velocity['location']['dx'] = vel.dx[i]\n", + " velocity['location']['dy'] = vel.dy[i]\n", + " velocity['location']['dz'] = vel.dz[i]\n", + " velocity['rxyz']['dalpha_0'] = vel.dalpha_0[i]\n", + " velocity['rxyz']['dalpha_1'] = vel.dalpha_1[i]\n", + " velocity['rxyz']['dalpha_2'] = vel.dalpha_2[i]\n", + " \n", + " my_scene = mydb.scene(rowid=i)\n", + " rof, hof, vof = mcode.optic_flow(my_scene, viewing_directions,\n", + " velocity)" + ] }, { "cell_type": "code", diff --git a/navipy/maths/new_euler.py b/navipy/maths/new_euler.py index 4145953b3524dcd699a5474ae040c48631fd2367..b5a7fc998f229e64ce883cc52af4382223c0987d 100644 --- a/navipy/maths/new_euler.py +++ b/navipy/maths/new_euler.py @@ -1,25 +1,19 @@ import numpy as np -import navipy.maths.constants as constants import navipy.maths.quaternion as quat from navipy.scene import is_numeric_array c = np.cos s = np.sin - -def getAxes(axes='rxyz'): - if axes not in list(constants._AXES2TUPLE.keys()): - raise Exception("the chosen convention is not supported") - _AXES2TUPLE = { - 'sxyz': (0, 0, 1, 2), 'sxyx': (0, 0, 1, 0), 'sxzy': (0, 0, 2, 1), - 'sxzx': (0, 0, 2, 0), 'syzx': (0, 1, 2, 0), 'syzy': (0, 1, 2, 1), - 'syxz': (0, 1, 0, 2), 'syxy': (0, 1, 0, 1), 'szxy': (0, 2, 0, 1), - 'szxz': (0, 2, 0, 2), 'szyx': (0, 2, 1, 0), 'szyz': (0, 2, 1, 2), - 'rzyx': (1, 2, 1, 0), 'rxyx': (1, 0, 1, 0), 'ryzx': (1, 1, 2, 0), - 'rxzx': (1, 0, 2, 0), 'rxzy': (1, 0, 2, 1), 'ryzy': (1, 1, 2, 1), - 'rzxy': (1, 2, 0, 1), 'ryxy': (1, 1, 2, 1), 'ryxz': (1, 1, 0, 2), - 'rzxz': (1, 2, 0, 2), 'rxyz': (1, 0, 1, 2), 'rzyz': (1, 2, 1, 2)} - return _AXES2TUPLE[axes] +_AXES2TUPLE = { + 'sxyz': (0, 0, 1, 2), 'sxyx': (0, 0, 1, 0), 'sxzy': (0, 0, 2, 1), + 'sxzx': (0, 0, 2, 0), 'syzx': (0, 1, 2, 0), 'syzy': (0, 1, 2, 1), + 'syxz': (0, 1, 0, 2), 'syxy': (0, 1, 0, 1), 'szxy': (0, 2, 0, 1), + 'szxz': (0, 2, 0, 2), 'szyx': (0, 2, 1, 0), 'szyz': (0, 2, 1, 2), + 'rzyx': (1, 2, 1, 0), 'rxyx': (1, 0, 1, 0), 'ryzx': (1, 1, 2, 0), + 'rxzx': (1, 0, 2, 0), 'rxzy': (1, 0, 2, 1), 'ryzy': (1, 1, 2, 1), + 'rzxy': (1, 2, 0, 1), 'ryxy': (1, 1, 2, 1), 'ryxz': (1, 1, 0, 2), + 'rzxz': (1, 2, 0, 2), 'rxyz': (1, 0, 1, 2), 'rzyz': (1, 2, 1, 2)} def R1(a): @@ -50,19 +44,9 @@ def R3(a): def rotation_matrix(ai, aj, ak, axes='rxyz'): - if not isinstance(ai, float) and not isinstance(ai, int): - raise TypeError("euler angle must be of type float") - if not isinstance(aj, float) and not isinstance(aj, int): - raise TypeError("euler angle must be of type float") - if not isinstance(ak, float) and not isinstance(ak, int): - raise TypeError("euler angle must be of type float") - if np.isnan(np.array([ai], dtype=np.float64)) or\ - np.isnan(np.array([aj], dtype=np.float64)) or\ - np.isnan(np.array([ak], dtype=np.float64)): - raise ValueError("quaternions must not be nan or none") - if axes not in list(constants._AXES2TUPLE.keys()): + if axes not in list(_AXES2TUPLE.keys()): raise Exception("the chosen convention is not supported") - r, i, j, k = getAxes(axes) + r, i, j, k = _AXES2TUPLE[axes] matrixes = [R1, R2, R3] Rijk = np.dot(matrixes[i](ai), np.dot(matrixes[j](aj), @@ -73,7 +57,7 @@ def rotation_matrix(ai, aj, ak, axes='rxyz'): return np.transpose(Rijk) -def from_matrix(matrix, axes='sxyz'): +def from_matrix_new(matrix, axes='sxyz'): """Return Euler angles from rotation matrix for specified axis sequence. axes : One of 24 axis sequences as string or encoded tuple @@ -82,51 +66,72 @@ def from_matrix(matrix, axes='sxyz'): """ if not isinstance(matrix, np.ndarray) and not isinstance(matrix, list): raise TypeError("matrix must be np.array or list") - if axes not in list(constants._AXES2TUPLE.keys()): + if axes not in list(_AXES2TUPLE.keys()): raise Exception("the chosen convention is not supported") - if np.any(np.isnan(np.array(matrix, dtype=np.float64))): - raise ValueError('posorient must not contain nan') + # if np.any(np.isnan(np.array(matrix, dtype=np.float64))): + # raise ValueError('posorient must not contain nan') if not is_numeric_array(matrix): raise ValueError("matrix must contain numeric values") - try: - firstaxis, parity, \ - repetition, frame = constants._AXES2TUPLE[axes.lower( - )] - except (AttributeError, KeyError): - constants._TUPLE2AXES[axes] # validation - firstaxis, parity, repetition, frame = axes - - i = firstaxis - j = constants._NEXT_AXIS[i + parity] - k = constants._NEXT_AXIS[i - parity + 1] - - M = np.array(matrix, dtype=np.float64, copy=False)[:3, :3] - if repetition: - sy = np.sqrt(M[i, j] * M[i, j] + M[i, k] * M[i, k]) - if sy > constants._EPS: - ax = np.arctan2(M[i, j], M[i, k]) - ay = np.arctan2(sy, M[i, i]) - az = np.arctan2(M[j, i], -M[k, i]) - else: - ax = np.arctan2(-M[j, k], M[j, j]) - ay = np.arctan2(sy, M[i, i]) - az = 0.0 - else: - cy = np.sqrt(M[i, i] * M[i, i] + M[j, i] * M[j, i]) - if cy > constants._EPS: - ax = np.arctan2(M[k, j], M[k, k]) - ay = np.arctan2(-M[k, i], cy) - az = np.arctan2(M[j, i], M[i, i]) - else: - ax = np.arctan2(-M[j, k], M[j, j]) - ay = np.arctan2(-M[k, i], cy) - az = 0.0 - - if parity: - ax, ay, az = -ax, -ay, -az - if frame: - ax, az = az, ax - return ax, ay, az + u = None + rot, i, j, k = _AXES2TUPLE[axes] + if rot: + matrix = np.transpose(matrix) + new = list(axes) + new[0] = 's' + axes = ''.join(new) + if axes == 'sxyx': + u = [np.arctan2(matrix[1, 0], matrix[2, 0]), + np.arccos(matrix[0, 0]), + np.arctan2(matrix[0, 1], -matrix[0, 2])] + if axes == 'sxyz': + u = [np.arctan2(matrix[1, 2], matrix[2, 2]), + -np.arcsin(matrix[0, 2]), + np.arctan2(matrix[0, 1], matrix[0, 0])] + if axes == 'sxzx': + u = [np.arctan2(matrix[2, 1], -matrix[1, 0]), + np.arccos(matrix[0, 0]), + np.arctan2(matrix[0, 2], matrix[0, 1])] + if axes == 'sxzy': + u = [np.arctan2(-matrix[2, 1], matrix[1, 1]), + np.arcsin(matrix[0, 0]), + np.arctan2(-matrix[0, 2], matrix[0, 0])] + if axes == 'syxy': + u = [np.arctan2(matrix[0, 1], -matrix[2, 1]), + np.arccos(matrix[1, 1]), + np.arctan2(-matrix[1, 0], matrix[1, 2])] + if axes == 'syxz': + u = [np.arctan2(-matrix[0, 2], matrix[2, 2]), + np.arcsin(matrix[1, 2]), + np.arctan2(-matrix[1, 0], matrix[1, 1])] + if axes == 'syzx': + u = [np.arctan2(matrix[2, 0], matrix[0, 0]), + -np.arcsin(matrix[1, 0]), + np.arctan2(matrix[1, 2], matrix[1, 1])] + if axes == 'syzy': + u = [np.arctan2(matrix[2, 1], matrix[0, 1]), + np.arccos(matrix[1, 1]), + np.arctan2(matrix[1, 2], -matrix[1, 0])] + if axes == 'szxy': + u = [np.arctan2(matrix[0, 1], matrix[1, 1]), + -np.arcsin(matrix[2, 1]), + np.arctan2(matrix[2, 0], matrix[2, 2])] + if axes == 'szxz': + u = [np.arctan2(matrix[0, 2], matrix[1, 2]), + np.arccos(matrix[2, 2]), + np.arctan2(matrix[2, 0], -matrix[2, 1])] + if axes == 'szyx': + u = [np.arctan2(-matrix[1, 0], matrix[0, 0]), + np.arcsin(matrix[2, 0]), + np.arctan2(-matrix[2, 1], matrix[2, 2])] + if axes == 'szyz': + u = [np.arctan2(matrix[1, 2], -matrix[0, 2]), + np.arccos(matrix[2, 2]), + np.arctan2(matrix[2, 1], matrix[2, 0])] + + if u is None: + print("conv", axes, matrix) + + return u def from_quaternion(quaternion, axes='sxyz'): @@ -135,11 +140,11 @@ def from_quaternion(quaternion, axes='sxyz'): if not isinstance(quaternion, np.ndarray) and\ not isinstance(quaternion, list): raise TypeError("quaternions must be np.array or list") - if np.any(np.isnan(np.array(quaternion, dtype=np.float64))): - raise ValueError('posorient must not contain nan') - if axes not in list(constants._AXES2TUPLE.keys()): + # if np.any(np.isnan(np.array(quaternion, dtype=np.float64))): + # raise ValueError('posorient must not contain nan') + if axes not in list(_AXES2TUPLE.keys()): raise Exception("the chosen convention is not supported") - return from_matrix(quat.matrix(quaternion), axes) + return from_matrix_new(quat.matrix(quaternion), axes) def angular_rate_matrix(ai, aj, ak, axes='rxyz'): @@ -155,13 +160,13 @@ def angular_rate_matrix(ai, aj, ak, axes='rxyz'): raise TypeError("euler angle must be of type float") if not isinstance(ak, float) and not isinstance(ak, int): raise TypeError("euler angle must be of type float") - if np.isnan(np.array([ai], dtype=np.float64)) or\ - np.isnan(np.array([aj], dtype=np.float64)) or\ - np.isnan(np.array([ak], dtype=np.float64)): - raise ValueError("quaternions must not be nan or none") - if axes not in list(constants._AXES2TUPLE.keys()): + # if np.isnan(np.array([ai], dtype=np.float64)) or\ + # np.isnan(np.array([aj], dtype=np.float64)) or\ + # np.isnan(np.array([ak], dtype=np.float64)): + # raise ValueError("quaternions must not be nan or none") + if axes not in list(_AXES2TUPLE.keys()): raise Exception("the chosen convention is not supported") - s, i, j, k = getAxes(axes) + s, i, j, k = _AXES2TUPLE[axes] ei = np.zeros(3) ej = np.zeros(3) ek = np.zeros(3) @@ -202,15 +207,15 @@ def angular_velocity(ai, aj, ak, dai, daj, dak, axes='rxyz'): raise TypeError("euler angle time derivative must be of type float") if not isinstance(dak, float) and not isinstance(dak, int): raise TypeError("euler angle time derivative must be of type float") - if np.isnan(np.array([ai], dtype=np.float64)) or\ - np.isnan(np.array([aj], dtype=np.float64)) or\ - np.isnan(np.array([ak], dtype=np.float64)): - raise ValueError("quaternions must not be nan or none") - if np.isnan(np.array([dai], dtype=np.float64)) or\ - np.isnan(np.array([daj], dtype=np.float64)) or\ - np.isnan(np.array([dak], dtype=np.float64)): - raise ValueError("quaternions must not be nan or none") - if axes not in list(constants._AXES2TUPLE.keys()): + # if np.isnan(np.array([ai], dtype=np.float64)) or\ + # np.isnan(np.array([aj], dtype=np.float64)) or\ + # np.isnan(np.array([ak], dtype=np.float64)): + # raise ValueError("quaternions must not be nan or none") + # if np.isnan(np.array([dai], dtype=np.float64)) or\ + # np.isnan(np.array([daj], dtype=np.float64)) or\ + # np.isnan(np.array([dak], dtype=np.float64)): + # raise ValueError("quaternions must not be nan or none") + if axes not in list(_AXES2TUPLE.keys()): raise Exception("the chosen convention is not supported") rotM = angular_rate_matrix(ai, aj, ak, axes) vel = np.dot(rotM, [dai, daj, dak]) diff --git a/navipy/maths/test_euler.py b/navipy/maths/test_euler.py index ca57f0b543426cf33ee354e36d4ceec423546fb2..f690de1e95f4159da5e1eeb8619c29ed8d7e298e 100644 --- a/navipy/maths/test_euler.py +++ b/navipy/maths/test_euler.py @@ -1,6 +1,7 @@ import numpy as np -import navipy.maths.euler as euler -import navipy.maths.constants as constants +# import navipy.maths.euler as euler +import navipy.maths.new_euler as new_euler +# import navipy.maths.constants as constants import unittest # from navipy.maths.euler import matrix from navipy.maths.new_euler import angular_rate_matrix @@ -8,14 +9,127 @@ from navipy.maths.new_euler import angular_velocity from navipy.maths.new_euler import rotation_matrix from navipy.maths.new_euler import R1, R2, R3 # from navipy.maths.euler import angle_rate_matrix as old_angular_rate_matrix -from navipy.maths.euler import angular_velocity as old_angular_velocity -from navipy.maths.euler import matrix as old_rotation_matrix +# from navipy.maths.euler import angular_velocity as old_angular_velocity +# from navipy.maths.euler import matrix as old_rotation_matrix + +c = np.cos +s = np.sin +""" +[['sxyz','rzyx'(mirrored), 'Z3Y2X1'(transposed)], +['sxyx', 'rxyx', 'X3Y2X1'(transposed)], +['sxzy', 'ryzx'(mirrored), 'Y3Z2X1'(transposed)], +['sxzx', 'rxzx', 'X3Z2X1'(transposed)], +['syzx', 'rxzy'(mirrored), 'X3Z2Y1'(transposed)], +['syzy', 'ryzy', 'Y3Z2Y1'(transposed)], +['syxz', 'rzxy'(mirrored), 'Z3X2Y1'(transposed)], +['syxy', 'ryxy', 'Y3X2Y1'(transposed)], +['szxy', 'ryzx'(mirrored), 'Y3X2Z1'(transposed)], +['szxz', 'rzxz', 'Z3X2Z1'(transposed)], +['szyx', 'rxyz'(mirrored), 'X3Y2Z1'(transposed)], +['szyz', 'rzyz', 'Z3Y2Z1'(transposed)], +['rzyx', 'szyx', 'Z1Y2X3'], +['rxyx', 'sxyx', 'X1Y2X3'], +['ryzx', 'syzx', 'Y1Z2X3'], +['rxzx', 'sxzx', 'X1Z2X3'], +['rxzy', 'sxzy', 'X1Z2Y3'], +['ryzy', 'syzy', 'Y1Z2Y3'], +['rzxy', 'szxy', 'Z1X2Y3'], +['ryxy', 'syxy', 'Y1X2Y3'], +['ryxz', 'syxz', 'Y1X2Z3'], +['rzxz', 'szxz', 'Z1X2Z3'], +['rxyz', 'sxyz', 'X1Y2Z3'], +['rzyz', 'szyz', 'Z1Y2Z3']] +""" + +ConvTrans = [['sxyz', 'rzyx', 1, 0, 'Z3Y2X1', 0, 1], + ['sxyx', 'sxyx', 0, 1, 'X3Y2X1', 0, 1], + ['sxzy', 'sxzy', 0, 1, 'Y3Z2X1', 0, 1], + ['sxzx', 'sxzx', 0, 1, 'X3Z2X1', 0, 1], + ['syzx', 'syzx', 0, 1, 'X3Z2Y1', 0, 1], + ['syzy', 'syzy', 0, 1, 'Y3Z2Y1', 0, 1], + ['syxz', 'syxz', 0, 1, 'Z3X2Y1', 0, 1], + ['syxy', 'syxy', 0, 1, 'Y3X2Y1', 0, 1], + ['szxy', 'szxy', 0, 1, 'Y3X2Z1', 0, 1], + ['szxz', 'szxz', 0, 1, 'Z3X2Z1', 0, 1], + ['szyx', 'szyx', 0, 1, 'X3Y2Z1', 0, 1], + ['szyz', 'szyz', 0, 1, 'Z3Y2Z1', 0, 1], + ['rzyx', 'szyx', 0, 0, 'X1Y2Z3', 1, 1], + ['rxyx', 'sxyx', 0, 0, 'X3Y2X1', 0, 0], + ['ryzx', 'syzx', 0, 0, 'X3Z2Y1', 0, 0], + ['rxzx', 'sxzx', 0, 0, 'X3Z2X1', 0, 0], + ['rxzy', 'sxzy', 0, 0, 'Y3Z2X1', 0, 0], + ['ryzy', 'syzy', 0, 0, 'Y3Z2Y1', 0, 0], + ['rzxy', 'szxy', 0, 0, 'Y3X2Z1', 0, 0], + ['ryxy', 'ryxy', 1, 1, 'Y1X2Y3', 1, 1], + ['ryxz', 'syxz', 0, 0, 'Z3X2Y1', 0, 0], + ['rzxz', 'szxz', 0, 0, 'Z3X2Z1', 0, 0], + ['rxyz', 'sxyz', 0, 0, 'Z3Y2X1', 0, 0], + ['rzyz', 'szyz', 0, 0, 'Z3Y2Z1', 0, 0]] + + +_AXES2TUPLEwiki = {'Z3Y2X1': (2, 1, 0, 2, 1, 0), + 'X3Y2X1': (0, 1, 0, 2, 1, 0), + 'Y3Z2X1': (1, 2, 0, 2, 1, 0), + 'X3Z2X1': (0, 2, 0, 2, 1, 0), + 'X3Z2Y1': (0, 2, 1, 2, 1, 0), + 'Y3Z2Y1': (1, 2, 1, 2, 1, 0), + 'Z3X2Y1': (2, 0, 1, 2, 1, 0), + 'Y3X2Y1': (1, 0, 1, 2, 1, 0), + 'Y3X2Z1': (1, 0, 2, 2, 1, 0), + 'Z3X2Z1': (2, 0, 2, 2, 1, 0), + 'X3Y2Z1': (0, 1, 2, 2, 1, 0), + 'Z3Y2Z1': (2, 1, 2, 2, 1, 0), + 'Z1Y2X3': (2, 1, 0, 0, 1, 2), + 'X1Y2X3': (0, 1, 2, 0, 1, 2), + 'Y1Z2X3': (1, 2, 0, 0, 1, 2), + 'X1Z2X3': (0, 2, 0, 0, 1, 2), + 'X1Z2Y3': (0, 2, 1, 0, 1, 2), + 'Y1Z2Y3': (1, 2, 1, 0, 1, 2), + 'Z1X2Y3': (2, 0, 1, 0, 1, 2), + 'Y1X2Y3': (1, 0, 1, 0, 1, 2), + 'Y1X2Z3': (1, 0, 2, 0, 1, 2), + 'Z1X2Z3': (2, 0, 2, 0, 1, 2), + 'X1Y2Z3': (0, 1, 2, 0, 1, 2), + 'Z1Y2Z3': (2, 1, 2, 0, 1, 2)} + + +def rot_M_wiki_R1(a): + R1 = np.array([[1, 0, 0], + [0, c(a), -s(a)], + [0, s(a), c(a)]]) + return R1 + + +def rot_M_wiki_R2(a): + R1 = np.array([[c(a), 0, s(a)], + [0, 1, 0], + [-s(a), 0, c(a)]]) + return R1 + + +def rot_M_wiki_R3(a): + R1 = np.array([[c(a), -s(a), 0], + [s(a), c(a), 0], + [0, 0, 1]]) + return R1 + + +def rotation_matrix_wiki(ai, aj, ak, conv='Z3Y2X1'): + i, j, k, a_i, a_j, a_k = _AXES2TUPLEwiki[conv] + angles = [ai, aj, ak] + matrixes = [rot_M_wiki_R1, rot_M_wiki_R2, rot_M_wiki_R3] + Rijk = np.dot(matrixes[i](angles[a_i]), + np.dot(matrixes[j](angles[a_j]), + matrixes[k](angles[a_k]))) + return Rijk class TestEuler(unittest.TestCase): + """ def test_from_matrix(self): condition = dict() - for key, _ in constants._AXES2TUPLE.items(): + for key in list(constants._AXES2TUPLE.keys()): + print("key", key) rotation_0 = euler.matrix(1, 2, 3, key) ai, aj, ak = euler.from_matrix(rotation_0, key) rotation_1 = euler.matrix(ai, aj, ak, key) @@ -24,12 +138,107 @@ class TestEuler(unittest.TestCase): if condition[key] is False: print('axes', key, 'failed') self.assertTrue(np.all(np.array(condition.values()))) + """ - def test_betweenconvention(self): - """ - Test orientation from one convention to another - """ + def test_from_matrix_new(self): condition = dict() + for key in list(new_euler._AXES2TUPLE.keys()): + # print("key", key) + rotation_0 = new_euler.rotation_matrix(1, 2, 3, key) + # print("roation_0", rotation_0) + [ai, aj, ak] = new_euler.from_matrix_new(rotation_0, key) + # print("res", ai, aj, ak) + rotation_1 = new_euler.rotation_matrix(ai, aj, ak, key) + condition[key] = np.allclose(rotation_0, + rotation_1) + if condition[key] is False: + print('axes', key, 'failed from matrix') + self.assertTrue(np.all(np.array(condition.values()))) + """ + def est_wiki_conv(self): + a = 1 + b = 2 + c = 3 + Convs = [['sxyz', 'Z3Y2X1'], +['sxyx', 'X3Y2X1'], +['sxzy', 'Y3Z2X1'], +['sxzx', 'X3Z2X1'], +['syzx', 'X3Z2Y1'], +['syzy', 'Y3Z2Y1'], +['syxz', 'Z3X2Y1'], +['syxy', 'Y3X2Y1'], +['szxy', 'Y3X2Z1'], +['szxz', 'Z3X2Z1'], +['szyx', 'X3Y2Z1'], +['szyz', 'Z3Y2Z1'], +['rzyx', 'Z1Y2X3'], +['rxyx', 'X1Y2X3'], +['ryzx', 'Y1Z2X3'], +['rxzx', 'X1Z2X3'], +['rxzy', 'X1Z2Y3'], +['ryzy', 'Y1Z2Y3'], +['rzxy', 'Z1X2Y3'], +['ryxy', 'Y1X2Y3'], +['ryxz', 'Y1X2Z3'], +['rzxz', 'Z1X2Z3'], +['rxyz', 'X1Y2Z3'], +['rzyz', 'Z1Y2Z3']] + + + + + + r_ref = rotation_matrix(a,b,c,'sxyz') + r_wiki=euler.matrix(a,b,c,'rxyz')[:3,:3] + r_ref =np.flip(r_ref,axis=1) + r_ref =np.flip(r_ref,axis=0) + #print("r ref", r_ref) + #print("r wiki", r_wiki) + vec = [1,2,3] + for i, _ in constants._AXES2TUPLE.items(): + r_wiki=euler.matrix(a,b,c,i)[:3,:3] + #r_wiki_t=rotation_matrix_wiki(a,b,c,'X3Y2Z1') + #r_wiki=np.transpose(r_wiki) + #r_new = rotation_matrix(a,b,c,'sxyz') + #r_new_t = rotation_matrix(a,b,c,'rxyz') + #new_vec_wiki=np.dot(r_wiki,vec) + #new_vec= np.dot(r_new, vec) + #print("matrices", + # r_wiki_t, r_wiki, r_new,r_new_t) + #break + if np.allclose(r_wiki, r_ref): + #print("conv found", i, r_wiki) + + def est_convTrans(self): + ai = 0.1 + aj = 0.2 + ak = 0.3 + for i in ConvTrans: + Molivier=euler.matrix(ai, aj, ak, i[1])[:3,:3] + Mwiki = rotation_matrix_wiki(ai, aj, ak, i[4]) + Mmy=rotation_matrix(ai, aj, ak, i[0]) + if i[3]==1: + Molivier=np.transpose(Molivier) + if i[2]==1: + Molivier = np.flip(Molivier,axis=0) + Molivier = np.flip(Molivier,axis=1) + if i[6]==1: + Mwiki=np.transpose(Mwiki) + if i[5]==1: + Mwiki = np.flip(Mwiki,axis=0) + Mwiki = np.flip(Mwiki,axis=1) + print("conv test", Mwiki, Mmy,i) + print("conv test", Molivier, Mmy,i) + self.assertTrue(np.allclose(Mwiki, Mmy)) + self.assertTrue(np.allclose(Molivier, Mmy)) + """ + + # def test_betweenconvention(self): + # """ + # Test orientation from one convention to another + # """ + + """ condition = dict() refkey = 'rxyz' for key, _ in constants._AXES2TUPLE.items(): rotation_0 = euler.matrix(1, 2, 3, refkey) @@ -40,9 +249,29 @@ class TestEuler(unittest.TestCase): if condition[key] is False: print('axes', key, 'failed') self.assertTrue(np.all(np.array(condition.values()))) + """ + + def test_betweenconvention_new(self): + """ + Test orientation from one convention to another + """ + condition = dict() + refkey = 'rxyz' + for key in list(new_euler._AXES2TUPLE.keys()): + print("between key", key) + rotation_0 = new_euler.rotation_matrix(1, 2, 3, refkey) + print("betewwen matrix", rotation_0) + [ai, aj, ak] = new_euler.from_matrix_new(rotation_0, key) + print("between", ai, aj, ak) + rotation_1 = new_euler.rotation_matrix(ai, aj, ak, key) + condition[key] = np.allclose(rotation_0, + rotation_1) + if condition[key] is False: + print('axes', key, 'failed between') + self.assertTrue(np.all(np.array(condition.values()))) def test_from_quaternion(self): - angles = euler.from_quaternion([0.99810947, 0.06146124, 0, 0]) + angles = new_euler.from_quaternion([0.99810947, 0.06146124, 0, 0]) self.assertTrue(np.allclose(angles, [0.123, 0, 0])) def test_from_quaternion_params(self): @@ -50,42 +279,44 @@ class TestEuler(unittest.TestCase): (5.0, 4.0, None, 8.0), (1.0, 2.0, 3.0, 'w')]: with self.assertRaises(ValueError): - euler.from_quaternion([a, b, c, d]) + new_euler.from_quaternion([a, b, c, d]) with self.assertRaises(Exception): - euler.from_quaternion([9.0, 8.0, 7.0, 0.3], 'w') + new_euler.from_quaternion([9.0, 8.0, 7.0, 0.3], 'w') + """ def test_matrix_params(self): - for a, b, c, d in [(None, 2, 6, 8), (5.0, 4.0, None, 8.0)]: + for a, b, c, d in [(None, 2, 6, 'rxyz'), (5.0, 4.0, None, 'syxz')]: with self.assertRaises(TypeError): euler.matrix(a, b, c, d) - for a, b, c, d in [(9.0, np.nan, 2, 3)]: - with self.assertRaises(ValueError): - euler.matrix(a, b, c, d) + # for a, b, c, d in [(9.0, np.nan, 2, 'rxyz')]: + # with self.assertRaises(ValueError): + # euler.matrix(a, b, c, d) with self.assertRaises(Exception): euler.matrix(9.0, 8.0, 7.0, 'w') + """ def test_from_matrix_params(self): for a, b, c, d in [(None, 2, 6, 8), (9.0, np.nan, 2, 3), (5.0, 4.0, None, 8.0)]: with self.assertRaises(ValueError): - euler.from_matrix([a, b, c, d]) + new_euler.from_matrix([a, b, c, d]) with self.assertRaises(Exception): - euler.from_matrix(9.0, 8.0, 7.0, 'w') + new_euler.from_matrix(9.0, 8.0, 7.0, 'w') def test_angle_rate_matrix_params(self): - for a, b, c, d in [(None, 2, 6, 8), (5.0, 4.0, None, 8.0)]: + for a, b, c, d in [(None, 2, 6, 'rxyz'), (5.0, 4.0, None, 'sxyx')]: with self.assertRaises(TypeError): - euler.angle_rate_matrix(a, b, c, d) + new_euler.angle_rate_matrix(a, b, c, d) for a, b, c, d in [(9.0, np.nan, 2, 3)]: with self.assertRaises(ValueError): - euler.angle_rate_matrix(a, b, c, d) + new_euler.angle_rate_matrix(a, b, c, d) with self.assertRaises(Exception): - euler.angle_rate_matrix(9.0, 8.0, 7.0, 'w') + new_euler.angle_rate_matrix(9.0, 8.0, 7.0, 'w') def test_angular_velocity_params(self): for a, b, c, d, e, f, g in [(None, 2, 6, 8, 7, 8, 'b'), @@ -96,12 +327,23 @@ class TestEuler(unittest.TestCase): (1.0, 2.0, 3.0, 4.0, None, 4, 'w'), (5.0, 4.0, 3.0, 8.0, 8.0, 4.0, 'l')]: with self.assertRaises(TypeError): - euler.angle_rate_matrix(a, b, c, d, e, f, g) + new_euler.angle_rate_matrix(a, b, c, d, e, f, g) - def test_old_vs_newXZX(self): - """ example from wikipedia - """ - c = np.cos + def test_angular_velocity_params_new(self): + for a, b, c, d, e, f, g in [(None, 2, 6, 8, 7, 8, 'b'), + (9.0, 8, 2, 3, 3, 5, 7), + (5.0, 4.0, None, 8.0, 8.0, 4.0, 2.0), + (9.0, 8.0, 7.0, np.nan, 6.0, 9.0, 'w'), + (4.0, 2.0, 1.0, 3.0, 'w', 2.0, 9), + (1.0, 2.0, 3.0, 4.0, None, 4, 'w'), + (5.0, 4.0, 3.0, 8.0, 8.0, 4.0, 'l')]: + with self.assertRaises(TypeError): + new_euler.angle_rate_matrix(a, b, c, d, e, f, g) + + # def test_old_vs_newXZX(self): + # """ example from wikipedia + # """ + """ c = np.cos s = np.sin ai = 0.10 aj = 0.20 @@ -126,34 +368,60 @@ class TestEuler(unittest.TestCase): self.assertTrue(np.allclose(R2, R2_old)) self.assertTrue(np.allclose(R2, MwikiXZX)) self.assertTrue(np.allclose(R2_old, MwikiXZX)) - + """ + """ def test_old_vs_newXYZ(self): ai = 0.10 aj = 0.20 ak = 0.70 - R1 = rotation_matrix(ai, aj, ak, 'rxyz') - R2 = rotation_matrix(ai, aj, ak, 'sxyz') - R1_old = old_rotation_matrix(ai, aj, ak, 'rxyz') - R2_old = old_rotation_matrix(ai, aj, ak, 'sxyz') - self.assertTrue(np.allclose(R1, np.transpose(R2))) - self.assertTrue(np.allclose(R1_old, np.transpose(R2_old))) - self.assertTrue(np.allclose(R1, R1_old)) - self.assertTrue(np.allclose(R2, R2_old)) + for c in new_euler._AXES2TUPLE: + R1 = rotation_matrix(ai, aj, ak, 'rxyz') + R2 = rotation_matrix(ai, aj, ak, 'sxyz') + R1_old = old_rotation_matrix(ai, aj, ak, 'rxyz')[:3,:3] + R2_old = old_rotation_matrix(ai, aj, ak, 'sxyz')[:3,:3] + if not np.allclose(R1, R1_old): + print("wrong conf old vs new", c) + if not np.allclose(R2, R2_old): + print("wrong conf old vs new", c) + # self.assertTrue(np.allclose(R1, np.transpose(R2))) + # self.assertTrue(np.allclose(R1_old, np.transpose(R2_old))) + # self.assertTrue(np.allclose(R1, R1_old)) + # self.assertTrue(np.allclose(R2, R2_old)) # print("R1", R1) # print("R2", R2) # print("R1 old", R1_old) # print("R2 old", R2_old) + """ def test_rotMatrix(self): - """the stationary rotation matrix should be the transpose of the rotational + """the stationary rotation matrix should be + the transpose of the rotational with the same axis order""" ai = 0.10 aj = 0.20 ak = 0.70 R1 = rotation_matrix(ai, aj, ak, 'rxyz') R2 = rotation_matrix(ai, aj, ak, 'sxyz') + # print("test trans",R1, R2) self.assertTrue(np.allclose(R1, np.transpose(R2))) + # def test_rotvelOldvsNew(self): + # """the stationary rotation matrix should be the + # transpose of the rotational + # with the same axis order""" + """ + ai = 0.10 + aj = 0.20 + ak = 0.70 + dai = 0.01 + daj = 0.03 + dak = 0.5 + vel = angular_velocity(ai, aj, ak, dai, daj, dak, 'sxyz') + vel_old = old_angular_velocity(ai, aj, ak, dai, daj, dak, 'sxyz') + print("vel test", vel, vel_old) + self.assertTrue(np.allclose(vel, vel_old)) + """ + def test_convertE(self): """ the rotation matrix can be obtained by Rijk=E'ijk*(Eijk)^(-1) @@ -248,21 +516,13 @@ class TestEuler(unittest.TestCase): dai = 0 daj = 0 dak = 0.01 - vel_old = old_angular_velocity(ai, aj, ak, dai, daj, dak, 'szxz') - vel_old_r = old_angular_velocity(ai, aj, ak, dai, daj, dak, 'rzxz') - Rijk_old = old_rotation_matrix(ai, aj, ak, 'szxz')[:3, :3] - Rijk_r_old = old_rotation_matrix(ai, aj, ak, 'szxz')[:3, :3] Rijk = rotation_matrix(ai, aj, ak, 'szxz') Rijk_r = rotation_matrix(ai, aj, ak, 'rzxz') vel = angular_velocity(ai, aj, ak, dai, daj, dak, 'szxz') vel_r = angular_velocity(ai, aj, ak, dai, daj, dak, 'rzxz') vel_p = np.dot(Rijk, vel) vel_new = np.dot(Rijk_r, vel_p) - vel_p_old = np.dot(Rijk_old, vel_old) - vel_new_old = np.dot(Rijk_r_old, vel_p_old) self.assertTrue(np.allclose(vel_p, vel_r)) - self.assertTrue(np.allclose(vel_p_old, vel_old_r)) - self.assertTrue(np.allclose(vel_new_old, vel_old)) self.assertTrue(np.allclose(vel_new, vel)) """