|
| 1 | +# Spatial Maths for Python |
| 2 | + |
| 3 | +Spatial maths capability underpins all of robotics and robotic vision. The aim of the `spatialmath` package is to replicate the functionality of the MATLAB® Spatial Math Toolbox while achieving the conflicting high-level design aims of being: |
| 4 | + |
| 5 | +* as similar as possible to the MATLAB function names and semantics |
| 6 | +* as Pythonic as possible |
| 7 | + |
| 8 | +More detailed design aims include: |
| 9 | + |
| 10 | +* Python3 support only |
| 11 | +* Use Python keyword arguments to replace the RTB string options supported using `tb_optparse` |
| 12 | +* Use `numpy` arrays for all rotation and homogeneous transformation matrices, as well as vectors |
| 13 | +* Functions that accept a vector can accept a list, tuple, or `np.ndarray` |
| 14 | +* By default all `np.ndarray` vectors have the shape `(N,)` but functions also accept row `(1,N)` and column `(N,1)` vectors. This is a gnarly aspect of numpy. |
| 15 | +* Unlike RTB these functions do not support sequences, that functionality is supported by the pose classes `SO2`, `SE2`, `SO3`, `SE3`. |
| 16 | + |
| 17 | +Quick example: |
| 18 | + |
| 19 | +``` |
| 20 | +import spatialmath as sm |
| 21 | +
|
| 22 | +R = sm.SO3.Rx(30, 'deg') |
| 23 | +print(R) |
| 24 | + 1 0 0 |
| 25 | + 0 0.866025 -0.5 |
| 26 | + 0 0.5 0.866025 |
| 27 | +``` |
| 28 | +which constructs a rotation about the x-axis by 30 degrees. |
| 29 | + |
| 30 | + |
| 31 | +## Low-level spatial math |
| 32 | + |
| 33 | +First lets import the low-level transform functions |
| 34 | + |
| 35 | +``` |
| 36 | +>>> from spatialmath.base.transforms import * |
| 37 | +``` |
| 38 | + |
| 39 | +Let's start with a familiar and tangible example: |
| 40 | + |
| 41 | +``` |
| 42 | +>>> rotx(0.3) |
| 43 | +array([[ 1. , 0. , 0. ], |
| 44 | + [ 0. , 0.95533649, -0.29552021], |
| 45 | + [ 0. , 0.29552021, 0.95533649]]) |
| 46 | +
|
| 47 | +>>> rotx(30, unit='deg') |
| 48 | +Out[438]: |
| 49 | +array([[ 1. , 0. , 0. ], |
| 50 | + [ 0. , 0.8660254, -0.5 ], |
| 51 | + [ 0. , 0.5 , 0.8660254]]) |
| 52 | +``` |
| 53 | +Remember that these are `numpy` arrays so to perform matrix multiplication you need to use the `@` operator, for example |
| 54 | + |
| 55 | +``` |
| 56 | +rotx(0.3) @ roty(0.2) |
| 57 | +``` |
| 58 | + |
| 59 | +Note that the `*` operator performs element-wise multiplication, equivalent to the MATLAB `.*` operator. |
| 60 | + |
| 61 | +We also support multiple ways of passing vector information to functions that require it: |
| 62 | + |
| 63 | +* as separate positional arguments |
| 64 | + |
| 65 | +``` |
| 66 | +transl2(1, 2) |
| 67 | +Out[442]: |
| 68 | +array([[1., 0., 1.], |
| 69 | + [0., 1., 2.], |
| 70 | + [0., 0., 1.]]) |
| 71 | +``` |
| 72 | + |
| 73 | +* as a list or a tuple |
| 74 | + |
| 75 | +``` |
| 76 | +transl2( [1,2] ) |
| 77 | +Out[443]: |
| 78 | +array([[1., 0., 1.], |
| 79 | + [0., 1., 2.], |
| 80 | + [0., 0., 1.]]) |
| 81 | +
|
| 82 | +transl2( (1,2) ) |
| 83 | +Out[444]: |
| 84 | +array([[1., 0., 1.], |
| 85 | + [0., 1., 2.], |
| 86 | + [0., 0., 1.]]) |
| 87 | +``` |
| 88 | + |
| 89 | +* or as a `numpy` array |
| 90 | + |
| 91 | +``` |
| 92 | +transl2( np.array([1,2]) ) |
| 93 | +Out[445]: |
| 94 | +array([[1., 0., 1.], |
| 95 | + [0., 1., 2.], |
| 96 | + [0., 0., 1.]]) |
| 97 | +``` |
| 98 | + |
| 99 | +trplot example |
| 100 | +packages, animation |
| 101 | + |
| 102 | +There is a single module that deals with quaternions, unit or not, and the representation is a `numpy` array of four elements. As above, functions can accept the `numpy` array, a list, dict or `numpy` row or column vectors. |
| 103 | + |
| 104 | +``` |
| 105 | +>>> from spatialmath.base.quaternion import * |
| 106 | +>>> q = qqmul([1,2,3,4], [5,6,7,8]) |
| 107 | +>>> q |
| 108 | +array([-60, 12, 30, 24]) |
| 109 | +>>> qprint(q) |
| 110 | +-60.000000 < 12.000000, 30.000000, 24.000000 > |
| 111 | +>>> qnorm(q) |
| 112 | +72.24956747275377 |
| 113 | +``` |
| 114 | + |
| 115 | + |
| 116 | +## High-level classes |
| 117 | + |
| 118 | +``` |
| 119 | +>>> from spatialmath import * |
| 120 | +>>> SO2(.1) |
| 121 | +[[ 0.99500417 -0.09983342] |
| 122 | + [ 0.09983342 0.99500417]] |
| 123 | +``` |
| 124 | + |
| 125 | +These classes abstract the low-level numpy arrays into objects that obey the rules associated with the mathematical groups SO(2), SE(2), SO(3), SE(3) as well as twists and quaternions. pose classes `SO2`, `SE2`, `SO3`, `SE3`. |
| 126 | + |
| 127 | +Using classes ensures type safety, for example it stops us mixing a 2D homogeneous transformation with a 3D rotation matrix -- both are 3x3 matrices. |
| 128 | + |
| 129 | +These classes are all derived from two parent classes: |
| 130 | + |
| 131 | +* `RTBPose` which provides common functionality for all |
| 132 | +* `UserList` which provdides the ability to act like a list |
| 133 | + |
| 134 | +The latter is important because frequnetly in robotics we want a sequence, a trajectory, of rotation matrices or poses. However a list of these items has the type `list` and the elements are not enforced to be homogeneous, ie. a list could contain a mixture of classes. |
| 135 | + |
| 136 | +Another option would be to create a `numpy` array of these objects, the upside being it could be a multi-dimensional array. The downside is that again the array is not guaranteed to be homogeneous. |
| 137 | + |
| 138 | + |
| 139 | +The approach adopted here is to give these classes list superpowers. Using the example of SE(3) but applicable to all |
| 140 | + |
| 141 | +``` |
| 142 | +T = transl(1,2,3) # create a 4x4 np.array |
| 143 | +
|
| 144 | +a = SE3(T) |
| 145 | +a.append(a) # append a copy |
| 146 | +a.append(a) # append a copy |
| 147 | +type(a) |
| 148 | +len(a) |
| 149 | +a[1] # extract one element of the list |
| 150 | +for x in a: |
| 151 | + # do a thing |
| 152 | +``` |
| 153 | + |
| 154 | +## Symbolic support |
| 155 | + |
| 156 | +Some functions have support for symbolic variables, for example |
| 157 | + |
| 158 | +``` |
| 159 | +import sympy |
| 160 | +
|
| 161 | +theta = sym.symbols('theta') |
| 162 | +print(rotx(theta)) |
| 163 | +[[1 0 0] |
| 164 | + [0 cos(theta) -sin(theta)] |
| 165 | + [0 sin(theta) cos(theta)]] |
| 166 | +``` |
| 167 | + |
| 168 | +The resulting `numpy` array is an array of symbolic objects not numbers – the constants are also symbolic objects. You can read the elements of the matrix |
| 169 | + |
| 170 | +``` |
| 171 | +a = T[0,0] |
| 172 | +
|
| 173 | +a |
| 174 | +Out[258]: 1 |
| 175 | +
|
| 176 | +type(a) |
| 177 | +Out[259]: int |
| 178 | +
|
| 179 | +a = T[1,1] |
| 180 | +a |
| 181 | +Out[256]: |
| 182 | +cos(theta) |
| 183 | +type(a) |
| 184 | +Out[255]: cos |
| 185 | +``` |
| 186 | +We see that the symbolic constants are converted back to Python numeric types on read. |
| 187 | + |
| 188 | +Similarly when we assign an element or slice of the symbolic matrix to a numeric value, they are converted to symbolic constants on the way in. |
| 189 | + |
| 190 | + |
| 191 | + |
| 192 | +``` |
| 193 | +T[0,3] = 22 |
| 194 | +print(T) |
| 195 | +[[1 0 0 22] |
| 196 | + [0 cos(theta) -sin(theta) 0] |
| 197 | + [0 sin(theta) cos(theta) 0] |
| 198 | + [0 0 0 1]] |
| 199 | +``` |
| 200 | +but you can't write a symbolic value into a floating point matrix |
| 201 | + |
| 202 | +``` |
| 203 | +T=trotx(0.2) |
| 204 | +
|
| 205 | +T[0,3]=theta |
| 206 | +Traceback (most recent call last): |
| 207 | +
|
| 208 | + File "<ipython-input-248-b6823f58f38d>", line 1, in <module> |
| 209 | + T[0,3]=th |
| 210 | +
|
| 211 | + File "/opt/anaconda3/lib/python3.7/site-packages/sympy/core/expr.py", line 325, in __float__ |
| 212 | + raise TypeError("can't convert expression to float") |
| 213 | +
|
| 214 | +TypeError: can't convert expression to float |
| 215 | +``` |
| 216 | + |
| 217 | +| Function | Symbolic support | |
| 218 | +|----------|------------------| |
| 219 | +| rot2 | yes | |
| 220 | +| transl2 | yes | |
| 221 | +| rotx | yes | |
| 222 | +| roty | yes | |
| 223 | +| rotz | yes | |
| 224 | +| transl | yes | |
| 225 | +| r2t | yes | |
| 226 | +| t2r | yes | |
| 227 | +| rotx | yes | |
| 228 | +| rotx | yes | |
| 229 | + |
| 230 | + |
| 231 | + |
| 232 | + |
| 233 | + |
| 234 | + |
| 235 | + |
0 commit comments