├── .gitattributes
├── .github
├── CODEOWNERS
├── CONTRIBUTORS.md
├── workflows
│ ├── publish.yml
│ ├── sphinx.yml
│ └── master.yml
└── svg
│ ├── sm_powered.min.svg
│ └── sm_powered.svg
├── docs
├── figs
│ ├── fig1.png
│ ├── icon.png
│ ├── animate.gif
│ ├── animate.mp4
│ ├── classes.pdf
│ ├── classes.png
│ ├── pose-values.png
│ ├── broadcasting.png
│ ├── transforms2d.png
│ ├── transforms3d.png
│ ├── colored_output.png
│ ├── CartesianSnakes_LogoW.png
│ ├── classes_spatialvec.dot
│ └── classes.dot
├── source
│ ├── favicon.ico
│ ├── modules.rst
│ ├── 2d_line.rst
│ ├── _static
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── apple-touch-icon.png
│ │ ├── android-chrome-192x192.png
│ │ └── android-chrome-512x512.png
│ ├── 2d_linesegment.rst
│ ├── 2d_polygon.rst
│ ├── 2d_ellipse.rst
│ ├── func_vector.rst
│ ├── func_graphics.rst
│ ├── func_quat.rst
│ ├── func_2d.rst
│ ├── func_args.rst
│ ├── func_nd.rst
│ ├── func_animation.rst
│ ├── func_symbolic.rst
│ ├── func_3d.rst
│ ├── 6d_m6.rst
│ ├── func_numeric.rst
│ ├── 6d_f6.rst
│ ├── 3d_plane.rst
│ ├── classes-3d.rst
│ ├── func_2d_graphics.rst
│ ├── func_3d_graphics.rst
│ ├── 3d_line.rst
│ ├── classes-2d.rst
│ ├── 6d_spatial.rst
│ ├── 6d_force.rst
│ ├── functions.rst
│ ├── 2d_orient_SO2.rst
│ ├── 2d_pose_SE2.rst
│ ├── 6d_momentum.rst
│ ├── 3d_dualquaternion.rst
│ ├── 3d_quaternion.rst
│ ├── 3d_orient_SO3.rst
│ ├── 6d_inertia.rst
│ ├── 6d_velocity.rst
│ ├── 6d_acceleration.rst
│ ├── 3d_pose_SE3.rst
│ ├── 2d_pose_twist.rst
│ ├── 3d_pose_twist.rst
│ ├── 3d_pose_dualquaternion.rst
│ ├── indices.rst
│ ├── 3d_orient_unitquaternion.rst
│ ├── support.rst
│ ├── index.rst
│ ├── spatialmath.rst
│ └── conf.py
├── .buildinfo
├── Makefile
└── generated
│ ├── spatialmath.pose2d.html
│ ├── spatialmath.pose3d.html
│ ├── spatialmath.quaternion.html
│ ├── spatialmath.base.quaternions.html
│ ├── spatialmath.base.vectors.html
│ ├── spatialmath.base.transforms2d.html
│ ├── spatialmath.base.transforms3d.html
│ └── spatialmath.base.transformsNd.html
├── spatialmath
├── base
│ ├── types.py
│ ├── _types_35.py
│ ├── _types_311.py
│ ├── _types_39.py
│ ├── README.md
│ ├── __init__.py
│ └── symbolic.py
├── __init__.py
└── timing.py
├── Makefile
├── .pre-commit-config.yaml
├── .gitignore
├── LICENSE
├── tests
├── base
│ ├── test_transforms3d_plot.py
│ ├── test_symbolic.py
│ ├── test_numeric.py
│ ├── test_graphics.py
│ ├── test_quaternions.py
│ └── test_transforms2d.py
├── test_dualquaternion.py
├── test_spline.py
├── test_baseposelist.py
├── test_spatialvector.py
├── test_geom2d.py
└── test_geom3d.py
├── pyproject.toml
└── symbolic
└── angvelxform_dot.ipynb
/.gitattributes:
--------------------------------------------------------------------------------
1 | * linguist-vendored
2 | *.py linguist-vendored=false
3 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # Global Owners
2 | * @petercorke @jhavl @myeatman-bdai
3 |
--------------------------------------------------------------------------------
/docs/figs/fig1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bdaiinstitute/spatialmath-python/HEAD/docs/figs/fig1.png
--------------------------------------------------------------------------------
/docs/figs/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bdaiinstitute/spatialmath-python/HEAD/docs/figs/icon.png
--------------------------------------------------------------------------------
/docs/figs/animate.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bdaiinstitute/spatialmath-python/HEAD/docs/figs/animate.gif
--------------------------------------------------------------------------------
/docs/figs/animate.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bdaiinstitute/spatialmath-python/HEAD/docs/figs/animate.mp4
--------------------------------------------------------------------------------
/docs/figs/classes.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bdaiinstitute/spatialmath-python/HEAD/docs/figs/classes.pdf
--------------------------------------------------------------------------------
/docs/figs/classes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bdaiinstitute/spatialmath-python/HEAD/docs/figs/classes.png
--------------------------------------------------------------------------------
/docs/figs/pose-values.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bdaiinstitute/spatialmath-python/HEAD/docs/figs/pose-values.png
--------------------------------------------------------------------------------
/docs/source/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bdaiinstitute/spatialmath-python/HEAD/docs/source/favicon.ico
--------------------------------------------------------------------------------
/docs/figs/broadcasting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bdaiinstitute/spatialmath-python/HEAD/docs/figs/broadcasting.png
--------------------------------------------------------------------------------
/docs/figs/transforms2d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bdaiinstitute/spatialmath-python/HEAD/docs/figs/transforms2d.png
--------------------------------------------------------------------------------
/docs/figs/transforms3d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bdaiinstitute/spatialmath-python/HEAD/docs/figs/transforms3d.png
--------------------------------------------------------------------------------
/docs/figs/colored_output.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bdaiinstitute/spatialmath-python/HEAD/docs/figs/colored_output.png
--------------------------------------------------------------------------------
/docs/source/modules.rst:
--------------------------------------------------------------------------------
1 | spatialmath
2 | ===========
3 |
4 | .. toctree::
5 | :maxdepth: 4
6 |
7 | spatialmath
8 |
9 |
--------------------------------------------------------------------------------
/docs/source/2d_line.rst:
--------------------------------------------------------------------------------
1 | 2D line
2 | ^^^^^^^
3 |
4 | .. autoclass:: spatialmath.geom2d.Line2
5 | :members:
6 | :undoc-members:
--------------------------------------------------------------------------------
/docs/figs/CartesianSnakes_LogoW.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bdaiinstitute/spatialmath-python/HEAD/docs/figs/CartesianSnakes_LogoW.png
--------------------------------------------------------------------------------
/docs/source/_static/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bdaiinstitute/spatialmath-python/HEAD/docs/source/_static/favicon-16x16.png
--------------------------------------------------------------------------------
/docs/source/_static/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bdaiinstitute/spatialmath-python/HEAD/docs/source/_static/favicon-32x32.png
--------------------------------------------------------------------------------
/docs/source/_static/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bdaiinstitute/spatialmath-python/HEAD/docs/source/_static/apple-touch-icon.png
--------------------------------------------------------------------------------
/docs/source/2d_linesegment.rst:
--------------------------------------------------------------------------------
1 | 2D line segment
2 | ^^^^^^^^^^^^^^^
3 |
4 | .. autoclass:: spatialmath.geom2d.LineSegment2
5 | :members:
6 | :undoc-members:
--------------------------------------------------------------------------------
/docs/source/_static/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bdaiinstitute/spatialmath-python/HEAD/docs/source/_static/android-chrome-192x192.png
--------------------------------------------------------------------------------
/docs/source/_static/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bdaiinstitute/spatialmath-python/HEAD/docs/source/_static/android-chrome-512x512.png
--------------------------------------------------------------------------------
/docs/source/2d_polygon.rst:
--------------------------------------------------------------------------------
1 | 2D polgon
2 | ^^^^^^^^^
3 |
4 | .. autoclass:: spatialmath.geom2d.Polygon2
5 | :members:
6 | :undoc-members:
7 | :special-members: __init__, __str__, __len__
8 |
--------------------------------------------------------------------------------
/docs/source/2d_ellipse.rst:
--------------------------------------------------------------------------------
1 | 2D ellipse
2 | ^^^^^^^^^^
3 |
4 | .. autoclass:: spatialmath.geom2d.Ellipse
5 | :members:
6 | :undoc-members:
7 | :special-members: __init__, __str__, __len__
8 |
--------------------------------------------------------------------------------
/docs/source/func_vector.rst:
--------------------------------------------------------------------------------
1 | Vectors
2 | =======
3 |
4 | .. automodule:: spatialmath.base.vectors
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 | :inherited-members:
9 | :special-members:
--------------------------------------------------------------------------------
/docs/source/func_graphics.rst:
--------------------------------------------------------------------------------
1 | Graphics and animation
2 | ======================
3 |
4 |
5 | .. toctree::
6 | :maxdepth: 2
7 |
8 | func_2d_graphics
9 | func_3d_graphics
10 | func_animation
11 |
12 |
--------------------------------------------------------------------------------
/docs/source/func_quat.rst:
--------------------------------------------------------------------------------
1 | Quaternions
2 | ===========
3 |
4 | .. automodule:: spatialmath.base.quaternions
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 | :inherited-members:
9 | :special-members:
--------------------------------------------------------------------------------
/docs/source/func_2d.rst:
--------------------------------------------------------------------------------
1 | Transforms in 2D
2 | ================
3 |
4 | .. automodule:: spatialmath.base.transforms2d
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 | :inherited-members:
9 | :special-members:
--------------------------------------------------------------------------------
/docs/source/func_args.rst:
--------------------------------------------------------------------------------
1 | Argument checking
2 | =================
3 |
4 | .. automodule:: spatialmath.base.argcheck
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 | :inherited-members:
9 | :special-members:
--------------------------------------------------------------------------------
/docs/source/func_nd.rst:
--------------------------------------------------------------------------------
1 | Transforms in ND
2 | ================
3 |
4 | .. automodule:: spatialmath.base.transformsNd
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 | :inherited-members:
9 | :special-members:
--------------------------------------------------------------------------------
/docs/source/func_animation.rst:
--------------------------------------------------------------------------------
1 | Animation support
2 | =================
3 |
4 | .. automodule:: spatialmath.base.animate
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 | :inherited-members:
9 | :special-members:
--------------------------------------------------------------------------------
/docs/source/func_symbolic.rst:
--------------------------------------------------------------------------------
1 | Symbolic computation
2 | ====================
3 |
4 | .. automodule:: spatialmath.base.symbolic
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 | :inherited-members:
9 | :special-members:
--------------------------------------------------------------------------------
/docs/source/func_3d.rst:
--------------------------------------------------------------------------------
1 | Transforms in 3D
2 | ================
3 |
4 |
5 | .. automodule:: spatialmath.base.transforms3d
6 | :members:
7 | :undoc-members:
8 | :show-inheritance:
9 | :inherited-members:
10 | :special-members:
--------------------------------------------------------------------------------
/docs/source/6d_m6.rst:
--------------------------------------------------------------------------------
1 | Spatial M6
2 | ^^^^^^^^^^
3 |
4 | .. autoclass:: spatialmath.spatialvector.SpatialM6
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 | :exclude-members: count, copy, index, sort, remove, binop, unop, arghandler
--------------------------------------------------------------------------------
/docs/source/func_numeric.rst:
--------------------------------------------------------------------------------
1 | Numerical utility functions
2 | ===========================
3 |
4 | .. automodule:: spatialmath.base.numeric
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 | :inherited-members:
9 | :special-members:
--------------------------------------------------------------------------------
/docs/.buildinfo:
--------------------------------------------------------------------------------
1 | # Sphinx build info version 1
2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
3 | config: 8aa8f71f5a36f2a6c49e8bbeb8797bef
4 | tags: 645f666f9bcd5a90fca523b33c5a78b7
5 |
--------------------------------------------------------------------------------
/docs/source/6d_f6.rst:
--------------------------------------------------------------------------------
1 | Spatial F6
2 | ^^^^^^^^^^
3 |
4 | .. autoclass:: spatialmath.spatialvector.SpatialF6
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | :exclude-members: count, copy, index, sort, remove, binop, unop, arghandler
--------------------------------------------------------------------------------
/docs/source/3d_plane.rst:
--------------------------------------------------------------------------------
1 | Plane
2 | ^^^^^
3 |
4 | .. autoclass:: spatialmath.geom3d.Plane3
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 | :inherited-members:
9 | :special-members: __mul__, __rmul__, __eq__, __ne__, __init__, __or__, __xor__
10 |
--------------------------------------------------------------------------------
/docs/source/classes-3d.rst:
--------------------------------------------------------------------------------
1 |
2 | Geometry
3 | --------
4 |
5 | .. automodule:: spatialmath.geom3d
6 | :members:
7 | :undoc-members:
8 | :show-inheritance:
9 | :inherited-members:
10 | :special-members: __mul__, __rmul__, __eq__, __ne__, __init__, __or__, __xor__
--------------------------------------------------------------------------------
/docs/source/func_2d_graphics.rst:
--------------------------------------------------------------------------------
1 | 2D graphics
2 | ===========
3 |
4 | 2d graphical primitives which build on Matplotlib.
5 |
6 | .. automodule:: spatialmath.base.graphics
7 | :members: plot_point, plot_text, plot_homline, plot_box, plot_arrow, plot_circle, plot_ellipse, plot_polygon, plotvol2
8 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTORS.md:
--------------------------------------------------------------------------------
1 | A number of people have contributed to this, and earlier, versions of this Toolbox
2 |
3 | * Jesse Haviland, 2020 (part of the ropy project)
4 | * Luis Fernando Lara Tobar, 2008
5 | * Josh Carrigg Hodson, Aditya Dua, Chee Ho Chan, 2017 (part of the robopy project)
6 | * Peter Corke
7 |
--------------------------------------------------------------------------------
/docs/source/func_3d_graphics.rst:
--------------------------------------------------------------------------------
1 | 3D graphics
2 | ===========
3 |
4 | 3d graphical primitives which build on Matplotlib ``plot_wireframe`` and ``plot_surface``.
5 |
6 | .. automodule:: spatialmath.base.graphics
7 | :members: plot_cuboid, plot_sphere, plot_ellipsoid, plot_cylinder, plot_cone, plotvol3
8 |
--------------------------------------------------------------------------------
/spatialmath/base/types.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | _version = sys.version_info.minor
4 |
5 |
6 | if _version >= 11:
7 | from spatialmath.base._types_311 import *
8 | elif _version >= 9:
9 | from spatialmath.base._types_39 import *
10 | else:
11 | from spatialmath.base._types_35 import *
12 |
--------------------------------------------------------------------------------
/docs/source/3d_line.rst:
--------------------------------------------------------------------------------
1 | 3D line
2 | ^^^^^^^
3 |
4 | A 3D line using Plücker coordinates.
5 |
6 | .. autoclass:: spatialmath.geom3d.Line3
7 | :members:
8 | :undoc-members:
9 | :show-inheritance:
10 | :inherited-members:
11 | :special-members: __mul__, __rmul__, __eq__, __ne__, __init__, __or__, __xor__
--------------------------------------------------------------------------------
/docs/source/classes-2d.rst:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Geometry
6 | --------
7 |
8 | .. automodule:: spatialmath.geom2d
9 | :members:
10 | :undoc-members:
11 | :show-inheritance:
12 | :inherited-members:
13 | :special-members: __mul__, __rmul__, __eq__, __ne__, __init__, __or__, __xor__
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/docs/source/6d_spatial.rst:
--------------------------------------------------------------------------------
1 | Spatial vector
2 | ^^^^^^^^^^^^^^
3 |
4 | .. autoclass:: spatialmath.spatialvector.SpatialVector
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 | :special-members: __add__, __sub__, __neg__, __init__
9 | :exclude-members: count, copy, index, sort, remove, binop, unop, arghandler
--------------------------------------------------------------------------------
/docs/source/6d_force.rst:
--------------------------------------------------------------------------------
1 | Spatial force
2 | ^^^^^^^^^^^^^
3 |
4 | .. autoclass:: spatialmath.spatialvector.SpatialForce
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 | :inherited-members:
9 | :special-members: __add__, __sub__, __init__
10 | :exclude-members: count, copy, index, sort, remove, binop, unop, arghandler
--------------------------------------------------------------------------------
/docs/source/functions.rst:
--------------------------------------------------------------------------------
1 | ******************
2 | Function reference
3 | ******************
4 |
5 |
6 | .. toctree::
7 | :maxdepth: 2
8 |
9 | func_2d
10 | func_3d
11 | func_nd
12 | func_quat
13 | func_symbolic
14 | func_vector
15 | func_graphics
16 | func_args
17 | func_numeric
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/docs/source/2d_orient_SO2.rst:
--------------------------------------------------------------------------------
1 | SO(2) matrix
2 | ^^^^^^^^^^^^
3 |
4 | .. autoclass:: spatialmath.pose2d.SO2
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 | :inherited-members:
9 | :special-members: __mul__, __truediv__, __add__, __sub__, __eq__, __ne__, __pow__, __init__
10 | :exclude-members: count, copy, index, sort, remove
--------------------------------------------------------------------------------
/docs/source/2d_pose_SE2.rst:
--------------------------------------------------------------------------------
1 | SE(2) matrix
2 | ^^^^^^^^^^^^
3 |
4 | .. autoclass:: spatialmath.pose2d.SE2
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 | :inherited-members:
9 | :special-members: __mul__, __truediv__, __add__, __sub__, __eq__, __ne__, __pow__, __init__
10 | :exclude-members: count, copy, index, sort, remove
--------------------------------------------------------------------------------
/docs/source/6d_momentum.rst:
--------------------------------------------------------------------------------
1 | Spatial momentum
2 | ^^^^^^^^^^^^^^^^
3 |
4 | .. autoclass:: spatialmath.spatialvector.SpatialMomentum
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 | :inherited-members:
9 | :special-members: __add__, __sub__, __init__
10 | :exclude-members: count, copy, index, sort, remove, binop, unop, arghandler
--------------------------------------------------------------------------------
/docs/source/3d_dualquaternion.rst:
--------------------------------------------------------------------------------
1 | Dual Quaternion
2 | ^^^^^^^^^^^^^^^
3 |
4 | .. autoclass:: spatialmath.DualQuaternion.DualQuaternion
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 | :inherited-members:
9 | :special-members: __mul__, __truediv__, __add__, __sub__, __init__
10 | :exclude-members: count, copy, index, sort, remove
--------------------------------------------------------------------------------
/docs/source/3d_quaternion.rst:
--------------------------------------------------------------------------------
1 | Quaternion
2 | ^^^^^^^^^^
3 |
4 | .. autoclass:: spatialmath.quaternion.Quaternion
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 | :inherited-members:
9 | :special-members: __mul__, __truediv__, __add__, __sub__, __eq__, __ne__, __pow__, __init__
10 | :exclude-members: count, copy, index, sort, remove
--------------------------------------------------------------------------------
/docs/source/3d_orient_SO3.rst:
--------------------------------------------------------------------------------
1 |
2 |
3 | SO(3) matrix
4 | ^^^^^^^^^^^^
5 |
6 | .. autoclass:: spatialmath.pose3d.SO3
7 | :members:
8 | :undoc-members:
9 | :show-inheritance:
10 | :inherited-members:
11 | :special-members: __mul__, __truediv__, __add__, __sub__, __eq__, __ne__, __pow__, __init__
12 | :exclude-members: count, copy, index, sort, remove
--------------------------------------------------------------------------------
/docs/source/6d_inertia.rst:
--------------------------------------------------------------------------------
1 | Spatial inertia
2 | ^^^^^^^^^^^^^^^
3 |
4 | .. autoclass:: spatialmath.spatialvector.SpatialInertia
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 | :inherited-members:
9 | :special-members: __mul__, __rmul__, __add__, __sub__, __init__
10 | :exclude-members: count, copy, index, sort, remove, binop, unop, arghandler
--------------------------------------------------------------------------------
/docs/source/6d_velocity.rst:
--------------------------------------------------------------------------------
1 | Spatial velocity
2 | ^^^^^^^^^^^^^^^^
3 |
4 | .. autoclass:: spatialmath.spatialvector.SpatialVelocity
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 | :inherited-members:
9 | :special-members: _A, __add__, __sub__, __xor__, __init__
10 | :exclude-members: count, copy, index, sort, remove, binop, unop, arghandler
--------------------------------------------------------------------------------
/docs/source/6d_acceleration.rst:
--------------------------------------------------------------------------------
1 | Spatial acceleration
2 | ^^^^^^^^^^^^^^^^^^^^
3 |
4 | .. autoclass:: spatialmath.spatialvector.SpatialAcceleration
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 | :inherited-members:
9 | :special-members: __add__, __sub__, __init__
10 | :exclude-members: count, copy, index, sort, remove, binop, unop, arghandler
--------------------------------------------------------------------------------
/docs/source/3d_pose_SE3.rst:
--------------------------------------------------------------------------------
1 | SE(3) matrix
2 | ^^^^^^^^^^^^
3 |
4 | .. autoclass:: spatialmath.pose3d.SE3
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 | :inherited-members:
9 | :special-members: __mul__, __truediv__, __add__, __sub__, __eq__, __ne__, __pow__, __init__
10 | :exclude-members: count, copy, index, sort, remove, arghandler, binop, unop
--------------------------------------------------------------------------------
/docs/source/2d_pose_twist.rst:
--------------------------------------------------------------------------------
1 | se(2) twist
2 | ^^^^^^^^^^^
3 |
4 | .. autoclass:: spatialmath.twist.Twist2
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 | :inherited-members:
9 | :special-members: __mul__, __truediv__, __add__, __sub__, __eq__, __ne__, __pow__, __init__
10 | :exclude-members: count, copy, index, sort, remove, arghandler, binop, unop, __add__,
--------------------------------------------------------------------------------
/docs/source/3d_pose_twist.rst:
--------------------------------------------------------------------------------
1 | se(3) twist
2 | ^^^^^^^^^^^
3 |
4 | .. autoclass:: spatialmath.twist.Twist3
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 | :inherited-members:
9 | :special-members: __mul__, __truediv__, __add__, __sub__, __eq__, __ne__, __pow__, __init__
10 | :exclude-members: count, copy, index, sort, remove, arghandler, binop, unop, __add__,
11 |
--------------------------------------------------------------------------------
/docs/source/3d_pose_dualquaternion.rst:
--------------------------------------------------------------------------------
1 | Unit dual quaternion
2 | ^^^^^^^^^^^^^^^^^^^^^
3 |
4 | .. autoclass:: spatialmath.DualQuaternion.UnitDualQuaternion
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 | :inherited-members:
9 | :special-members: __mul__, __truediv__, __add__, __sub__, __init__
10 | :exclude-members: count, copy, index, sort, remove, arghandler, binop, unop
--------------------------------------------------------------------------------
/docs/source/indices.rst:
--------------------------------------------------------------------------------
1 | Indices
2 | =======
3 |
4 | * :ref:`genindex`
5 | * :ref:`modindex`
6 | * :ref:`search`
7 |
8 |
9 | .. autosummary::
10 | :toctree: generated
11 |
12 | spatialmath.pose3d
13 | spatialmath.quaternion
14 | spatialmath.base.transforms2d
15 | spatialmath.base.transforms3d
16 | spatialmath.base.transformsNd
17 | spatialmath.base.vectors
18 | spatialmath.base.quaternions
--------------------------------------------------------------------------------
/docs/source/3d_orient_unitquaternion.rst:
--------------------------------------------------------------------------------
1 | Unit quaternion
2 | ^^^^^^^^^^^^^^^
3 |
4 | .. autoclass:: spatialmath.quaternion.UnitQuaternion
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 | :inherited-members:
9 | :special-members: __mul__, __truediv__, __add__, __sub__, __eq__, __ne__, __pow__, __init__
10 | :exclude-members: count, copy, index, sort, remove, arghandler, binop, unop,
11 |
12 |
--------------------------------------------------------------------------------
/docs/figs/classes_spatialvec.dot:
--------------------------------------------------------------------------------
1 | # dot -Tpdf classes_spatialvec.dot > classes_spatialvec.pdf ; open classes_spatialvec.pdf
2 | digraph G {
3 | graph [rankdir=BT];
4 | BasePoseList -> "collections.UserList"
5 | SpatialVector -> BasePoseList
6 | SpatialM6 -> SpatialVector
7 | SpatialF6 -> SpatialVector
8 | SpatialVelocity -> SpatialM6
9 | SpatialAcceleration -> SpatialM6
10 | SpatialMomentum -> SpatialF6
11 | SpatialForce -> SpatialF6
12 | SpatialInertia -> BasePoseList
13 | }
--------------------------------------------------------------------------------
/docs/source/support.rst:
--------------------------------------------------------------------------------
1 | =======
2 | Support
3 | =======
4 |
5 | The easiest way to get help with the project is to join the ``#crawler``
6 | channel on Freenode_. We hang out there and you can get real-time help with
7 | your projects. The other good way is to open an issue on Github_.
8 |
9 | The mailing list at https://groups.google.com/forum/#!forum/crawler is also available for support.
10 |
11 | .. _Freenode: irc://freenode.net
12 | .. _Github: http://github.com/example/crawler/issues
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line, and also
5 | # from the environment for the first two.
6 | SPHINXOPTS ?=
7 | SPHINXBUILD ?= sphinx-build
8 | SOURCEDIR = source
9 | BUILDDIR = build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21 |
22 | open:
23 | open build/html/index.html
24 |
--------------------------------------------------------------------------------
/docs/figs/classes.dot:
--------------------------------------------------------------------------------
1 | # dot -Tpdf classes.dot > classes.pdf ; open classes.pdf
2 | # dot -Tpng -Gdpi=150 -Nfontsize=20 -Nfontname=Roboto classes.dot > classes.png
3 | digraph G {
4 | graph [rankdir=BT];
5 | BasePoseList -> "collections.UserList"
6 | BasePoseMatrix -> BasePoseList
7 | SO2 -> BasePoseMatrix
8 | SO3 -> BasePoseMatrix
9 | SE2 -> SO2
10 | SE3 -> SO3
11 | BaseTwist -> BasePoseList
12 | Twist2 -> BaseTwist
13 | Twist3 -> BaseTwist
14 | Quaternion -> BasePoseList
15 | UnitQuaternion -> Quaternion
16 | Line3 -> BasePoseList
17 | SpatialVector -> BasePoseList
18 | SpatialM6 -> SpatialVector
19 | SpatialF6 -> SpatialVector
20 | SpatialVelocity -> SpatialM6
21 | SpatialAcceleration -> SpatialM6
22 | SpatialMomentum -> SpatialF6
23 | SpatialForce -> SpatialF6
24 | SpatialInertia -> BasePoseList
25 | }
26 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .FORCE:
2 |
3 | BLUE=\033[0;34m
4 | BLACK=\033[0;30m
5 |
6 | help:
7 | @echo "$(BLUE) make test - run all unit tests"
8 | @echo " make coverage - run unit tests and coverage report"
9 | @echo " make docs - build Sphinx documentation"
10 | @echo " make dist - build dist files"
11 | @echo " make upload - upload to PyPI"
12 | @echo " make clean - remove dist and docs build files"
13 | @echo " make help - this message$(BLACK)"
14 |
15 | test:
16 | pytest
17 |
18 | coverage:
19 | coverage run --source='spatialmath' -m pytest
20 | coverage report
21 | coverage html
22 | open htmlcov/index.html
23 |
24 | docs: .FORCE
25 | (cd docs; make html)
26 |
27 | view:
28 | open docs/build/html/index.html
29 |
30 | dist: .FORCE
31 | #$(MAKE) test
32 | python -m build
33 | ls -lh dist
34 |
35 | upload: .FORCE
36 | twine upload dist/*
37 |
38 | clean: .FORCE
39 | (cd docs; make clean)
40 | -rm -r *.egg-info
41 | -rm -r dist build
42 |
--------------------------------------------------------------------------------
/docs/source/index.rst:
--------------------------------------------------------------------------------
1 | .. Spatial Maths package documentation master file, created by
2 | sphinx-quickstart on Sun Apr 12 15:50:23 2020.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Spatial Maths for Python
7 | ========================
8 |
9 |
10 | This package provides Python classes and functions to represent, print, plot,
11 | manipulate and covert between many common representations of position,
12 | orientation and pose of objects in 2D or 3D space. This includes
13 | mathematical objects such as rotation matrices :math:`\mat{R} \in \SO{2},
14 | \SO{3}`, angle sequences, exponential coordinates, homogeneous transformation matrices :math:`\mat{T} \in \SE{2}, \SE{3}`,
15 | unit quaternions :math:`\q \in \mathrm{S}^3`, and twists :math:`S \in \se{2},
16 | \se{3}`.
17 |
18 | .. toctree::
19 | :maxdepth: 2
20 |
21 | intro
22 | spatialmath
23 | functions
24 | indices
25 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | # - repo: https://github.com/charliermarsh/ruff-pre-commit
3 | # # Ruff version.
4 | # rev: 'v0.1.0'
5 | # hooks:
6 | # - id: ruff
7 | # args: ['--fix', '--config', 'pyproject.toml']
8 |
9 | - repo: https://github.com/psf/black
10 | rev: 'refs/tags/23.10.0:refs/tags/23.10.0'
11 | hooks:
12 | - id: black
13 | language_version: python3.10
14 | args: ['--config', 'pyproject.toml']
15 | verbose: true
16 |
17 | - repo: https://github.com/pre-commit/pre-commit-hooks
18 | rev: v4.5.0
19 | hooks:
20 | - id: end-of-file-fixer
21 | exclude: |
22 | (?x)(
23 | ^docs/
24 | )
25 | - id: debug-statements # Ensure we don't commit `import pdb; pdb.set_trace()`
26 | - id: trailing-whitespace
27 | exclude: |
28 | (?x)(
29 | ^docs/
30 | )
31 | # - repo: https://github.com/pre-commit/mirrors-mypy
32 | # rev: v1.6.1
33 | # hooks:
34 | # - id: mypy
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | ### Python ###
3 | # Byte-compiled / optimized / DLL files
4 | __pycache__/
5 | *.py[cod]
6 | *$py.class
7 | doc/build
8 |
9 | # C extensions
10 | *.so
11 |
12 | # Distribution / packaging
13 | build/
14 | develop-eggs/
15 | dist/
16 | downloads/
17 | eggs/
18 | .eggs/
19 | wheels/
20 | pip-wheel-metadata/
21 | *.egg-info/
22 | *.egg
23 | MANIFEST
24 |
25 | # Installer logs
26 | pip-log.txt
27 | pip-delete-this-directory.txt
28 |
29 | # Unit test / coverage reports
30 | htmlcov/
31 | .tox/
32 | .nox/
33 | .coverage
34 | .coverage.*
35 | .cache
36 | nosetests.xml
37 | coverage.xml
38 | *.cover
39 | .hypothesis/
40 | .pytest_cache/
41 |
42 | # Sphinx documentation
43 | docs/build/
44 | docs/source/generated
45 |
46 | # PyBuilder
47 | target/
48 |
49 | # pyenv
50 | .python-version
51 |
52 | # Pyre type checker
53 | .pyre/
54 |
55 | ### VisualStudioCode ###
56 | .vscode/
57 |
58 | ### VisualStudioCode Patch ###
59 | # Ignore all local history of files
60 | .history
61 |
62 | ### PyCharm ###
63 | .idea
64 |
65 | .ipynb_checkpoints/
66 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Peter Corke
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | # This workflows will upload a Python Package using Twine when a release is created
2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
3 |
4 | # name: Upload Python Package
5 |
6 | on:
7 | release:
8 | types: [created]
9 | workflow_dispatch:
10 |
11 | jobs:
12 | deploy:
13 |
14 | runs-on: ${{ matrix.os }}
15 | strategy:
16 | max-parallel: 2
17 | matrix:
18 | os: [ubuntu-22.04]
19 | python-version: [3.8]
20 |
21 | steps:
22 | - uses: actions/checkout@v2
23 | - name: Set up Python
24 | uses: actions/setup-python@v5
25 | with:
26 | python-version: ${{ matrix.python-version }}
27 | - name: Install dependencies
28 | run: |
29 | python -m pip install --upgrade pip
30 | pip install -U setuptools wheel twine build
31 | - name: Build and publish
32 | env:
33 | TWINE_USERNAME: __token__
34 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
35 | run: |
36 | python -m build
37 | ls ./dist/*.whl
38 | twine upload dist/*.gz
39 | twine upload dist/*.whl
40 |
--------------------------------------------------------------------------------
/.github/svg/sm_powered.min.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spatialmath/__init__.py:
--------------------------------------------------------------------------------
1 | # print("in spatialmath/__init__")
2 |
3 | from spatialmath.pose2d import SO2, SE2
4 | from spatialmath.pose3d import SO3, SE3
5 | from spatialmath.baseposematrix import BasePoseMatrix
6 | from spatialmath.geom2d import Line2, LineSegment2, Polygon2, Ellipse
7 | from spatialmath.geom3d import Line3, Plane3
8 | from spatialmath.twist import Twist3, Twist2
9 | from spatialmath.spatialvector import (
10 | SpatialVelocity,
11 | SpatialAcceleration,
12 | SpatialForce,
13 | SpatialMomentum,
14 | SpatialInertia,
15 | )
16 | from spatialmath.quaternion import Quaternion, UnitQuaternion
17 | from spatialmath.DualQuaternion import DualQuaternion, UnitDualQuaternion
18 | from spatialmath.spline import BSplineSE3, InterpSplineSE3, SplineFit
19 |
20 |
21 | __all__ = [
22 | # pose
23 | "SO2",
24 | "SE2",
25 | "SO3",
26 | "SE3",
27 | "BasePoseMatrix",
28 | "Quaternion",
29 | "UnitQuaternion",
30 | "DualQuaternion",
31 | "UnitDualQuaternion",
32 | "Twist3",
33 | "Twist2",
34 | "SpatialVelocity",
35 | "SpatialAcceleration",
36 | "SpatialForce",
37 | "SpatialMomentum",
38 | "SpatialInertia",
39 | "Line3",
40 | "Plane3",
41 | "Line2",
42 | "LineSegment2",
43 | "Polygon2",
44 | "Ellipse",
45 | "BSplineSE3",
46 | "InterpSplineSE3",
47 | "SplineFit",
48 | ]
49 |
50 | try:
51 | import importlib.metadata
52 |
53 | __version__ = importlib.metadata.version("spatialmath-python")
54 | except:
55 | pass
56 |
--------------------------------------------------------------------------------
/.github/workflows/sphinx.yml:
--------------------------------------------------------------------------------
1 | name: Sphinx
2 |
3 | on:
4 | workflow_call:
5 |
6 | jobs:
7 | sphinx:
8 | runs-on: ubuntu-22.04
9 | if: ${{ github.event_name != 'pull_request' }}
10 | steps:
11 | - uses: actions/checkout@v2
12 | - name: Set up Python 3.8
13 | uses: actions/setup-python@v5
14 | with:
15 | python-version: 3.8
16 | - name: Install dependencies
17 | run: |
18 | python -m pip install --upgrade pip
19 | pip install .[dev,docs]
20 | pip install git+https://github.com/petercorke/sphinx-autorun.git
21 | pip install sympy
22 | sudo apt-get install graphviz
23 | - name: Build docs
24 | run: |
25 | cd docs
26 | make html
27 | # Tell GitHub not to use jekyll to compile the docs
28 | touch build/html/.nojekyll
29 | cd ../
30 | - name: Commit documentation changes
31 | run: |
32 | git clone https://github.com/petercorke/spatialmath-python.git --branch gh-pages --single-branch gh-pages
33 | cp -r docs/build/html/* gh-pages/
34 | cd gh-pages
35 | git config --local user.email "action@github.com"
36 | git config --local user.name "GitHub Action"
37 | git add .
38 | git commit -m "Update documentation" -a || true
39 | # The above command will fail if no changes were present, so we ignore
40 | # that.
41 | - name: Push changes
42 | uses: ad-m/github-push-action@master
43 | with:
44 | branch: gh-pages
45 | directory: gh-pages
46 | github_token: ${{ secrets.GITHUB_TOKEN }}
47 |
--------------------------------------------------------------------------------
/docs/source/spatialmath.rst:
--------------------------------------------------------------------------------
1 | ***************
2 | Class reference
3 | ***************
4 |
5 |
6 | .. image:: ../figs/classes.png
7 | :width: 600
8 |
9 | (click to enlarged)
10 |
11 | The Spatial Math Toolbox classes form a hierarchy.
12 | They all inherit from the abstract class
13 | ``SMUserList`` which embues them with list-like functionality.
14 |
15 |
16 | 3D-space
17 | ========
18 |
19 | Pose in 3D
20 | ----------
21 |
22 | .. toctree::
23 | :maxdepth: 2
24 |
25 | 3d_pose_SE3
26 | 3d_pose_twist
27 | 3d_pose_dualquaternion
28 |
29 |
30 | Orientation in 3D
31 | -----------------
32 |
33 | .. toctree::
34 | :maxdepth: 2
35 |
36 | 3d_orient_SO3
37 | 3d_orient_unitquaternion
38 |
39 |
40 | 6D spatial vectors
41 | ------------------
42 |
43 | .. toctree::
44 | :maxdepth: 2
45 |
46 | 6d_spatial.rst
47 | 6d_m6.rst
48 | 6d_velocity.rst
49 | 6d_acceleration.rst
50 | 6d_f6.rst
51 | 6d_force.rst
52 | 6d_momentum.rst
53 | 6d_inertia.rst
54 |
55 | Geometry in 3D
56 | --------------
57 |
58 | .. toctree::
59 | :maxdepth: 2
60 |
61 | 3d_line
62 | 3d_plane
63 |
64 | Supporting
65 | ----------
66 |
67 | .. toctree::
68 | :maxdepth: 2
69 |
70 | 3d_quaternion
71 | 3d_dualquaternion
72 |
73 | ---------------
74 |
75 | 2D-space
76 | ========
77 |
78 | Pose in 2D
79 | ----------
80 |
81 | .. toctree::
82 | :maxdepth: 2
83 |
84 | 2d_pose_SE2
85 | 2d_pose_twist
86 |
87 | Orientation in 2D
88 | -----------------
89 |
90 | .. toctree::
91 | :maxdepth: 2
92 |
93 | 2d_orient_SO2
94 |
95 | Geometry in 2D
96 | --------------
97 |
98 | .. toctree::
99 | :maxdepth: 2
100 |
101 | 2d_line
102 | 2d_linesegment
103 | 2d_polygon
104 | 2d_ellipse
--------------------------------------------------------------------------------
/.github/workflows/master.yml:
--------------------------------------------------------------------------------
1 |
2 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions
3 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
4 |
5 | name: build
6 |
7 | on:
8 | push:
9 | branches: [ master, future ]
10 | pull_request:
11 |
12 |
13 | jobs:
14 | # Run tests on different versions of python
15 | unittest:
16 | runs-on: ${{ matrix.os }}
17 | strategy:
18 | matrix:
19 | os: [windows-latest, ubuntu-22.04, macos-latest]
20 | python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
21 | exclude:
22 | - os: windows-latest
23 | python-version: "3.11"
24 | - os: windows-latest
25 | python-version: "3.12"
26 |
27 | steps:
28 | - uses: actions/checkout@v2
29 | - name: Set up Python ${{ matrix.python-version }}
30 | uses: actions/setup-python@v5
31 | with:
32 | python-version: ${{ matrix.python-version }}
33 | - name: Install dependencies
34 | run: |
35 | python -m pip install --upgrade pip
36 | pip install .[dev]
37 | - name: Test with pytest
38 | env:
39 | MPLBACKEND: TkAgg
40 | run: |
41 | pytest -s --ignore=W605 --timeout=50 --timeout_method=thread
42 |
43 | codecov:
44 | # If all tests pass:
45 | # Run coverage and upload to codecov
46 | needs: unittest
47 | runs-on: ubuntu-22.04
48 | steps:
49 | - uses: actions/checkout@v2
50 | - name: Set up Python 3.8
51 | uses: actions/setup-python@v5
52 | with:
53 | python-version: 3.8
54 | - name: Install dependencies
55 | run: |
56 | python -m pip install --upgrade pip
57 | pip install .[dev]
58 | - name: Run coverage
59 | run: |
60 | coverage run --omit='tests/*.py,tests/base/*.py' -m pytest
61 | coverage report
62 | coverage xml
63 | - name: upload coverage to Codecov
64 | uses: codecov/codecov-action@v3
65 | with:
66 | file: ./coverage.xml
67 | sphinx:
68 | # If the above worked:
69 | # Build docs and upload to GH Pages
70 | needs: unittest
71 | uses: ./.github/workflows/sphinx.yml
72 |
--------------------------------------------------------------------------------
/.github/svg/sm_powered.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/base/test_transforms3d_plot.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | """
4 | Created on Fri Apr 10 14:19:04 2020
5 |
6 | @author: corkep
7 |
8 | """
9 |
10 |
11 | import numpy as np
12 | import numpy.testing as nt
13 | import unittest
14 | from math import pi
15 | import math
16 | from scipy.linalg import logm, expm
17 | import pytest
18 | import sys
19 |
20 | from spatialmath.base.transforms3d import *
21 | from spatialmath.base.transformsNd import isR, t2r, r2t, rt2tr
22 |
23 | import matplotlib.pyplot as plt
24 |
25 |
26 | class Test3D(unittest.TestCase):
27 | @pytest.mark.skipif(
28 | sys.platform.startswith("darwin") and sys.version_info < (3, 11),
29 | reason="tkinter bug with mac",
30 | )
31 | def test_plot(self):
32 | plt.figure()
33 | # test options
34 | trplot(
35 | transl(1, 2, 3),
36 | block=False,
37 | frame="A",
38 | style="line",
39 | width=1,
40 | dims=[0, 10, 0, 10, 0, 10],
41 | )
42 | trplot(
43 | transl(1, 2, 3),
44 | block=False,
45 | frame="A",
46 | style="arrow",
47 | width=1,
48 | dims=[0, 10, 0, 10, 0, 10],
49 | )
50 | trplot(
51 | transl(1, 2, 3),
52 | block=False,
53 | frame="A",
54 | style="rgb",
55 | width=1,
56 | dims=[0, 10, 0, 10, 0, 10],
57 | )
58 | trplot(transl(3, 1, 2), block=False, color="red", width=3, frame="B")
59 | trplot(
60 | transl(4, 3, 1) @ trotx(math.pi / 3),
61 | block=False,
62 | color="green",
63 | frame="c",
64 | dims=[0, 4, 0, 4, 0, 4],
65 | )
66 |
67 | # test for iterable
68 | plt.clf()
69 | T = [transl(1, 2, 3), transl(2, 3, 4), transl(3, 4, 5)]
70 | trplot(T)
71 |
72 | plt.close("all")
73 |
74 | @pytest.mark.skipif(
75 | sys.platform.startswith("darwin") and sys.version_info < (3, 11),
76 | reason="tkinter bug with mac",
77 | )
78 | def test_animate(self):
79 | tranimate(transl(1, 2, 3), repeat=False, wait=True)
80 |
81 | tranimate(transl(1, 2, 3), repeat=False, wait=True)
82 | # run again, with axes already created
83 | tranimate(transl(1, 2, 3), repeat=False, wait=True, dims=[0, 10, 0, 10, 0, 10])
84 |
85 | plt.close("all")
86 | # test animate with line not arrow, text, test with SO(3)
87 |
88 |
89 | # ---------------------------------------------------------------------------------------#
90 | if __name__ == "__main__":
91 | unittest.main()
92 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "spatialmath-python"
3 | version = "1.1.15"
4 | authors = [
5 | { name="Peter Corke", email="rvc@petercorke.com" },
6 | ]
7 | description = "Provides spatial maths capability for Python."
8 | readme = "README.md"
9 | requires-python = ">=3.7"
10 | classifiers = [
11 | "Development Status :: 5 - Production/Stable",
12 | # Indicate who your project is intended for
13 | "Intended Audience :: Developers",
14 | # Specify the Python versions you support here.
15 | "Programming Language :: Python :: 3.7",
16 | "Programming Language :: Python :: 3.8",
17 | "Programming Language :: Python :: 3.9",
18 | "Programming Language :: Python :: 3.10",
19 | "Programming Language :: Python :: 3.11",
20 | "Programming Language :: Python :: 3.12",
21 |
22 | "License :: OSI Approved :: MIT License",
23 | "Operating System :: OS Independent",
24 | ]
25 | keywords = [
26 | "spatial-math", "spatial math",
27 | "SO2", "SE2", "SO3", "SE3",
28 | "SO(2)", "SE(2)", "SO(3)", "SE(3)",
29 | "twist", "product of exponential", "translation", "orientation",
30 | "angle-axis", "Lie group", "skew symmetric matrix",
31 | "pose", "translation", "rotation matrix",
32 | "rigid body transform", "homogeneous transformation",
33 | "Euler angles", "roll-pitch-yaw angles",
34 | "quaternion", "unit-quaternion",
35 | "robotics", "robot vision", "computer vision",
36 | ]
37 |
38 | dependencies = [
39 | "numpy>=1.22",
40 | "scipy",
41 | "matplotlib",
42 | "ansitable",
43 | "typing_extensions",
44 | "pre-commit",
45 | ]
46 |
47 | [project.urls]
48 | "Homepage" = "https://github.com/bdaiinstitute/spatialmath-python"
49 | "Bug Tracker" = "https://github.com/bdaiinstitute/spatialmath-python/issues"
50 | "Documentation" = "https://bdaiinstitute.github.io/spatialmath-python/"
51 | "Source" = "https://github.com/bdaiinstitute/spatialmath-python"
52 |
53 | [project.optional-dependencies]
54 |
55 | dev = [
56 | "sympy",
57 | "pytest",
58 | "pytest-timeout",
59 | "pytest-xvfb",
60 | "coverage",
61 | "flake8"
62 | ]
63 |
64 | docs = [
65 | "sphinx",
66 | "sphinx-rtd-theme",
67 | "sphinx-autorun",
68 | "sphinxcontrib-jsmath",
69 | "sphinx-favicon",
70 | "sphinx-autodoc-typehints",
71 | ]
72 |
73 | ros-humble = [
74 | "matplotlib==3.5.1", # Large user-base has apt-installed python3-matplotlib (ROS2) which is pinned to this version.
75 | "numpy<2", # Cannot use 2.0 due to matplotlib version pinning.
76 | ]
77 |
78 | [build-system]
79 |
80 | requires = ["setuptools", "oldest-supported-numpy"]
81 | build-backend = "setuptools.build_meta"
82 |
83 | [tool.setuptools]
84 |
85 | packages = [
86 | "spatialmath",
87 | "spatialmath.base",
88 | ]
89 |
90 | [tool.black]
91 | required-version = "23.10.0"
92 | line-length = 88
93 | target-version = ['py38']
94 | exclude = "camera_derivatives.py"
95 |
--------------------------------------------------------------------------------
/tests/base/test_symbolic.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import math
3 |
4 | try:
5 | import sympy as sp
6 |
7 | _symbolics = True
8 | except ImportError:
9 | _symbolics = False
10 |
11 | from spatialmath.base.symbolic import *
12 |
13 |
14 | class Test_symbolic(unittest.TestCase):
15 | @unittest.skipUnless(_symbolics, "sympy required")
16 | def test_symbol(self):
17 | theta = symbol("theta")
18 | self.assertTrue(isinstance(theta, sp.Expr))
19 | self.assertTrue(theta.is_real)
20 |
21 | theta = symbol("theta", real=False)
22 | self.assertTrue(isinstance(theta, sp.Expr))
23 | self.assertFalse(theta.is_real)
24 |
25 | theta, psi = symbol("theta, psi")
26 | self.assertTrue(isinstance(theta, sp.Expr))
27 | self.assertTrue(isinstance(psi, sp.Expr))
28 |
29 | theta, psi = symbol("theta psi")
30 | self.assertTrue(isinstance(theta, sp.Expr))
31 | self.assertTrue(isinstance(psi, sp.Expr))
32 |
33 | q = symbol("q:6")
34 | self.assertEqual(len(q), 6)
35 | for _ in q:
36 | self.assertTrue(isinstance(_, sp.Expr))
37 | self.assertTrue(_.is_real)
38 |
39 | @unittest.skipUnless(_symbolics, "sympy required")
40 | def test_issymbol(self):
41 | theta = symbol("theta")
42 | self.assertFalse(issymbol(3))
43 | self.assertFalse(issymbol("not a symbol"))
44 | self.assertFalse(issymbol([1, 2]))
45 | self.assertTrue(issymbol(theta))
46 |
47 | @unittest.skipUnless(_symbolics, "sympy required")
48 | def test_functions(self):
49 | theta = symbol("theta")
50 | self.assertTrue(isinstance(sin(theta), sp.Expr))
51 | self.assertTrue(isinstance(sin(1.0), float))
52 |
53 | self.assertTrue(isinstance(cos(theta), sp.Expr))
54 | self.assertTrue(isinstance(cos(1.0), float))
55 |
56 | self.assertTrue(isinstance(sqrt(theta), sp.Expr))
57 | self.assertTrue(isinstance(sqrt(1.0), float))
58 |
59 | x = (theta - 1) * (theta + 1) - theta**2
60 | self.assertTrue(math.isclose(simplify(x).evalf(), -1))
61 |
62 | @unittest.skipUnless(_symbolics, "sympy required")
63 | def test_constants(self):
64 | x = zero()
65 | self.assertTrue(isinstance(x, sp.Expr))
66 | self.assertTrue(math.isclose(x.evalf(), 0))
67 |
68 | x = one()
69 | self.assertTrue(isinstance(x, sp.Expr))
70 | self.assertTrue(math.isclose(x.evalf(), 1))
71 |
72 | x = negative_one()
73 | self.assertTrue(isinstance(x, sp.Expr))
74 | self.assertTrue(math.isclose(x.evalf(), -1))
75 |
76 | x = pi()
77 | self.assertTrue(isinstance(x, sp.Expr))
78 | self.assertTrue(math.isclose(x.evalf(), math.pi))
79 |
80 |
81 | # ---------------------------------------------------------------------------------------#
82 | if __name__ == "__main__": # pragma: no cover
83 | unittest.main()
84 |
--------------------------------------------------------------------------------
/tests/base/test_numeric.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | """
4 | Created on Fri Apr 10 14:19:04 2020
5 |
6 | @author: corkep
7 |
8 | """
9 |
10 | import numpy as np
11 | import unittest
12 |
13 |
14 | from spatialmath.base.numeric import *
15 |
16 |
17 | class TestNumeric(unittest.TestCase):
18 | def test_numjac(self):
19 | pass
20 |
21 | def test_array2str(self):
22 | x = [1.2345678]
23 | s = array2str(x)
24 |
25 | self.assertIsInstance(s, str)
26 | self.assertEqual(s, "[ 1.23 ]")
27 |
28 | s = array2str(x, fmt="{:.5f}")
29 | self.assertEqual(s, "[ 1.23457 ]")
30 |
31 | s = array2str([1, 2, 3])
32 | self.assertEqual(s, "[ 1, 2, 3 ]")
33 |
34 | s = array2str([1, 2, 3], valuesep=":")
35 | self.assertEqual(s, "[ 1:2:3 ]")
36 |
37 | s = array2str([1, 2, 3], brackets=("<< ", " >>"))
38 | self.assertEqual(s, "<< 1, 2, 3 >>")
39 |
40 | s = array2str([1, 2e-8, 3])
41 | self.assertEqual(s, "[ 1, 2e-08, 3 ]")
42 |
43 | s = array2str([1, -2e-14, 3])
44 | self.assertEqual(s, "[ 1, 0, 3 ]")
45 |
46 | x = np.array([[1, 2, 3], [4, 5, 6]])
47 | s = array2str(x)
48 | self.assertEqual(s, "[ 1, 2, 3 | 4, 5, 6 ]")
49 |
50 | def test_bresenham(self):
51 | x, y = bresenham((-10, -10), (20, 10))
52 | self.assertIsInstance(x, np.ndarray)
53 | self.assertEqual(x.ndim, 1)
54 | self.assertIsInstance(y, np.ndarray)
55 | self.assertEqual(y.ndim, 1)
56 | self.assertEqual(len(x), len(y))
57 |
58 | # test points are no more than sqrt(2) apart
59 | z = np.array([x, y])
60 | d = np.diff(z, axis=1)
61 | d = np.linalg.norm(d, axis=0)
62 | self.assertTrue(all(d <= np.sqrt(2)))
63 |
64 | x, y = bresenham((20, 10), (-10, -10))
65 |
66 | # test points are no more than sqrt(2) apart
67 | z = np.array([x, y])
68 | d = np.diff(z, axis=1)
69 | d = np.linalg.norm(d, axis=0)
70 | self.assertTrue(all(d <= np.sqrt(2)))
71 |
72 | x, y = bresenham((-10, -10), (10, 20))
73 |
74 | # test points are no more than sqrt(2) apart
75 | z = np.array([x, y])
76 | d = np.diff(z, axis=1)
77 | d = np.linalg.norm(d, axis=0)
78 | self.assertTrue(all(d <= np.sqrt(2)))
79 |
80 | x, y = bresenham((10, 20), (-10, -10))
81 |
82 | # test points are no more than sqrt(2) apart
83 | z = np.array([x, y])
84 | d = np.diff(z, axis=1)
85 | d = np.linalg.norm(d, axis=0)
86 | self.assertTrue(all(d <= np.sqrt(2)))
87 |
88 | def test_mpq(self):
89 | data = np.array([[-1, 1, 1, -1], [-1, -1, 1, 1]])
90 |
91 | self.assertEqual(mpq_point(data, 0, 0), 4)
92 | self.assertEqual(mpq_point(data, 1, 0), 0)
93 | self.assertEqual(mpq_point(data, 0, 1), 0)
94 |
95 | def test_gauss1d(self):
96 | x = np.arange(-10, 10, 0.02)
97 | y = gauss1d(2, 1, x)
98 |
99 | self.assertEqual(len(x), len(y))
100 |
101 | m = np.argmax(y)
102 | self.assertAlmostEqual(x[m], 2)
103 |
104 | def test_gauss2d(self):
105 | r = np.arange(-10, 10, 0.02)
106 | X, Y = np.meshgrid(r, r)
107 | Z = gauss2d([2, 3], np.eye(2), X, Y)
108 |
109 | m = np.unravel_index(np.argmax(Z, axis=None), Z.shape)
110 | self.assertAlmostEqual(r[m[0]], 3)
111 | self.assertAlmostEqual(r[m[1]], 2)
112 |
113 |
114 | # ---------------------------------------------------------------------------------------#
115 | if __name__ == "__main__":
116 | unittest.main()
117 |
--------------------------------------------------------------------------------
/tests/test_dualquaternion.py:
--------------------------------------------------------------------------------
1 | from math import pi
2 | import numpy as np
3 |
4 | import numpy.testing as nt
5 | import unittest
6 |
7 | from spatialmath import DualQuaternion, UnitDualQuaternion, Quaternion, SE3
8 |
9 |
10 | def qcompare(x, y):
11 | if isinstance(x, Quaternion):
12 | x = x.vec
13 | elif isinstance(x, SMPose):
14 | x = x.A
15 | if isinstance(y, Quaternion):
16 | y = y.vec
17 | elif isinstance(y, SMPose):
18 | y = y.A
19 | nt.assert_array_almost_equal(x, y)
20 |
21 |
22 | class TestDualQuaternion(unittest.TestCase):
23 | def test_init(self):
24 | dq = DualQuaternion(Quaternion([1.0, 2, 3, 4]), Quaternion([5.0, 6, 7, 8]))
25 | nt.assert_array_almost_equal(dq.vec, np.r_[1, 2, 3, 4, 5, 6, 7, 8])
26 |
27 | dq = DualQuaternion([1.0, 2, 3, 4, 5, 6, 7, 8])
28 | nt.assert_array_almost_equal(dq.vec, np.r_[1, 2, 3, 4, 5, 6, 7, 8])
29 | dq = DualQuaternion(np.r_[1, 2, 3, 4, 5, 6, 7, 8])
30 | nt.assert_array_almost_equal(dq.vec, np.r_[1, 2, 3, 4, 5, 6, 7, 8])
31 |
32 | def test_pure(self):
33 | dq = DualQuaternion.Pure([1.0, 2, 3])
34 | nt.assert_array_almost_equal(dq.vec, np.r_[1, 0, 0, 0, 0, 1, 2, 3])
35 |
36 | def test_strings(self):
37 | dq = DualQuaternion(Quaternion([1.0, 2, 3, 4]), Quaternion([5.0, 6, 7, 8]))
38 | self.assertIsInstance(str(dq), str)
39 | self.assertIsInstance(repr(dq), str)
40 |
41 | def test_conj(self):
42 | dq = DualQuaternion(Quaternion([1.0, 2, 3, 4]), Quaternion([5.0, 6, 7, 8]))
43 | nt.assert_array_almost_equal(dq.conj().vec, np.r_[1, -2, -3, -4, 5, -6, -7, -8])
44 |
45 | # def test_norm(self):
46 | # q1 = Quaternion([1.,2,3,4])
47 | # q2 = Quaternion([5.,6,7,8])
48 |
49 | # dq = DualQuaternion(q1, q2)
50 | # nt.assert_array_almost_equal(dq.norm(), (q1.norm(), q2.norm()))
51 |
52 | def test_plus(self):
53 | dq = DualQuaternion(Quaternion([1.0, 2, 3, 4]), Quaternion([5.0, 6, 7, 8]))
54 | s = dq + dq
55 | nt.assert_array_almost_equal(s.vec, 2 * np.r_[1, 2, 3, 4, 5, 6, 7, 8])
56 |
57 | def test_minus(self):
58 | dq = DualQuaternion(Quaternion([1.0, 2, 3, 4]), Quaternion([5.0, 6, 7, 8]))
59 | s = dq - dq
60 | nt.assert_array_almost_equal(s.vec, np.zeros((8,)))
61 |
62 | def test_matrix(self):
63 | dq1 = DualQuaternion(Quaternion([1.0, 2, 3, 4]), Quaternion([5.0, 6, 7, 8]))
64 |
65 | M = dq1.matrix()
66 | self.assertIsInstance(M, np.ndarray)
67 | self.assertEqual(M.shape, (8, 8))
68 |
69 | def test_multiply(self):
70 | dq1 = DualQuaternion(Quaternion([1.0, 2, 3, 4]), Quaternion([5.0, 6, 7, 8]))
71 | dq2 = DualQuaternion(Quaternion([4, 3, 2, 1]), Quaternion([5, 6, 7, 8]))
72 |
73 | M = dq1.matrix()
74 | v = dq2.vec
75 | nt.assert_array_almost_equal(M @ v, (dq1 * dq2).vec)
76 |
77 | def test_unit(self):
78 | pass
79 |
80 |
81 | class TestUnitDualQuaternion(unittest.TestCase):
82 | def test_init(self):
83 | T = SE3.Rx(pi / 4)
84 | dq = UnitDualQuaternion(T)
85 | nt.assert_array_almost_equal(dq.SE3().A, T.A)
86 |
87 | def test_norm(self):
88 | T = SE3.Rx(pi / 4)
89 | dq = UnitDualQuaternion(T)
90 | nt.assert_array_almost_equal(dq.norm(), (1, 0))
91 |
92 | def test_multiply(self):
93 | T1 = SE3.Rx(pi / 4)
94 | T2 = SE3.Rz(-pi / 3)
95 |
96 | T = T1 * T2
97 |
98 | d1 = UnitDualQuaternion(T1)
99 | d2 = UnitDualQuaternion(T2)
100 |
101 | d = d1 * d2
102 | nt.assert_array_almost_equal(d.SE3().A, T.A)
103 |
104 |
105 | # ---------------------------------------------------------------------------------------#
106 | if __name__ == "__main__": # pragma: no cover
107 | unittest.main()
108 |
--------------------------------------------------------------------------------
/tests/test_spline.py:
--------------------------------------------------------------------------------
1 | import numpy.testing as nt
2 | import numpy as np
3 | import matplotlib.pyplot as plt
4 | import unittest
5 |
6 | from spatialmath import BSplineSE3, SE3, InterpSplineSE3, SplineFit, SO3
7 |
8 |
9 | class TestBSplineSE3(unittest.TestCase):
10 | control_poses = [
11 | SE3.Trans([e, 2 * np.cos(e / 2 * np.pi), 2 * np.sin(e / 2 * np.pi)])
12 | * SE3.Ry(e / 8 * np.pi)
13 | for e in range(0, 8)
14 | ]
15 |
16 | @classmethod
17 | def tearDownClass(cls):
18 | plt.close("all")
19 |
20 | def test_constructor(self):
21 | BSplineSE3(self.control_poses)
22 |
23 | def test_evaluation(self):
24 | spline = BSplineSE3(self.control_poses)
25 | nt.assert_almost_equal(spline(0).A, self.control_poses[0].A)
26 | nt.assert_almost_equal(spline(1).A, self.control_poses[-1].A)
27 |
28 | def test_visualize(self):
29 | spline = BSplineSE3(self.control_poses)
30 | spline.visualize(
31 | sample_times=np.linspace(0, 1.0, 100), animate=True, repeat=False
32 | )
33 |
34 |
35 | class TestInterpSplineSE3:
36 | waypoints = [
37 | SE3.Trans([e, 2 * np.cos(e / 2 * np.pi), 2 * np.sin(e / 2 * np.pi)])
38 | * SE3.Ry(e / 8 * np.pi)
39 | for e in range(0, 8)
40 | ]
41 | time_horizon = 10
42 | times = np.linspace(0, time_horizon, len(waypoints))
43 |
44 | @classmethod
45 | def tearDownClass(cls):
46 | plt.close("all")
47 |
48 | def test_constructor(self):
49 | InterpSplineSE3(self.times, self.waypoints)
50 |
51 | def test_evaluation(self):
52 | spline = InterpSplineSE3(self.times, self.waypoints)
53 | for time, pose in zip(self.times, self.waypoints):
54 | nt.assert_almost_equal(spline(time).angdist(pose), 0.0)
55 | nt.assert_almost_equal(np.linalg.norm(spline(time).t - pose.t), 0.0)
56 |
57 | spline = InterpSplineSE3(self.times, self.waypoints, normalize_time=True)
58 | norm_time = spline.timepoints
59 | for time, pose in zip(norm_time, self.waypoints):
60 | nt.assert_almost_equal(spline(time).angdist(pose), 0.0)
61 | nt.assert_almost_equal(np.linalg.norm(spline(time).t - pose.t), 0.0)
62 |
63 | def test_small_delta_t(self):
64 | InterpSplineSE3(
65 | np.linspace(0, InterpSplineSE3._e, len(self.waypoints)), self.waypoints
66 | )
67 |
68 | def test_visualize(self):
69 | spline = InterpSplineSE3(self.times, self.waypoints)
70 | spline.visualize(
71 | sample_times=np.linspace(0, self.time_horizon, 100),
72 | animate=True,
73 | repeat=False,
74 | )
75 |
76 |
77 | class TestSplineFit:
78 | num_data_points = 300
79 | time_horizon = 5
80 | num_viz_points = 100
81 |
82 | # make a helix
83 | timestamps = np.linspace(0, 1, num_data_points)
84 | trajectory = [
85 | SE3.Rt(
86 | t=[
87 | t * 0.4,
88 | 0.4 * np.sin(t * 2 * np.pi * 0.5),
89 | 0.4 * np.cos(t * 2 * np.pi * 0.5),
90 | ],
91 | R=SO3.Rx(t * 2 * np.pi * 0.5),
92 | )
93 | for t in timestamps * time_horizon
94 | ]
95 |
96 | def test_spline_fit(self):
97 | fit = SplineFit(self.timestamps, self.trajectory)
98 | spline, kept_indices = fit.stochastic_downsample_interpolation()
99 |
100 | fraction_points_removed = 1.0 - len(kept_indices) / self.num_data_points
101 |
102 | assert fraction_points_removed > 0.2
103 | assert len(spline.control_poses) == len(kept_indices)
104 | assert len(spline.timepoints) == len(kept_indices)
105 |
106 | assert fit.max_angular_error() < np.deg2rad(5.0)
107 | assert fit.max_angular_error() < 0.1
108 | spline.visualize(
109 | sample_times=np.linspace(0, self.time_horizon, 100),
110 | animate=True,
111 | repeat=False,
112 | )
113 |
--------------------------------------------------------------------------------
/docs/generated/spatialmath.pose2d.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | spatialmath.pose2d — Spatial Maths package 0.7.0
8 | documentation
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
spatialmath.pose2d
38 |
Classes
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
105 |
106 |
107 |
118 |
119 |
120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/docs/generated/spatialmath.pose3d.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | spatialmath.pose3d — Spatial Maths package 0.7.0
8 | documentation
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
spatialmath.pose3d
38 |
Classes
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
105 |
106 |
107 |
118 |
119 |
120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/docs/generated/spatialmath.quaternion.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | spatialmath.quaternion — Spatial Maths package 0.7.0
8 | documentation
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
spatialmath.quaternion
38 |
Classes
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
105 |
106 |
107 |
118 |
119 |
120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/docs/generated/spatialmath.base.quaternions.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | spatialmath.base.quaternions — Spatial Maths package 0.7.0
8 | documentation
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
spatialmath.base.quaternions
38 |
Created on Fri Apr 10 14:12:56 2020
39 |
@author: Peter Corke
40 |
Functions
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
107 |
108 |
109 |
120 |
121 |
122 |
123 |
124 |
125 |
--------------------------------------------------------------------------------
/spatialmath/base/_types_35.py:
--------------------------------------------------------------------------------
1 | # for Python <= 3.8
2 |
3 | from typing import (
4 | overload,
5 | Union,
6 | List,
7 | Tuple,
8 | Type,
9 | TextIO,
10 | Any,
11 | Callable,
12 | Optional,
13 | Iterator,
14 | )
15 | from typing_extensions import Literal as L
16 | from typing_extensions import Self
17 |
18 | # array like
19 |
20 | # these are input to many functions in spatialmath.base, and can be a list, tuple or
21 | # ndarray. The elements are generally float, but some functions accept symbolic
22 | # arguments as well, which leads to a NumPy array with dtype=object
23 | #
24 | # The variants like ArrayLike2 indicate that a list, tuple or ndarray of length 2 is
25 | # expected. Static checking of tuple length is possible but not a lists. This might be
26 | # possible in future versions of Python, but for now it is a hint to the coder about
27 | # what is expected
28 |
29 | from numpy.typing import DTypeLike, NDArray # , ArrayLike
30 |
31 | from typing import cast
32 |
33 | # from typing import TypeVar
34 | # NDArray = TypeVar('NDArray')
35 | import numpy as np
36 |
37 |
38 | ArrayLike = Union[float, List[float], Tuple[float, ...], NDArray]
39 | ArrayLikePure = Union[List[float], Tuple[float, ...], NDArray]
40 | ArrayLike2 = Union[List, Tuple[float, float], NDArray]
41 | ArrayLike3 = Union[List, Tuple[float, float, float], NDArray]
42 | ArrayLike4 = Union[List, Tuple[float, float, float, float], NDArray]
43 | ArrayLike6 = Union[List, Tuple[float, float, float, float, float, float], NDArray]
44 |
45 | # real vectors
46 | R1 = NDArray[np.floating] # R^1
47 | R2 = NDArray[np.floating] # R^2
48 | R3 = NDArray[np.floating] # R^3
49 | R4 = NDArray[np.floating] # R^4
50 | R6 = NDArray[np.floating] # R^6
51 | R8 = NDArray[np.floating] # R^8
52 |
53 | # real matrices
54 | R1x1 = NDArray # R^{1x1} matrix
55 | R2x2 = NDArray # R^{3x3} matrix
56 | R3x3 = NDArray # R^{3x3} matrix
57 | R4x4 = NDArray # R^{4x4} matrix
58 | R6x6 = NDArray # R^{6x6} matrix
59 | R8x8 = NDArray # R^{8x8} matrix
60 |
61 | R1x3 = NDArray # R^{1x3} row vector
62 | R3x1 = NDArray # R^{3x1} column vector
63 | R1x2 = NDArray # R^{1x2} row vector
64 | R2x1 = NDArray # R^{2x1} column vector
65 |
66 | Points2 = NDArray # R^{2xN} matrix
67 | Points3 = NDArray # R^{2xN} matrix
68 |
69 | RNx3 = NDArray # R^{Nx3} matrix
70 |
71 |
72 | # Lie group elements
73 | SO2Array = NDArray # SO(2) rotation matrix
74 | SE2Array = NDArray # SE(2) rigid-body transform
75 | SO3Array = NDArray # SO(3) rotation matrix
76 | SE3Array = NDArray # SE(3) rigid-body transform
77 |
78 | # Lie algebra elements
79 | so2Array = NDArray # so(2) Lie algebra of SO(2), skew-symmetrix matrix
80 | se2Array = NDArray # se(2) Lie algebra of SE(2), augmented skew-symmetrix matrix
81 | so3Array = NDArray # so(3) Lie algebra of SO(3), skew-symmetrix matrix
82 | se3Array = NDArray # se(3) Lie algebra of SE(3), augmented skew-symmetrix matrix
83 |
84 | # quaternion arrays
85 | QuaternionArray = NDArray
86 | UnitQuaternionArray = NDArray
87 |
88 | Rn = Union[R2, R3]
89 |
90 | SOnArray = Union[SO2Array, SO3Array]
91 | SEnArray = Union[SE2Array, SE3Array]
92 |
93 | sonArray = Union[so2Array, so3Array]
94 | senArray = Union[se2Array, se3Array]
95 |
96 | # __all__ = [
97 | # overload,
98 | # Union,
99 | # List,
100 | # Tuple,
101 | # Type,
102 | # TextIO,
103 | # Any,
104 | # Callable,
105 | # Optional,
106 | # Iterator,
107 | # ArrayLike,
108 | # ArrayLike2,
109 | # ArrayLike3,
110 | # ArrayLike4,
111 | # ArrayLike6,
112 | # # real vectors
113 | # R2,
114 | # R3,
115 | # R4,
116 | # R6,
117 | # R8,
118 | # # real matrices
119 | # R2x2,
120 | # R3x3,
121 | # R4x4,
122 | # R6x6,
123 | # R8x8,
124 | # R1x3,
125 | # R3x1,
126 | # R1x2,
127 | # R2x1,
128 | # Points2,
129 | # Points3,
130 | # RNx3,
131 | # # Lie group elements
132 | # SO2Array,
133 | # SE2Array,
134 | # SO3Array,
135 | # SE3Array,
136 | # # Lie algebra elements
137 | # so2Array,
138 | # se2Array,
139 | # so3Array,
140 | # se3Array,
141 | # # quaternion arrays
142 | # QuaternionArray,
143 | # UnitQuaternionArray,
144 | # Rn,
145 | # SOnArray,
146 | # SEnArray,
147 | # sonArray,
148 | # senArray,
149 | # ]
150 | Color = Union[str, ArrayLike3]
151 |
--------------------------------------------------------------------------------
/docs/generated/spatialmath.base.vectors.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | spatialmath.base.vectors — Spatial Maths package 0.7.0
8 | documentation
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
spatialmath.base.vectors
38 |
This modules contains functions to create and transform rotation matrices
39 | and homogeneous tranformation matrices.
40 |
Vector arguments are what numpy refers to as array_like and can be a list,
41 | tuple, numpy array, numpy row vector or numpy column vector.
42 |
Functions
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
109 |
110 |
111 |
122 |
123 |
124 |
125 |
126 |
127 |
--------------------------------------------------------------------------------
/docs/generated/spatialmath.base.transforms2d.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | spatialmath.base.transforms2d — Spatial Maths package 0.7.0
8 | documentation
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
111 |
122 |
123 |
124 |
125 |
126 |
127 |
--------------------------------------------------------------------------------
/tests/test_baseposelist.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import numpy as np
3 | from spatialmath.baseposelist import BasePoseList
4 |
5 |
6 | # create a subclass to test with, its value is a scalar
7 | class X(BasePoseList):
8 | def __init__(self, value=0, check=False):
9 | super().__init__()
10 | self.data = [value]
11 |
12 | @staticmethod
13 | def _identity():
14 | return 0
15 |
16 | @property
17 | def shape(self):
18 | return (1, 1)
19 |
20 | @staticmethod
21 | def isvalid(x):
22 | return True
23 |
24 |
25 | class TestBasePoseList(unittest.TestCase):
26 | def test_constructor(self):
27 | x = X()
28 | self.assertIsInstance(x, X)
29 | self.assertEqual(len(x), 1)
30 |
31 | x = X.Empty()
32 | self.assertIsInstance(x, X)
33 | self.assertEqual(len(x), 0)
34 |
35 | x = X.Alloc(10)
36 | self.assertIsInstance(x, X)
37 | self.assertEqual(len(x), 10)
38 | for xx in x:
39 | self.assertEqual(xx.A, 0)
40 |
41 | def test_setget(self):
42 | x = X.Alloc(10)
43 | for i in range(0, 10):
44 | x[i] = X(2 * i)
45 |
46 | for i, v in enumerate(x):
47 | self.assertEqual(v.A, 2 * i)
48 |
49 | def test_append(self):
50 | x = X.Empty()
51 | for i in range(0, 10):
52 | x.append(X(i + 1))
53 | self.assertEqual(len(x), 10)
54 | self.assertEqual([xx.A for xx in x], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
55 |
56 | def test_extend(self):
57 | x = X.Alloc(5)
58 | for i in range(0, 5):
59 | x[i] = X(i + 1)
60 | y = X.Alloc(5)
61 | for i in range(0, 5):
62 | y[i] = X(i + 10)
63 | x.extend(y)
64 | self.assertEqual(len(x), 10)
65 | self.assertEqual([xx.A for xx in x], [1, 2, 3, 4, 5, 10, 11, 12, 13, 14])
66 |
67 | def test_insert(self):
68 | x = X.Alloc(10)
69 | for i in range(0, 10):
70 | x[i] = X(i + 1)
71 | x.insert(5, X(100))
72 | self.assertEqual(len(x), 11)
73 | self.assertEqual([xx.A for xx in x], [1, 2, 3, 4, 5, 100, 6, 7, 8, 9, 10])
74 |
75 | def test_pop(self):
76 | x = X.Alloc(10)
77 | for i in range(0, 10):
78 | x[i] = X(i + 1)
79 |
80 | y = x.pop()
81 | self.assertEqual(len(y), 1)
82 | self.assertEqual(y.A, 10)
83 | self.assertEqual(len(x), 9)
84 | self.assertEqual([xx.A for xx in x], [1, 2, 3, 4, 5, 6, 7, 8, 9])
85 |
86 | def test_clear(self):
87 | x = X.Alloc(10)
88 | x.clear()
89 | self.assertEqual(len(x), 0)
90 |
91 | def test_reverse(self):
92 | x = X.Alloc(5)
93 | for i in range(0, 5):
94 | x[i] = X(i + 1)
95 | x.reverse()
96 | self.assertEqual(len(x), 5)
97 | self.assertEqual([xx.A for xx in x], [5, 4, 3, 2, 1])
98 |
99 | def test_binop(self):
100 | x = X(2)
101 | y = X(3)
102 |
103 | # singelton x singleton
104 | self.assertEqual(x.binop(y, lambda x, y: x * y), [6])
105 | self.assertEqual(x.binop(y, lambda x, y: x * y, list1=False), 6)
106 |
107 | y = X.Alloc(5)
108 | for i in range(0, 5):
109 | y[i] = X(i + 1)
110 |
111 | # singelton x non-singleton
112 | self.assertEqual(x.binop(y, lambda x, y: x * y), [2, 4, 6, 8, 10])
113 | self.assertEqual(x.binop(y, lambda x, y: x * y, list1=False), [2, 4, 6, 8, 10])
114 |
115 | # non-singelton x singleton
116 | self.assertEqual(y.binop(x, lambda x, y: x * y), [2, 4, 6, 8, 10])
117 | self.assertEqual(y.binop(x, lambda x, y: x * y, list1=False), [2, 4, 6, 8, 10])
118 |
119 | # non-singelton x non-singleton
120 | self.assertEqual(y.binop(y, lambda x, y: x * y), [1, 4, 9, 16, 25])
121 | self.assertEqual(y.binop(y, lambda x, y: x * y, list1=False), [1, 4, 9, 16, 25])
122 |
123 | def test_unop(self):
124 | x = X(2)
125 |
126 | f = lambda x: 2 * x
127 |
128 | self.assertEqual(x.unop(f), [4])
129 | self.assertEqual(x.unop(f, matrix=True), np.r_[4])
130 |
131 | x = X.Alloc(5)
132 | for i in range(0, 5):
133 | x[i] = X(i + 1)
134 |
135 | self.assertEqual(x.unop(f), [2, 4, 6, 8, 10])
136 | y = x.unop(f, matrix=True)
137 | self.assertEqual(y.shape, (5, 1))
138 | self.assertTrue(np.all(y - np.c_[2, 4, 6, 8, 10].T == 0))
139 |
140 | def test_arghandler(self):
141 | pass
142 |
143 |
144 | # ---------------------------------------------------------------------------------------#
145 | if __name__ == "__main__":
146 | unittest.main()
147 |
--------------------------------------------------------------------------------
/docs/generated/spatialmath.base.transforms3d.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | spatialmath.base.transforms3d — Spatial Maths package 0.7.0
8 | documentation
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
119 |
130 |
131 |
132 |
133 |
134 |
135 |
--------------------------------------------------------------------------------
/docs/generated/spatialmath.base.transformsNd.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | spatialmath.base.transformsNd — Spatial Maths package 0.7.0
8 | documentation
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
119 |
130 |
131 |
132 |
133 |
134 |
135 |
--------------------------------------------------------------------------------
/spatialmath/base/_types_311.py:
--------------------------------------------------------------------------------
1 | # for Python >= 3.9
2 |
3 | from typing import (
4 | overload,
5 | cast,
6 | Union,
7 | List,
8 | Tuple,
9 | Type,
10 | TextIO,
11 | Any,
12 | Callable,
13 | Optional,
14 | Iterator,
15 | Self,
16 | )
17 | from typing import Literal as L
18 |
19 | from numpy import ndarray, dtype, floating
20 | from numpy.typing import NDArray, DTypeLike
21 |
22 | # array like
23 |
24 | # these are input to many functions in spatialmath.base, and can be a list, tuple or
25 | # ndarray. The elements are generally float, but some functions accept symbolic
26 | # arguments as well, which leads to a NumPy array with dtype=object. For now
27 | # symbolics will throw a lint error. Possibly create variants ArrayLikeSym that
28 | # admits symbols and can be used for those functions that accept symbols.
29 | #
30 | # The variants like ArrayLike2 indicate that a list, tuple or ndarray of
31 | # length 2 is expected. Static checking of tuple length is possible, but not for lists.
32 | # This might be possible in future versions of Python, but for now it is a hint to the
33 | # coder about what is expected
34 |
35 | # cannot be a scalar
36 | ArrayLikePure = Union[List[float], Tuple[float, ...], ndarray[Any, dtype[floating]]]
37 |
38 | ArrayLike = Union[float, List[float], Tuple[float, ...], ndarray[Any, dtype[floating]]]
39 | ArrayLike2 = Union[
40 | List[float],
41 | Tuple[float, float],
42 | ndarray[
43 | Tuple[L[2]],
44 | dtype[floating],
45 | ],
46 | ]
47 | ArrayLike3 = Union[
48 | List[float],
49 | Tuple[float, float, float],
50 | ndarray[
51 | Tuple[L[3]],
52 | dtype[floating],
53 | ],
54 | ]
55 | ArrayLike4 = Union[
56 | List[float],
57 | Tuple[float, float, float, float],
58 | ndarray[
59 | Tuple[L[4]],
60 | dtype[floating],
61 | ],
62 | ]
63 | ArrayLike6 = Union[
64 | List[float],
65 | Tuple[float, float, float, float, float, float],
66 | ndarray[
67 | Tuple[L[6]],
68 | dtype[floating],
69 | ],
70 | ]
71 |
72 | # real vectors
73 | R1 = ndarray[
74 | Tuple[L[1]],
75 | dtype[floating],
76 | ] # R^1
77 | R2 = ndarray[
78 | Tuple[L[2]],
79 | dtype[floating],
80 | ] # R^2
81 | R3 = ndarray[
82 | Tuple[L[3]],
83 | dtype[floating],
84 | ] # R^3
85 | R4 = ndarray[
86 | Tuple[L[4]],
87 | dtype[floating],
88 | ] # R^4
89 | R6 = ndarray[
90 | Tuple[L[6]],
91 | dtype[floating],
92 | ] # R^6
93 |
94 | R8 = ndarray[
95 | Tuple[L[8,]],
96 | dtype[floating],
97 | ] # R^8
98 |
99 | # real matrices
100 | R1x1 = ndarray[Tuple[L[1], L[1]], dtype[floating]] # R^{1x1} matrix
101 | R2x2 = ndarray[Tuple[L[2], L[2]], dtype[floating]] # R^{2x2} matrix
102 | R3x3 = ndarray[Tuple[L[3], L[3]], dtype[floating]] # R^{3x3} matrix
103 | R4x4 = ndarray[Tuple[L[4], L[4]], dtype[floating]] # R^{4x4} matrix
104 | R6x6 = ndarray[Tuple[L[6], L[6]], dtype[floating]] # R^{6x6} matrix
105 | R8x8 = ndarray[Tuple[L[8], L[8]], dtype[floating]] # R^{8x8} matrix
106 | R1x3 = ndarray[Tuple[L[1], L[3]], dtype[floating]] # R^{1x3} row vector
107 | R3x1 = ndarray[Tuple[L[3], L[1]], dtype[floating]] # R^{3x1} column vector
108 | R1x2 = ndarray[Tuple[L[1], L[2]], dtype[floating]] # R^{1x2} row vector
109 | R2x1 = ndarray[Tuple[L[2], L[1]], dtype[floating]] # R^{2x1} column vector
110 |
111 | # Points2 = ndarray[Tuple[L[2, Any]], dtype[floating]] # R^{2xN} matrix
112 | # Points3 = ndarray[Tuple[L[3, Any]], dtype[floating]] # R^{2xN} matrix
113 | Points2 = NDArray # R^{2xN} matrix
114 | Points3 = NDArray # R^{2xN} matrix
115 |
116 | # RNx3 = ndarray[(Any, 3), dtype[floating]] # R^{Nx3} matrix
117 | RNx3 = NDArray
118 |
119 | # Lie group elements
120 | SO2Array = ndarray[Tuple[L[2], L[2]], dtype[floating]] # SO(2) rotation matrix
121 | SE2Array = ndarray[Tuple[L[3], L[3]], dtype[floating]] # SE(2) rigid-body transform
122 | SO3Array = ndarray[Tuple[L[3], L[3]], dtype[floating]] # SO(3) rotation matrix
123 | SE3Array = ndarray[Tuple[L[4], L[4]], dtype[floating]] # SE(3) rigid-body transform
124 |
125 | # Lie algebra elements
126 | so2Array = ndarray[
127 | Tuple[L[2, 2]], dtype[floating]
128 | ] # so(2) Lie algebra of SO(2), skew-symmetrix matrix
129 | se2Array = ndarray[
130 | Tuple[L[3, 3]], dtype[floating]
131 | ] # se(2) Lie algebra of SE(2), augmented skew-symmetrix matrix
132 | so3Array = ndarray[
133 | Tuple[L[3, 3]], dtype[floating]
134 | ] # so(3) Lie algebra of SO(3), skew-symmetrix matrix
135 | se3Array = ndarray[
136 | Tuple[L[4, 4]], dtype[floating]
137 | ] # se(3) Lie algebra of SE(3), augmented skew-symmetrix matrix
138 |
139 | # quaternion arrays
140 | QuaternionArray = ndarray[
141 | Tuple[L[4,]],
142 | dtype[floating],
143 | ]
144 | UnitQuaternionArray = ndarray[
145 | Tuple[L[4,]],
146 | dtype[floating],
147 | ]
148 |
149 | Rn = Union[R2, R3]
150 |
151 | SOnArray = Union[SO2Array, SO3Array]
152 | SEnArray = Union[SE2Array, SE3Array]
153 |
154 | sonArray = Union[so2Array, so3Array]
155 | senArray = Union[se2Array, se3Array]
156 |
157 | Color = Union[str, ArrayLike3]
158 |
--------------------------------------------------------------------------------
/spatialmath/base/_types_39.py:
--------------------------------------------------------------------------------
1 | # for Python >= 3.9
2 |
3 | from typing import (
4 | overload,
5 | cast,
6 | Union,
7 | List,
8 | Tuple,
9 | Type,
10 | TextIO,
11 | Any,
12 | Callable,
13 | Optional,
14 | Iterator,
15 | )
16 | from typing import Literal as L
17 | from typing_extensions import Self
18 |
19 | import numpy as np
20 | from numpy import ndarray, dtype, floating
21 | from numpy.typing import NDArray, DTypeLike
22 |
23 | # array like
24 |
25 | # these are input to many functions in spatialmath.base, and can be a list, tuple or
26 | # ndarray. The elements are generally float, but some functions accept symbolic
27 | # arguments as well, which leads to a NumPy array with dtype=object. For now
28 | # symbolics will throw a lint error. Possibly create variants ArrayLikeSym that
29 | # admits symbols and can be used for those functions that accept symbols.
30 | #
31 | # The variants like ArrayLike2 indicate that a list, tuple or ndarray of
32 | # length 2 is expected. Static checking of tuple length is possible, but not for lists.
33 | # This might be possible in future versions of Python, but for now it is a hint to the
34 | # coder about what is expected
35 |
36 |
37 | ArrayLike = Union[float, List[float], Tuple[float, ...], ndarray[Any, dtype[floating]]]
38 | ArrayLikePure = Union[List[float], Tuple[float, ...], ndarray[Any, dtype[floating]]]
39 | ArrayLike2 = Union[
40 | List[float],
41 | Tuple[float, float],
42 | ndarray[
43 | Tuple[L[2,]],
44 | dtype[floating],
45 | ],
46 | ]
47 | ArrayLike3 = Union[
48 | List[float],
49 | Tuple[float, float, float],
50 | ndarray[
51 | Tuple[L[3,]],
52 | dtype[floating],
53 | ],
54 | ]
55 | ArrayLike4 = Union[
56 | List[float],
57 | Tuple[float, float, float, float],
58 | ndarray[
59 | Tuple[L[4,]],
60 | dtype[floating],
61 | ],
62 | ]
63 | ArrayLike6 = Union[
64 | List[float],
65 | Tuple[float, float, float, float, float, float],
66 | ndarray[
67 | Tuple[L[6,]],
68 | dtype[floating],
69 | ],
70 | ]
71 |
72 | # real vectors
73 | R1 = ndarray[
74 | Tuple[L[1]],
75 | dtype[floating],
76 | ] # R^1
77 | R2 = ndarray[
78 | Tuple[L[2]],
79 | dtype[floating],
80 | ] # R^2
81 | R3 = ndarray[
82 | Tuple[L[3]],
83 | dtype[floating],
84 | ] # R^3
85 | R4 = ndarray[
86 | Tuple[L[4]],
87 | dtype[floating],
88 | ] # R^4
89 | R6 = ndarray[
90 | Tuple[L[6]],
91 | dtype[floating],
92 | ] # R^6
93 | R8 = ndarray[
94 | Tuple[L[8]],
95 | dtype[floating],
96 | ] # R^8
97 |
98 | # real matrices
99 | R1x1 = ndarray[Tuple[L[1], L[1]], dtype[floating]] # R^{1x1} matrix
100 | R2x2 = ndarray[Tuple[L[2], L[2]], dtype[floating]] # R^{2x2} matrix
101 | R3x3 = ndarray[Tuple[L[3], L[3]], dtype[floating]] # R^{3x3} matrix
102 | R4x4 = ndarray[Tuple[L[4], L[4]], dtype[floating]] # R^{4x4} matrix
103 | R6x6 = ndarray[Tuple[L[6], L[6]], dtype[floating]] # R^{6x6} matrix
104 | R8x8 = ndarray[Tuple[L[8], L[8]], dtype[floating]] # R^{8x8} matrix
105 | R1x3 = ndarray[Tuple[L[1], L[3]], dtype[floating]] # R^{1x3} row vector
106 | R3x1 = ndarray[Tuple[L[3], L[1]], dtype[floating]] # R^{3x1} column vector
107 | R1x2 = ndarray[Tuple[L[1], L[2]], dtype[floating]] # R^{1x2} row vector
108 | R2x1 = ndarray[Tuple[L[2], L[1]], dtype[floating]] # R^{2x1} column vector
109 |
110 | # Points2 = ndarray[Tuple[L[2, Any]], dtype[floating]] # R^{2xN} matrix
111 | # Points3 = ndarray[Tuple[L[3, Any]], dtype[floating]] # R^{2xN} matrix
112 | Points2 = NDArray # R^{2xN} matrix
113 | Points3 = NDArray # R^{2xN} matrix
114 |
115 | # RNx3 = ndarray[(Any, 3), dtype[floating]] # R^{Nx3} matrix
116 | RNx3 = NDArray
117 |
118 | # Lie group elements
119 | SO2Array = ndarray[Tuple[L[2, 2]], dtype[floating]] # SO(2) rotation matrix
120 | SE2Array = ndarray[Tuple[L[3, 3]], dtype[floating]] # SE(2) rigid-body transform
121 | # SO3Array = ndarray[Tuple[L[3, 3]], dtype[floating]]
122 | SO3Array = np.ndarray[Tuple[L[3], L[3]], dtype[floating]] # SO(3) rotation matrix
123 | SE3Array = ndarray[Tuple[L[4], L[4]], dtype[floating]] # SE(3) rigid-body transform
124 |
125 |
126 | # Lie algebra elements
127 | so2Array = ndarray[
128 | Tuple[L[2, 2]], dtype[floating]
129 | ] # so(2) Lie algebra of SO(2), skew-symmetrix matrix
130 | se2Array = ndarray[
131 | Tuple[L[3, 3]], dtype[floating]
132 | ] # se(2) Lie algebra of SE(2), augmented skew-symmetrix matrix
133 | so3Array = ndarray[
134 | Tuple[L[3, 3]], dtype[floating]
135 | ] # so(3) Lie algebra of SO(3), skew-symmetrix matrix
136 | se3Array = ndarray[
137 | Tuple[L[4, 4]], dtype[floating]
138 | ] # se(3) Lie algebra of SE(3), augmented skew-symmetrix matrix
139 |
140 | # quaternion arrays
141 | QuaternionArray = ndarray[
142 | Tuple[L[4,]],
143 | dtype[floating],
144 | ]
145 | UnitQuaternionArray = ndarray[
146 | Tuple[L[4,]],
147 | dtype[floating],
148 | ]
149 |
150 | Rn = Union[R2, R3]
151 |
152 | SOnArray = Union[SO2Array, SO3Array]
153 | SEnArray = Union[SE2Array, SE3Array]
154 |
155 | sonArray = Union[so2Array, so3Array]
156 | senArray = Union[se2Array, se3Array]
157 |
158 | Color = Union[str, ArrayLike3]
159 |
--------------------------------------------------------------------------------
/tests/base/test_graphics.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import numpy as np
3 | import matplotlib.pyplot as plt
4 | import pytest
5 | import sys
6 | from spatialmath.base import *
7 |
8 | # test graphics primitives
9 | # TODO check they actually create artists
10 |
11 |
12 | class TestGraphics(unittest.TestCase):
13 | def teardown_method(self, method):
14 | plt.close("all")
15 |
16 | @pytest.mark.skipif(
17 | sys.platform.startswith("darwin") and sys.version_info < (3, 11),
18 | reason="tkinter bug with mac",
19 | )
20 | def test_plotvol2(self):
21 | plotvol2(5)
22 |
23 | @pytest.mark.skipif(
24 | sys.platform.startswith("darwin") and sys.version_info < (3, 11),
25 | reason="tkinter bug with mac",
26 | )
27 | def test_plotvol3(self):
28 | plotvol3(5)
29 |
30 | @pytest.mark.skipif(
31 | sys.platform.startswith("darwin") and sys.version_info < (3, 11),
32 | reason="tkinter bug with mac",
33 | )
34 | def test_plot_point(self):
35 | plot_point((2, 3))
36 | plot_point(np.r_[2, 3])
37 | plot_point((2, 3), "x")
38 | plot_point((2, 3), "x", text="foo")
39 |
40 | @pytest.mark.skipif(
41 | sys.platform.startswith("darwin") and sys.version_info < (3, 11),
42 | reason="tkinter bug with mac",
43 | )
44 | def test_plot_text(self):
45 | plot_text((2, 3), "foo")
46 | plot_text(np.r_[2, 3], "foo")
47 |
48 | @pytest.mark.skipif(
49 | sys.platform.startswith("darwin") and sys.version_info < (3, 11),
50 | reason="tkinter bug with mac",
51 | )
52 | def test_plot_box(self):
53 | plot_box("r--", centre=(-2, -3), wh=(1, 1))
54 | plot_box(lt=(1, 1), rb=(2, 0), filled=True, color="b")
55 | plot_box(lrbt=(1, 2, 0, 1), filled=True, color="b")
56 | plot_box(ltrb=(1, 0, 2, 0), filled=True, color="b")
57 | plot_box(lt=(1, 2), wh=(2, 3))
58 | plot_box(lbwh=(1, 2, 3, 4))
59 | plot_box(centre=(1, 2), wh=(2, 3))
60 |
61 | @pytest.mark.skipif(
62 | sys.platform.startswith("darwin") and sys.version_info < (3, 11),
63 | reason="tkinter bug with mac",
64 | )
65 | def test_plot_circle(self):
66 | plot_circle(1, (0, 0), "r") # red circle
67 | plot_circle(2, (0, 0), "b--") # blue dashed circle
68 | plot_circle(0.5, (0, 0), filled=True, color="y") # yellow filled circle
69 |
70 | @pytest.mark.skipif(
71 | sys.platform.startswith("darwin") and sys.version_info < (3, 11),
72 | reason="tkinter bug with mac",
73 | )
74 | def test_ellipse(self):
75 | plot_ellipse(np.diag((1, 2)), (0, 0), "r") # red ellipse
76 | plot_ellipse(np.diag((1, 2)), (0, 0), "b--") # blue dashed ellipse
77 | plot_ellipse(
78 | np.diag((1, 2)), centre=(1, 1), filled=True, color="y"
79 | ) # yellow filled ellipse
80 |
81 | @pytest.mark.skipif(
82 | sys.platform.startswith("darwin") and sys.version_info < (3, 11),
83 | reason="tkinter bug with mac",
84 | )
85 | def test_plot_homline(self):
86 | plot_homline((1, 2, 3))
87 | plot_homline((2, 1, 3))
88 | plot_homline((1, -2, 3), "k--")
89 |
90 | @pytest.mark.skipif(
91 | sys.platform.startswith("darwin") and sys.version_info < (3, 11),
92 | reason="tkinter bug with mac",
93 | )
94 | def test_cuboid(self):
95 | plot_cuboid((1, 2, 3), color="g")
96 | plot_cuboid((1, 2, 3), centre=(2, 3, 4), color="g")
97 | plot_cuboid((1, 2, 3), filled=True, color="y")
98 |
99 | @pytest.mark.skipif(
100 | sys.platform.startswith("darwin") and sys.version_info < (3, 11),
101 | reason="tkinter bug with mac",
102 | )
103 | def test_sphere(self):
104 | plot_sphere(0.3, color="r")
105 | plot_sphere(1, centre=(1, 1, 1), filled=True, color="b")
106 |
107 | @pytest.mark.skipif(
108 | sys.platform.startswith("darwin") and sys.version_info < (3, 11),
109 | reason="tkinter bug with mac",
110 | )
111 | def test_ellipsoid(self):
112 | plot_ellipsoid(np.diag((1, 2, 3)), color="r") # red ellipsoid
113 | plot_ellipsoid(
114 | np.diag((1, 2, 3)), centre=(1, 2, 3), filled=True, color="y"
115 | ) # yellow filled ellipsoid
116 |
117 | @pytest.mark.skipif(
118 | sys.platform.startswith("darwin") and sys.version_info < (3, 11),
119 | reason="tkinter bug with mac",
120 | )
121 | def test_cylinder(self):
122 | plot_cylinder(radius=0.2, centre=(0.5, 0.5, 0), height=[-0.2, 0.2])
123 | plot_cylinder(
124 | radius=0.2,
125 | centre=(0.5, 0.5, 0),
126 | height=[-0.2, 0.2],
127 | filled=True,
128 | resolution=5,
129 | color="red",
130 | )
131 |
132 | @pytest.mark.skipif(
133 | sys.platform.startswith("darwin") and sys.version_info < (3, 11),
134 | reason="tkinter bug with mac",
135 | )
136 | def test_cone(self):
137 | plot_cone(radius=0.2, centre=(0.5, 0.5, 0), height=0.3)
138 | plot_cone(
139 | radius=0.2,
140 | centre=(0.5, 0.5, 0),
141 | height=0.3,
142 | filled=True,
143 | resolution=5,
144 | color="red",
145 | )
146 |
147 |
148 | # ---------------------------------------------------------------------------------------#
149 | if __name__ == "__main__":
150 | unittest.main(buffer=True)
151 |
--------------------------------------------------------------------------------
/spatialmath/base/README.md:
--------------------------------------------------------------------------------
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 "", line 1, in
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 |
--------------------------------------------------------------------------------
/spatialmath/base/__init__.py:
--------------------------------------------------------------------------------
1 | # Part of Spatial Math Toolbox for Python
2 | # Copyright (c) 2000 Peter Corke
3 | # MIT Licence, see details in top-level file: LICENCE
4 |
5 | from spatialmath.base.argcheck import * # lgtm [py/polluting-import]
6 | from spatialmath.base.quaternions import * # lgtm [py/polluting-import]
7 | from spatialmath.base.transforms2d import * # lgtm [py/polluting-import]
8 | from spatialmath.base.transforms3d import * # lgtm [py/polluting-import]
9 | from spatialmath.base.transformsNd import * # lgtm [py/polluting-import]
10 | from spatialmath.base.vectors import * # lgtm [py/polluting-import]
11 | from spatialmath.base.symbolic import * # lgtm [py/polluting-import]
12 | from spatialmath.base.animate import * # lgtm [py/polluting-import]
13 | from spatialmath.base.graphics import * # lgtm [py/polluting-import]
14 | from spatialmath.base.numeric import * # lgtm [py/polluting-import]
15 |
16 | from spatialmath.base.argcheck import (
17 | assertmatrix,
18 | ismatrix,
19 | getvector,
20 | assertvector,
21 | isvector,
22 | isscalar,
23 | getunit,
24 | isnumberlist,
25 | isvectorlist,
26 | )
27 |
28 | # from spatialmath.base.quaternions import (
29 | # pure,
30 | # qnorm,
31 | # unit,
32 | # isunit,
33 | # isequal,
34 | # q2v,
35 | # v2q,
36 | # qqmul,
37 | # inner,
38 | # qvmul,
39 | # vvmul,
40 | # qpow,
41 | # conj,
42 | # q2r,
43 | # r2q,
44 | # slerp,
45 | # rand,
46 | # matrix,
47 | # dot,
48 | # dotb,
49 | # angle,
50 | # qprint,
51 | # )
52 | # from spatialmath.base.transforms2d import (
53 | # rot2,
54 | # trot2,
55 | # transl2,
56 | # ishom2,
57 | # isrot2,
58 | # trlog2,
59 | # trexp2,
60 | # tr2jac2,
61 | # trinterp2,
62 | # trprint2,
63 | # trplot2,
64 | # tranimate2,
65 | # xyt2tr,
66 | # tr2xyt,
67 | # trinv2,
68 | # )
69 | # from spatialmath.base.transforms3d import (
70 | # rotx,
71 | # roty,
72 | # rotz,
73 | # trotx,
74 | # troty,
75 | # trotz,
76 | # transl,
77 | # ishom,
78 | # isrot,
79 | # rpy2r,
80 | # rpy2tr,
81 | # eul2r,
82 | # eul2tr,
83 | # angvec2r,
84 | # angvec2tr,
85 | # exp2r,
86 | # exp2tr,
87 | # oa2r,
88 | # oa2tr,
89 | # tr2angvec,
90 | # tr2eul,
91 | # tr2rpy,
92 | # trlog,
93 | # trexp,
94 | # trnorm,
95 | # trinterp,
96 | # delta2tr,
97 | # trinv,
98 | # tr2delta,
99 | # tr2jac,
100 | # rpy2jac,
101 | # eul2jac,
102 | # exp2jac,
103 | # rot2jac,
104 | # angvelxform,
105 | # trprint,
106 | # trplot,
107 | # tranimate,
108 | # )
109 | # from spatialmath.base.transformsNd import (
110 | # t2r,
111 | # r2t,
112 | # tr2rt,
113 | # rt2tr,
114 | # Ab2M,
115 | # isR,
116 | # isskew,
117 | # isskewa,
118 | # iseye,
119 | # skew,
120 | # vex,
121 | # skewa,
122 | # vexa,
123 | # h2e,
124 | # e2h,
125 | # homtrans,
126 | # rodrigues,
127 | # )
128 | from spatialmath.base.vectors import (
129 | colvec,
130 | unitvec,
131 | unitvec_norm,
132 | norm,
133 | normsq,
134 | isunitvec,
135 | iszerovec,
136 | isunittwist,
137 | isunittwist2,
138 | unittwist,
139 | unittwist_norm,
140 | unittwist2,
141 | angdiff,
142 | removesmall,
143 | cross,
144 | iszero,
145 | wrap_0_2pi,
146 | wrap_mpi_pi,
147 | )
148 |
149 | # from spatialmath.base.symbolic import *
150 | # from spatialmath.base.animate import Animate, Animate2
151 | # from spatialmath.base.graphics import (
152 | # plotvol2,
153 | # plotvol3,
154 | # plot_point,
155 | # plot_text,
156 | # plot_box,
157 | # plot_poly,
158 | # circle,
159 | # ellipse,
160 | # sphere,
161 | # ellipsoid,
162 | # plot_box,
163 | # plot_circle,
164 | # plot_ellipse,
165 | # plot_homline,
166 | # plot_sphere,
167 | # plot_ellipsoid,
168 | # plot_cylinder,
169 | # plot_cone,
170 | # plot_cuboid,
171 | # axes_logic,
172 | # isnotebook,
173 | # )
174 | # from spatialmath.base.numeric import numjac, array2str, bresenham
175 |
176 |
177 | __all__ = [
178 | # spatialmath.base.argcheck
179 | "assertmatrix",
180 | "ismatrix",
181 | "getvector",
182 | "assertvector",
183 | "isvector",
184 | "isscalar",
185 | "getunit",
186 | "isnumberlist",
187 | "isvectorlist",
188 | # spatialmath.base.quaternions
189 | "qpure",
190 | "qnorm",
191 | "qunit",
192 | "qisunit",
193 | "qisequal",
194 | "q2v",
195 | "v2q",
196 | "qqmul",
197 | "qinner",
198 | "qvmul",
199 | "vvmul",
200 | "qpow",
201 | "qconj",
202 | "q2r",
203 | "r2q",
204 | "qslerp",
205 | "qrand",
206 | "qmatrix",
207 | "qdot",
208 | "qdotb",
209 | "qangle",
210 | "qprint",
211 | "q2str",
212 | # spatialmath.base.transforms2d
213 | "rot2",
214 | "trot2",
215 | "transl2",
216 | "ishom2",
217 | "isrot2",
218 | "trlog2",
219 | "trexp2",
220 | "trnorm2",
221 | "tr2jac2",
222 | "trinterp2",
223 | "trprint2",
224 | "trplot2",
225 | "tranimate2",
226 | "xyt2tr",
227 | "tr2xyt",
228 | "trinv2",
229 | # spatialmath.base.transforms3d
230 | "rotx",
231 | "roty",
232 | "rotz",
233 | "trotx",
234 | "troty",
235 | "trotz",
236 | "transl",
237 | "ishom",
238 | "isrot",
239 | "rpy2r",
240 | "rpy2tr",
241 | "eul2r",
242 | "eul2tr",
243 | "angvec2r",
244 | "angvec2tr",
245 | "exp2r",
246 | "exp2tr",
247 | "oa2r",
248 | "oa2tr",
249 | "rodrigues",
250 | "tr2angvec",
251 | "tr2eul",
252 | "tr2rpy",
253 | "trlog",
254 | "trexp",
255 | "trnorm",
256 | "trinterp",
257 | "delta2tr",
258 | "trinv",
259 | "tr2delta",
260 | "tr2jac",
261 | "tr2adjoint",
262 | "rpy2jac",
263 | "eul2jac",
264 | "exp2jac",
265 | "rot2jac",
266 | "trprint",
267 | "trplot",
268 | "tranimate",
269 | "tr2x",
270 | "x2tr",
271 | "r2x",
272 | "x2r",
273 | "rotvelxform",
274 | "rotvelxform_inv_dot",
275 | # deprecated
276 | "angvelxform",
277 | "angvelxform_dot",
278 | # spatialmath.base.transformsNd
279 | "t2r",
280 | "r2t",
281 | "tr2rt",
282 | "rt2tr",
283 | "Ab2M",
284 | "isR",
285 | "isskew",
286 | "isskewa",
287 | "iseye",
288 | "skew",
289 | "vex",
290 | "skewa",
291 | "vexa",
292 | "h2e",
293 | "e2h",
294 | "homtrans",
295 | # spatialmath.base.vectors
296 | "colvec",
297 | "unitvec",
298 | "unitvec_norm",
299 | "norm",
300 | "normsq",
301 | "isunitvec",
302 | "iszerovec",
303 | "isunittwist",
304 | "isunittwist2",
305 | "unittwist",
306 | "unittwist_norm",
307 | "unittwist2",
308 | "angdiff",
309 | "removesmall",
310 | "cross",
311 | "iszero",
312 | "wrap_0_2pi",
313 | "wrap_mpi_pi",
314 | "wrap_0_pi",
315 | # spatialmath.base.animate
316 | "Animate",
317 | "Animate2",
318 | # spatial.base.graphics
319 | "plotvol2",
320 | "plotvol3",
321 | "plot_point",
322 | "plot_text",
323 | "plot_box",
324 | "plot_polygon",
325 | "circle",
326 | "ellipse",
327 | "sphere",
328 | "ellipsoid",
329 | "plot_box",
330 | "plot_arrow",
331 | "plot_circle",
332 | "plot_ellipse",
333 | "plot_homline",
334 | "plot_sphere",
335 | "plot_ellipsoid",
336 | "plot_cylinder",
337 | "plot_cone",
338 | "plot_cuboid",
339 | "axes_logic",
340 | "expand_dims",
341 | "isnotebook",
342 | # spatial.base.numeric
343 | "numjac",
344 | "numhess",
345 | "array2str",
346 | "str2array",
347 | "bresenham",
348 | "mpq_point",
349 | "gauss1d",
350 | "gauss2d",
351 | ]
352 |
--------------------------------------------------------------------------------
/docs/source/conf.py:
--------------------------------------------------------------------------------
1 | # spatialmath
2 | # Configuration file for the Sphinx documentation builder.
3 | #
4 | # This file only contains a selection of the most common options. For a full
5 | # list see the documentation:
6 | # https://www.sphinx-doc.org/en/master/usage/configuration.html
7 |
8 | # -- Path setup --------------------------------------------------------------
9 |
10 | # If extensions (or modules to document with autodoc) are in another directory,
11 | # add these directories to sys.path here. If the directory is relative to the
12 | # documentation root, use os.path.abspath to make it absolute, like shown here.
13 | #
14 |
15 | # sys.path.insert(0, os.path.abspath('.'))
16 | # sys.path.insert(0, os.path.abspath('..'))
17 |
18 |
19 | # -- Project information -----------------------------------------------------
20 |
21 | project = "Spatial Maths package"
22 | copyright = "2020-, Peter Corke."
23 | author = "Peter Corke"
24 | try:
25 | import spatialmath
26 |
27 | version = spatialmath.__version__
28 | except AttributeError:
29 | import re
30 |
31 | with open("../../pyproject.toml", "r") as f:
32 | m = re.compile(r'version\s*=\s*"([0-9\.]+)"').search(f.read())
33 | version = m[1]
34 |
35 | # -- General configuration ---------------------------------------------------
36 |
37 | # Add any Sphinx extension module names here, as strings. They can be
38 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
39 | # ones.
40 | extensions = [
41 | "sphinx.ext.autodoc",
42 | "sphinx.ext.todo",
43 | "sphinx.ext.viewcode",
44 | "sphinx.ext.mathjax",
45 | "sphinx.ext.coverage",
46 | "sphinx.ext.doctest",
47 | "sphinx.ext.inheritance_diagram",
48 | "matplotlib.sphinxext.plot_directive",
49 | "sphinx_autodoc_typehints",
50 | "sphinx_autorun",
51 | "sphinx.ext.intersphinx",
52 | "sphinx_favicon",
53 | ]
54 | #'sphinx.ext.autosummary',
55 | # typehints_use_signature_return = True
56 |
57 | # inheritance_node_attrs = dict(style='rounded,filled', fillcolor='lightblue')
58 | inheritance_node_attrs = dict(style="rounded")
59 |
60 | autosummary_generate = True
61 | autodoc_member_order = "groupwise"
62 | # bysource
63 |
64 | # options for spinx_autorun, used for inline examples
65 | # choose UTF-8 encoding to allow for Unicode characters, eg. ansitable
66 | # Python session setup, turn off color printing for SE3, set NumPy precision
67 | autorun_languages = {}
68 | autorun_languages["pycon_output_encoding"] = "UTF-8"
69 | autorun_languages["pycon_input_encoding"] = "UTF-8"
70 | autorun_languages[
71 | "pycon_runfirst"
72 | ] = """
73 | from spatialmath import SE3
74 | SE3._color = False
75 | import numpy as np
76 | np.set_printoptions(precision=4, suppress=True)
77 | from ansitable import ANSITable
78 | ANSITable._color = False
79 | """
80 |
81 |
82 | # Add any paths that contain templates here, relative to this directory.
83 | templates_path = ["_templates"]
84 |
85 | # List of patterns, relative to source directory, that match files and
86 | # directories to ignore when looking for source files.
87 | # This pattern also affects html_static_path and html_extra_path.
88 | exclude_patterns = ["test_*"]
89 |
90 | add_module_names = False
91 | # -- Options for HTML output -------------------------------------------------
92 |
93 | # The theme to use for HTML and HTML Help pages. See the documentation for
94 | # a list of builtin themes.
95 | #
96 | html_theme = "sphinx_rtd_theme"
97 | # html_theme = 'alabaster'
98 | # html_theme = 'pyramid'
99 | # html_theme = 'sphinxdoc'
100 |
101 | html_theme_options = {
102 | #'github_user': 'petercorke',
103 | #'github_repo': 'spatialmath-python',
104 | #'logo_name': False,
105 | "logo_only": False,
106 | #'description': 'Spatial maths and geometry for Python',
107 | "display_version": True,
108 | "prev_next_buttons_location": "both",
109 | "analytics_id": "G-11Q6WJM565",
110 | }
111 | html_logo = "../figs/CartesianSnakes_LogoW.png"
112 |
113 | # Add any paths that contain custom static files (such as style sheets) here,
114 | # relative to this directory. They are copied after the builtin static files,
115 | # so a file named "default.css" will overwrite the builtin "default.css".
116 | # html_static_path = ['_static']
117 |
118 | # autodoc_mock_imports = ["numpy", "scipy"]
119 | html_last_updated_fmt = "%d-%b-%Y"
120 | # extensions = ['rst2pdf.pdfbuilder']
121 | # pdf_documents = [('index', u'rst2pdf', u'Sample rst2pdf doc', u'Your Name'),]
122 | latex_engine = "xelatex"
123 | # maybe need to set graphics path in here somewhere
124 | # \graphicspath{{figures/}{../figures/}{C:/Users/me/Documents/project/figures/}}
125 | # https://stackoverflow.com/questions/63452024/how-to-include-image-files-in-sphinx-latex-pdf-files
126 | latex_elements = {
127 | # The paper size ('letterpaper' or 'a4paper').
128 | "papersize": "a4paper",
129 | #'releasename':" ",
130 | # Sonny, Lenny, Glenn, Conny, Rejne, Bjarne and Bjornstrup
131 | # 'fncychap': '\\usepackage[Lenny]{fncychap}',
132 | "fncychap": "\\usepackage{fncychap}",
133 | }
134 |
135 | # -------- RVC maths notation -------------------------------------------------------#
136 |
137 | # see https://stackoverflow.com/questions/9728292/creating-latex-math-macros-within-sphinx
138 | mathjax3_config = {
139 | "tex": {
140 | "macros": {
141 | # RVC Math notation
142 | # - not possible to do the if/then/else approach
143 | # - subset only
144 | "presup": [r"\,{}^{\scriptscriptstyle #1}\!", 1],
145 | # groups
146 | "SE": [r"\mathbf{SE}(#1)", 1],
147 | "SO": [r"\mathbf{SO}(#1)", 1],
148 | "se": [r"\mathbf{se}(#1)", 1],
149 | "so": [r"\mathbf{so}(#1)", 1],
150 | # vectors
151 | "vec": [r"\boldsymbol{#1}", 1],
152 | "dvec": [r"\dot{\boldsymbol{#1}}", 1],
153 | "ddvec": [r"\ddot{\boldsymbol{#1}}", 1],
154 | "fvec": [r"\presup{#1}\boldsymbol{#2}", 2],
155 | "fdvec": [r"\presup{#1}\dot{\boldsymbol{#2}}", 2],
156 | "fddvec": [r"\presup{#1}\ddot{\boldsymbol{#2}}", 2],
157 | "norm": [r"\Vert #1 \Vert", 1],
158 | # matrices
159 | "mat": [r"\mathbf{#1}", 1],
160 | "dmat": [r"\dot{\mathbf{#1}}", 1],
161 | "fmat": [r"\presup{#1}\mathbf{#2}", 2],
162 | # skew matrices
163 | "sk": [r"\left[#1\right]", 1],
164 | "skx": [r"\left[#1\right]_{\times}", 1],
165 | "vex": [r"\vee\left( #1\right)", 1],
166 | "vexx": [r"\vee_{\times}\left( #1\right)", 1],
167 | # quaternions
168 | "q": r"\mathring{q}",
169 | "fq": [r"\presup{#1}\mathring{q}", 1],
170 | }
171 | }
172 | }
173 |
174 |
175 | autorun_languages = {}
176 | autorun_languages["pycon_output_encoding"] = "UTF-8"
177 | autorun_languages["pycon_input_encoding"] = "UTF-8"
178 | autorun_languages[
179 | "pycon_runfirst"
180 | ] = """
181 | from spatialmath import SE3
182 | SE3._color = False
183 | import numpy as np
184 | np.set_printoptions(precision=4, suppress=True)
185 | """
186 |
187 | intersphinx_mapping = {
188 | "numpy": ("http://docs.scipy.org/doc/numpy/", None),
189 | "scipy": ("http://docs.scipy.org/doc/scipy/reference/", None),
190 | "matplotlib": ("https://matplotlib.org/stable/", None),
191 | }
192 |
193 | # -------- Options favicon -------------------------------------------------------#
194 |
195 | html_static_path = ["_static"]
196 | # create favicons online using https://favicon.io/favicon-converter/
197 | favicons = [
198 | {
199 | "rel": "icon",
200 | "sizes": "16x16",
201 | "href": "favicon-16x16.png",
202 | "type": "image/png",
203 | },
204 | {
205 | "rel": "icon",
206 | "sizes": "32x32",
207 | "href": "favicon-32x32.png",
208 | "type": "image/png",
209 | },
210 | {
211 | "rel": "apple-touch-icon",
212 | "sizes": "180x180",
213 | "href": "apple-touch-icon.png",
214 | "type": "image/png",
215 | },
216 | {
217 | "rel": "android-chrome",
218 | "sizes": "192x192",
219 | "href": "android-chrome-192x192.png",
220 | "type": "image/png",
221 | },
222 | {
223 | "rel": "android-chrome",
224 | "sizes": "512x512",
225 | "href": "android-chrome-512x512.png",
226 | "type": "image/png",
227 | },
228 | ]
229 |
230 | autodoc_type_aliases = {"SO3Array": "SO3Array"}
231 |
--------------------------------------------------------------------------------
/spatialmath/base/symbolic.py:
--------------------------------------------------------------------------------
1 | # Part of Spatial Math Toolbox for Python
2 | # Copyright (c) 2000 Peter Corke
3 | # MIT Licence, see details in top-level file: LICENCE
4 |
5 | """
6 | This package provides a light-weight wrapper to support use of SymPy. It
7 | generalizes some common functions so that they can accept numerical or
8 | Symbolic arguments.
9 |
10 | If SymPy is not installed then only the standard numeric operations are
11 | supported.
12 | """
13 |
14 | import math
15 | from spatialmath.base.types import *
16 |
17 | try: # pragma: no cover
18 | # print('Using SymPy')
19 | import sympy
20 |
21 | _symbolics = True
22 | symtype = (sympy.Expr,)
23 | from sympy import Symbol
24 |
25 | except ImportError: # pragma: no cover
26 | # SymPy is not installed
27 | _symbolics = False
28 | symtype = ()
29 | Symbol = Any
30 |
31 |
32 | # ---------------------------------------------------------------------------------------#
33 |
34 | if _symbolics:
35 |
36 | def symbol(
37 | name: str, real: Optional[bool] = True
38 | ) -> Union[Symbol, Tuple[Symbol, ...]]:
39 | """
40 | Create symbolic variables
41 |
42 | :param name: symbol names
43 | :type name: str
44 | :param real: assume variable is real, defaults to True
45 | :type real: bool, optional
46 | :return: SymPy symbols
47 | :rtype: sympy
48 |
49 | .. runblock:: pycon
50 |
51 | >>> from spatialmath.base.symbolic import *
52 | >>> theta = symbol('theta')
53 | >>> theta
54 | >>> theta, psi = symbol('theta psi')
55 | >>> theta
56 | >>> psi
57 | >>> q = symbol('q_:6')
58 | >>> q
59 |
60 | .. note:: In Jupyter symbols are pretty printed.
61 |
62 | - symbols named after greek letters will appear as greek letters
63 | - underscore means subscript as it does in LaTex, so the symbols ``q``
64 | above will be subscripted.
65 |
66 | :seealso: :func:`sympy.symbols`
67 | """
68 | return sympy.symbols(name, real=real)
69 |
70 |
71 | def issymbol(var: Any) -> bool:
72 | """
73 | Test if variable is symbolic
74 |
75 | :param var: variable to test
76 | :return: whether variable is symbolic
77 | :rtype: bool
78 |
79 | .. runblock:: pycon
80 |
81 | >>> from spatialmath.base.symbolic import *
82 | >>> theta = symbol('theta')
83 | >>> issymbol(theta)
84 | >>> issymbol(3.4)
85 |
86 | """
87 | if _symbolics:
88 | if isinstance(var, (list, tuple)):
89 | return any([isinstance(x, symtype) for x in var])
90 | else:
91 | return isinstance(var, symtype)
92 | else:
93 | return False
94 |
95 |
96 | @overload
97 | def sin(theta: float) -> float:
98 | ...
99 |
100 |
101 | @overload
102 | def sin(theta: Symbol) -> Symbol:
103 | ...
104 |
105 |
106 | def sin(theta):
107 | """
108 | Generalized sine function
109 |
110 | :param θ: argument
111 | :type θ: float or symbolic
112 | :return: sin(θ)
113 | :rtype: float or symbolic
114 |
115 | .. runblock:: pycon
116 |
117 | >>> from spatialmath.base.symbolic import *
118 | >>> theta = symbol('theta')
119 | >>> sin(theta)
120 | >>> sin(0.5)
121 |
122 | :seealso: :func:`sympy.sin`
123 | """
124 | if issymbol(theta):
125 | return sympy.sin(theta)
126 | else:
127 | return math.sin(theta)
128 |
129 |
130 | @overload
131 | def cos(theta: float) -> float:
132 | ...
133 |
134 |
135 | @overload
136 | def cos(theta: Symbol) -> Symbol:
137 | ...
138 |
139 |
140 | def cos(theta):
141 | """
142 | Generalized cosine function
143 |
144 | :param θ: argument
145 | :type θ: float or symbolic
146 | :return: cos(θ)
147 | :rtype: float or symbolic
148 |
149 | .. runblock:: pycon
150 |
151 | >>> from spatialmath.base.symbolic import *
152 | >>> theta = symbol('theta')
153 | >>> cos(theta)
154 | >>> cos(0.5)
155 |
156 | :seealso: :func:`sympy.cos`
157 | """
158 | if issymbol(theta):
159 | return sympy.cos(theta)
160 | else:
161 | return math.cos(theta)
162 |
163 |
164 | @overload
165 | def tan(theta: float) -> float:
166 | ...
167 |
168 |
169 | @overload
170 | def tan(theta: Symbol) -> Symbol:
171 | ...
172 |
173 |
174 | def tan(theta):
175 | """
176 | Generalized tangent function
177 |
178 | :param θ: argument
179 | :type θ: float or symbolic
180 | :return: tan(θ)
181 | :rtype: float or symbolic
182 |
183 | .. runblock:: pycon
184 |
185 | >>> from spatialmath.base.symbolic import *
186 | >>> theta = symbol('theta')
187 | >>> tan(theta)
188 | >>> tan(0.5)
189 |
190 | :seealso: :func:`sympy.cos`
191 | """
192 | if issymbol(theta):
193 | return sympy.tan(theta)
194 | else:
195 | return math.tan(theta)
196 |
197 |
198 | @overload
199 | def sqrt(theta: float) -> float:
200 | ...
201 |
202 |
203 | @overload
204 | def sqrt(theta: Symbol) -> Symbol:
205 | ...
206 |
207 |
208 | def sqrt(v):
209 | """
210 | Generalized sqrt function
211 |
212 | :param v: argument
213 | :type v: float or symbolic
214 | :return: √ v
215 | :rtype: float or symbolic
216 |
217 | .. runblock:: pycon
218 |
219 | >>> from spatialmath.base.symbolic import *
220 | >>> x = symbol('x')
221 | >>> sqrt(x ** 2)
222 | >>> sqrt(4)
223 |
224 | :seealso: :func:`sympy.sqrt`
225 | """
226 | if issymbol(v):
227 | return sympy.sqrt(v)
228 | else:
229 | return math.sqrt(v)
230 |
231 |
232 | def zero() -> Symbol:
233 | """
234 | Symbolic constant: zero
235 |
236 | :return: 0
237 | :rtype: symbolic
238 |
239 | .. runblock:: pycon
240 |
241 | >>> from spatialmath.base.symbolic import *
242 | >>> x = symbol('x')
243 | >>> zero()
244 | >>> x + zero()
245 |
246 | :seealso: :func:`sympy.S.Zero`
247 | """
248 | return sympy.S.Zero
249 |
250 |
251 | def one() -> Symbol:
252 | """
253 | Symbolic constant: one
254 |
255 | :return: 1
256 | :rtype: symbolic
257 |
258 | .. runblock:: pycon
259 |
260 | >>> from spatialmath.base.symbolic import *
261 | >>> x = symbol('x')
262 | >>> one()
263 | >>> one() * x
264 |
265 | :seealso: :func:`sympy.S.One`
266 | """
267 | return sympy.S.One
268 |
269 |
270 | def negative_one() -> Symbol:
271 | """
272 | Symbolic constant: negative one
273 |
274 | :return: -1
275 | :rtype: symbolic
276 |
277 | .. runblock:: pycon
278 |
279 | >>> from spatialmath.base.symbolic import *
280 | >>> x = symbol('x')
281 | >>> negative_one()
282 | >>> negative_one() * x
283 |
284 | :seealso: :func:`sympy.S.NegativeOne`
285 | """
286 | return sympy.S.NegativeOne
287 |
288 |
289 | def pi() -> Symbol:
290 | """
291 | Symbolic constant: pi
292 |
293 | :return: π
294 | :rtype: symbolic
295 |
296 | .. runblock:: pycon
297 |
298 | >>> from spatialmath.base.symbolic import *
299 | >>> import math
300 | >>> sin(pi())
301 | >>> sin(math.pi)
302 |
303 | :seealso: :func:`sympy.S.Pi`
304 | """
305 | return sympy.S.Pi
306 |
307 |
308 | def simplify(x: Symbol) -> Symbol:
309 | """
310 | Symbolic simplification
311 |
312 | :param x: expression to simplify
313 | :type x: symbolic
314 | :return: -1
315 | :rtype: symbolic
316 |
317 | .. runblock:: pycon
318 |
319 | >>> from spatialmath.base.symbolic import *
320 | >>> x = symbol('x')
321 | >>> y = (x - 1) * (x + 1) - x ** 2
322 | >>> y
323 | >>> simplify(y)
324 |
325 | :seealso: :func:`sympy.simplify`
326 | """
327 | if _symbolics:
328 | return sympy.simplify(x)
329 | else:
330 | return x
331 |
332 |
333 | def det(x):
334 | """
335 | Symbolic determinant
336 |
337 | :param m: matrix
338 | :type x: ndarray with symbolic elements
339 | :return: determinant
340 | :rtype: ndarray with symbolic elements
341 |
342 | .. runblock:: pycon
343 |
344 | >>> from spatialmath.base.symbolic import *
345 | >>> from spatialmath.base import rot2
346 | >>> theta = symbol('theta')
347 | >>> R = rot2(theta)
348 | >>> print(R)
349 | >>> print(det(R))
350 | >>> simplify(print(det(R)))
351 |
352 | .. note:: Converts to a SymPy ``Matrix`` and then back again.
353 | """
354 |
355 | return sympy.Matrix(x).det()
356 |
--------------------------------------------------------------------------------
/tests/test_spatialvector.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import numpy.testing as nt
3 | import numpy as np
4 |
5 | from spatialmath.spatialvector import *
6 |
7 |
8 | class TestSpatialVector(unittest.TestCase):
9 | def test_list_powers(self):
10 | x = SpatialVelocity.Empty()
11 | self.assertEqual(len(x), 0)
12 | x.append(SpatialVelocity([1, 2, 3, 4, 5, 6]))
13 | self.assertEqual(len(x), 1)
14 |
15 | x.append(SpatialVelocity([7, 8, 9, 10, 11, 12]))
16 | self.assertEqual(len(x), 2)
17 |
18 | y = x[0]
19 | self.assertIsInstance(y, SpatialVelocity)
20 | self.assertEqual(len(y), 1)
21 | self.assertTrue(all(y.A == np.r_[1, 2, 3, 4, 5, 6]))
22 |
23 | y = x[1]
24 | self.assertIsInstance(y, SpatialVelocity)
25 | self.assertEqual(len(y), 1)
26 | self.assertTrue(all(y.A == np.r_[7, 8, 9, 10, 11, 12]))
27 |
28 | x.insert(0, SpatialVelocity([20, 21, 22, 23, 24, 25]))
29 |
30 | y = x[0]
31 | self.assertIsInstance(y, SpatialVelocity)
32 | self.assertEqual(len(y), 1)
33 | self.assertTrue(all(y.A == np.r_[20, 21, 22, 23, 24, 25]))
34 |
35 | y = x[1]
36 | self.assertIsInstance(y, SpatialVelocity)
37 | self.assertEqual(len(y), 1)
38 | self.assertTrue(all(y.A == np.r_[1, 2, 3, 4, 5, 6]))
39 |
40 | def test_velocity(self):
41 | a = SpatialVelocity([1, 2, 3, 4, 5, 6])
42 | self.assertIsInstance(a, SpatialVelocity)
43 | self.assertIsInstance(a, SpatialVector)
44 | self.assertIsInstance(a, SpatialM6)
45 | self.assertEqual(len(a), 1)
46 | self.assertTrue(all(a.A == np.r_[1, 2, 3, 4, 5, 6]))
47 |
48 | a = SpatialVelocity(np.r_[1, 2, 3, 4, 5, 6])
49 | self.assertIsInstance(a, SpatialVelocity)
50 | self.assertIsInstance(a, SpatialVector)
51 | self.assertIsInstance(a, SpatialM6)
52 | self.assertEqual(len(a), 1)
53 | self.assertTrue(all(a.A == np.r_[1, 2, 3, 4, 5, 6]))
54 |
55 | s = str(a)
56 | self.assertIsInstance(s, str)
57 | self.assertEqual(s.count("\n"), 0)
58 | self.assertTrue(s.startswith("SpatialVelocity"))
59 |
60 | r = np.random.rand(6, 10)
61 | a = SpatialVelocity(r)
62 | self.assertIsInstance(a, SpatialVelocity)
63 | self.assertIsInstance(a, SpatialVector)
64 | self.assertIsInstance(a, SpatialM6)
65 | self.assertEqual(len(a), 10)
66 |
67 | b = a[3]
68 | self.assertIsInstance(b, SpatialVelocity)
69 | self.assertIsInstance(b, SpatialVector)
70 | self.assertIsInstance(b, SpatialM6)
71 | self.assertEqual(len(b), 1)
72 | self.assertTrue(all(b.A == r[:, 3]))
73 |
74 | s = str(a)
75 | self.assertIsInstance(s, str)
76 | self.assertEqual(s.count("\n"), 9)
77 |
78 | def test_acceleration(self):
79 | a = SpatialAcceleration([1, 2, 3, 4, 5, 6])
80 | self.assertIsInstance(a, SpatialAcceleration)
81 | self.assertIsInstance(a, SpatialVector)
82 | self.assertIsInstance(a, SpatialM6)
83 | self.assertEqual(len(a), 1)
84 | self.assertTrue(all(a.A == np.r_[1, 2, 3, 4, 5, 6]))
85 |
86 | a = SpatialAcceleration(np.r_[1, 2, 3, 4, 5, 6])
87 | self.assertIsInstance(a, SpatialAcceleration)
88 | self.assertIsInstance(a, SpatialVector)
89 | self.assertIsInstance(a, SpatialM6)
90 | self.assertEqual(len(a), 1)
91 | self.assertTrue(all(a.A == np.r_[1, 2, 3, 4, 5, 6]))
92 |
93 | s = str(a)
94 | self.assertIsInstance(s, str)
95 | self.assertEqual(s.count("\n"), 0)
96 | self.assertTrue(s.startswith("SpatialAcceleration"))
97 |
98 | r = np.random.rand(6, 10)
99 | a = SpatialAcceleration(r)
100 | self.assertIsInstance(a, SpatialAcceleration)
101 | self.assertIsInstance(a, SpatialVector)
102 | self.assertIsInstance(a, SpatialM6)
103 | self.assertEqual(len(a), 10)
104 |
105 | b = a[3]
106 | self.assertIsInstance(b, SpatialAcceleration)
107 | self.assertIsInstance(b, SpatialVector)
108 | self.assertIsInstance(b, SpatialM6)
109 | self.assertEqual(len(b), 1)
110 | self.assertTrue(all(b.A == r[:, 3]))
111 |
112 | s = str(a)
113 | self.assertIsInstance(s, str)
114 |
115 | def test_force(self):
116 | a = SpatialForce([1, 2, 3, 4, 5, 6])
117 | self.assertIsInstance(a, SpatialForce)
118 | self.assertIsInstance(a, SpatialVector)
119 | self.assertIsInstance(a, SpatialF6)
120 | self.assertEqual(len(a), 1)
121 | self.assertTrue(all(a.A == np.r_[1, 2, 3, 4, 5, 6]))
122 |
123 | a = SpatialForce(np.r_[1, 2, 3, 4, 5, 6])
124 | self.assertIsInstance(a, SpatialForce)
125 | self.assertIsInstance(a, SpatialVector)
126 | self.assertIsInstance(a, SpatialF6)
127 | self.assertEqual(len(a), 1)
128 | self.assertTrue(all(a.A == np.r_[1, 2, 3, 4, 5, 6]))
129 |
130 | s = str(a)
131 | self.assertIsInstance(s, str)
132 | self.assertEqual(s.count("\n"), 0)
133 | self.assertTrue(s.startswith("SpatialForce"))
134 |
135 | r = np.random.rand(6, 10)
136 | a = SpatialForce(r)
137 | self.assertIsInstance(a, SpatialForce)
138 | self.assertIsInstance(a, SpatialVector)
139 | self.assertIsInstance(a, SpatialF6)
140 | self.assertEqual(len(a), 10)
141 |
142 | b = a[3]
143 | self.assertIsInstance(b, SpatialForce)
144 | self.assertIsInstance(b, SpatialVector)
145 | self.assertIsInstance(b, SpatialF6)
146 | self.assertEqual(len(b), 1)
147 | self.assertTrue(all(b.A == r[:, 3]))
148 |
149 | s = str(a)
150 | self.assertIsInstance(s, str)
151 |
152 | def test_momentum(self):
153 | a = SpatialMomentum([1, 2, 3, 4, 5, 6])
154 | self.assertIsInstance(a, SpatialMomentum)
155 | self.assertIsInstance(a, SpatialVector)
156 | self.assertIsInstance(a, SpatialF6)
157 | self.assertEqual(len(a), 1)
158 | self.assertTrue(all(a.A == np.r_[1, 2, 3, 4, 5, 6]))
159 |
160 | a = SpatialMomentum(np.r_[1, 2, 3, 4, 5, 6])
161 | self.assertIsInstance(a, SpatialMomentum)
162 | self.assertIsInstance(a, SpatialVector)
163 | self.assertIsInstance(a, SpatialF6)
164 | self.assertEqual(len(a), 1)
165 | self.assertTrue(all(a.A == np.r_[1, 2, 3, 4, 5, 6]))
166 |
167 | s = str(a)
168 | self.assertIsInstance(s, str)
169 | self.assertEqual(s.count("\n"), 0)
170 | self.assertTrue(s.startswith("SpatialMomentum"))
171 |
172 | r = np.random.rand(6, 10)
173 | a = SpatialMomentum(r)
174 | self.assertIsInstance(a, SpatialMomentum)
175 | self.assertIsInstance(a, SpatialVector)
176 | self.assertIsInstance(a, SpatialF6)
177 | self.assertEqual(len(a), 10)
178 |
179 | b = a[3]
180 | self.assertIsInstance(b, SpatialMomentum)
181 | self.assertIsInstance(b, SpatialVector)
182 | self.assertIsInstance(b, SpatialF6)
183 | self.assertEqual(len(b), 1)
184 | self.assertTrue(all(b.A == r[:, 3]))
185 |
186 | s = str(a)
187 | self.assertIsInstance(s, str)
188 |
189 | def test_arith(self):
190 | # just test SpatialVelocity since all types derive from same superclass
191 |
192 | r1 = np.r_[1, 2, 3, 4, 5, 6]
193 | r2 = np.r_[7, 8, 9, 10, 11, 12]
194 | a1 = SpatialVelocity(r1)
195 | a2 = SpatialVelocity(r2)
196 |
197 | self.assertTrue(all((a1 + a2).A == r1 + r2))
198 | self.assertTrue(all((a1 - a2).A == r1 - r2))
199 | self.assertTrue(all((-a1).A == -r1))
200 |
201 | def test_inertia(self):
202 | # constructor
203 | i0 = SpatialInertia()
204 | nt.assert_equal(i0.A, np.zeros((6, 6)))
205 |
206 | i1 = SpatialInertia(np.eye(6, 6))
207 | nt.assert_equal(i1.A, np.eye(6, 6))
208 |
209 | i2 = SpatialInertia(m=1, r=(1, 2, 3))
210 | nt.assert_almost_equal(i2.A, i2.A.T)
211 |
212 | i3 = SpatialInertia(m=1, r=(1, 2, 3), I=np.ones((3, 3)))
213 | nt.assert_almost_equal(i3.A, i3.A.T)
214 |
215 | # addition
216 | m_a, m_b = 1.1, 2.2
217 | r = (1, 2, 3)
218 | i4a, i4b = SpatialInertia(m=m_a, r=r), SpatialInertia(m=m_b, r=r)
219 | nt.assert_almost_equal((i4a + i4b).A, SpatialInertia(m=m_a + m_b, r=r).A)
220 |
221 | # isvalid - note this method is very barebone, to be improved
222 | self.assertTrue(SpatialInertia().isvalid(np.ones((6, 6)), check=False))
223 |
224 | def test_products(self):
225 | # v x v = a *, v x F6 = a
226 | # a x I, I x a
227 | # v x I, I x v
228 | # twist x v, twist x a, twist x F
229 | pass
230 |
231 |
232 | # ---------------------------------------------------------------------------------------#
233 | if __name__ == "__main__":
234 | unittest.main()
235 |
--------------------------------------------------------------------------------
/symbolic/angvelxform_dot.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "attachments": {},
5 | "cell_type": "markdown",
6 | "metadata": {},
7 | "source": [
8 | "# Determine derivative of Jacobian from angular velocity to exponential rates\n",
9 | "\n",
10 | "Peter Corke 2021, updated 1/23\n",
11 | "\n",
12 | "SymPy code to determine the time derivative of the mapping from angular velocity to exponential coordinate rates."
13 | ]
14 | },
15 | {
16 | "cell_type": "code",
17 | "execution_count": null,
18 | "metadata": {},
19 | "outputs": [],
20 | "source": [
21 | "from sympy import *"
22 | ]
23 | },
24 | {
25 | "attachments": {},
26 | "cell_type": "markdown",
27 | "metadata": {},
28 | "source": [
29 | "A rotation matrix can be expressed in terms of exponential coordinates (also called the Euler vector)\n",
30 | "\n",
31 | "$\n",
32 | "\\mathbf{R} = e^{[\\varphi]_\\times} \n",
33 | "$\n",
34 | "where $\\mathbf{R} \\in SO(3)$ and $\\varphi \\in \\mathbb{R}^3$.\n",
35 | "\n",
36 | "The mapping from angular velocity $\\omega$ to exponential coordinate rates $\\dot{\\varphi}$ is\n",
37 | "\n",
38 | "$\n",
39 | "\\dot{\\varphi} = \\mathbf{A} \\omega\n",
40 | "$\n",
41 | "\n",
42 | "where $\\mathbf{A}$ is given by (2.107) of [Robot Dynamics Lecture Notes, Robotic Systems Lab, ETH Zurich, 2017](https://ethz.ch/content/dam/ethz/special-interest/mavt/robotics-n-intelligent-systems/rsl-dam/documents/RobotDynamics2017/RD_HS2017script.pdf)\n",
43 | "\n",
44 | "\n",
45 | "$\n",
46 | "\\mathbf{A} = \\mathbf{1}_{3 \\times 3} - \\frac{1}{2} [\\varphi]_\\times + [\\varphi]^2_\\times \\frac{1}{\\theta^2} \\left( 1 - \\frac{\\theta}{2} \\frac{\\sin \\theta}{1 - \\cos \\theta} \\right),\n",
47 | "$\n",
48 | "where $\\theta = \\| \\varphi \\|$\n",
49 | "\n",
50 | "We simplify the equation as\n",
51 | "\n",
52 | "$\n",
53 | "\\mathbf{A} = \\mathbf{1}_{3 \\times 3} - \\frac{1}{2} [\\varphi]_\\times + [\\varphi]^2_\\times \\Theta\n",
54 | "$\n",
55 | "\n",
56 | "where\n",
57 | "$\n",
58 | "\\Theta = \\frac{1}{\\theta^2} \\left( 1 - \\frac{\\theta}{2} \\frac{\\sin \\theta}{1 - \\cos \\theta} \\right)\n",
59 | "$\n",
60 | "\n",
61 | "We can find the derivative using the chain rule\n",
62 | "\n",
63 | "$\n",
64 | "\\dot{\\mathbf{A}} = - \\frac{1}{2} [\\dot{\\varphi}]_\\times + \\left( [\\varphi]_\\times [\\dot{\\varphi}]_\\times + [\\dot{\\varphi}]_\\times[\\varphi]_\\times \\right) \\Theta + [\\varphi]^2_\\times \\dot{\\Theta}\n",
65 | "$\n",
66 | "\n",
67 | "noting that the derivative of a matrix squared is\n",
68 | "\n",
69 | "$\n",
70 | "\\frac{d}{dt} (\\mathbf{M}^2) = (\\frac{d}{dt} \\mathbf{M}) \\mathbf{M} + \\mathbf{M} (\\frac{d}{dt} \\mathbf{M})\n",
71 | "$\n",
72 | "\n",
73 | "We start by defining some symbols"
74 | ]
75 | },
76 | {
77 | "cell_type": "code",
78 | "execution_count": null,
79 | "metadata": {},
80 | "outputs": [],
81 | "source": [
82 | "theta, theta_dot, t = symbols('theta theta_dot t', real=True)"
83 | ]
84 | },
85 | {
86 | "cell_type": "markdown",
87 | "metadata": {},
88 | "source": [
89 | "We start by finding an expression for $\\Theta$ which depends on $\\theta(t)$"
90 | ]
91 | },
92 | {
93 | "cell_type": "code",
94 | "execution_count": null,
95 | "metadata": {},
96 | "outputs": [],
97 | "source": [
98 | "theta_t = Function(theta)(t)\n",
99 | "theta_t"
100 | ]
101 | },
102 | {
103 | "cell_type": "code",
104 | "execution_count": null,
105 | "metadata": {},
106 | "outputs": [],
107 | "source": [
108 | "Theta = 1 / theta_t ** 2 * (1 - theta_t / 2 * sin(theta_t) / (1 - cos(theta_t)))\n",
109 | "Theta"
110 | ]
111 | },
112 | {
113 | "cell_type": "markdown",
114 | "metadata": {},
115 | "source": [
116 | "and now determine the derivative"
117 | ]
118 | },
119 | {
120 | "cell_type": "code",
121 | "execution_count": null,
122 | "metadata": {},
123 | "outputs": [],
124 | "source": [
125 | "T_dot = Theta.diff(t)\n",
126 | "T_dot"
127 | ]
128 | },
129 | {
130 | "cell_type": "markdown",
131 | "metadata": {},
132 | "source": [
133 | "which is a somewhat complex expression that depends on $\\theta(t)$ and $\\dot{\\theta}(t)$.\n",
134 | "\n",
135 | "We will remove the time dependency and generate code"
136 | ]
137 | },
138 | {
139 | "cell_type": "code",
140 | "execution_count": null,
141 | "metadata": {},
142 | "outputs": [],
143 | "source": [
144 | "T_dot = T_dot.subs([(theta_t.diff(t), theta_dot), (theta_t, theta)])\n",
145 | "T_dot"
146 | ]
147 | },
148 | {
149 | "cell_type": "code",
150 | "execution_count": null,
151 | "metadata": {},
152 | "outputs": [],
153 | "source": [
154 | "pycode(T_dot)"
155 | ]
156 | },
157 | {
158 | "cell_type": "markdown",
159 | "metadata": {},
160 | "source": [
161 | "In order to evaluate the line above we need an expression for $\\theta$ and $\\dot{\\theta}$. $\\theta$ is the norm of $\\varphi$ whose elements are functions of time"
162 | ]
163 | },
164 | {
165 | "cell_type": "code",
166 | "execution_count": null,
167 | "metadata": {},
168 | "outputs": [],
169 | "source": [
170 | "phi_names = ('varphi_0', 'varphi_1', 'varphi_2')\n",
171 | "phi = [] # names of angles, eg. theta\n",
172 | "phi_t = [] # angles as function of time, eg. theta(t)\n",
173 | "phi_d = [] # derivative of above, eg. d theta(t) / dt\n",
174 | "phi_n = [] # symbol to represent above, eg. theta_dot\n",
175 | "for i in phi_names:\n",
176 | " phi.append(symbols(i, real=True))\n",
177 | " phi_t.append(Function(phi[-1])(t))\n",
178 | " phi_d.append(phi_t[-1].diff(t))\n",
179 | " phi_n.append(i + '_dot')"
180 | ]
181 | },
182 | {
183 | "cell_type": "markdown",
184 | "metadata": {},
185 | "source": [
186 | "Compute the norm"
187 | ]
188 | },
189 | {
190 | "cell_type": "code",
191 | "execution_count": null,
192 | "metadata": {},
193 | "outputs": [],
194 | "source": [
195 | "theta = Matrix(phi_t).norm()\n",
196 | "theta"
197 | ]
198 | },
199 | {
200 | "cell_type": "markdown",
201 | "metadata": {},
202 | "source": [
203 | "and find its derivative"
204 | ]
205 | },
206 | {
207 | "cell_type": "code",
208 | "execution_count": null,
209 | "metadata": {},
210 | "outputs": [],
211 | "source": [
212 | "theta_dot = theta.diff(t)\n",
213 | "theta_dot"
214 | ]
215 | },
216 | {
217 | "cell_type": "markdown",
218 | "metadata": {},
219 | "source": [
220 | "and now remove the time dependenices"
221 | ]
222 | },
223 | {
224 | "cell_type": "code",
225 | "execution_count": null,
226 | "metadata": {},
227 | "outputs": [],
228 | "source": [
229 | "theta_dot = theta_dot.subs(a for a in zip(phi_d, phi_n))\n",
230 | "theta_dot = theta_dot.subs(a for a in zip(phi_t, phi))\n",
231 | "theta_dot"
232 | ]
233 | },
234 | {
235 | "cell_type": "markdown",
236 | "metadata": {},
237 | "source": [
238 | "which is simply the dot product over the norm."
239 | ]
240 | },
241 | {
242 | "cell_type": "code",
243 | "execution_count": null,
244 | "metadata": {},
245 | "outputs": [],
246 | "source": [
247 | "A, t = symbols('A t', real=True)\n",
248 | "A_t = Function(A)(t)\n",
249 | "d = diff(exp(A_t), t)\n",
250 | "print(d)"
251 | ]
252 | }
253 | ],
254 | "metadata": {
255 | "kernelspec": {
256 | "display_name": "Python 3.8.5 ('dev')",
257 | "language": "python",
258 | "name": "python3"
259 | },
260 | "language_info": {
261 | "codemirror_mode": {
262 | "name": "ipython",
263 | "version": 3
264 | },
265 | "file_extension": ".py",
266 | "mimetype": "text/x-python",
267 | "name": "python",
268 | "nbconvert_exporter": "python",
269 | "pygments_lexer": "ipython3",
270 | "version": "3.9.15"
271 | },
272 | "varInspector": {
273 | "cols": {
274 | "lenName": 16,
275 | "lenType": 16,
276 | "lenVar": 40
277 | },
278 | "kernels_config": {
279 | "python": {
280 | "delete_cmd_postfix": "",
281 | "delete_cmd_prefix": "del ",
282 | "library": "var_list.py",
283 | "varRefreshCmd": "print(var_dic_list())"
284 | },
285 | "r": {
286 | "delete_cmd_postfix": ") ",
287 | "delete_cmd_prefix": "rm(",
288 | "library": "var_list.r",
289 | "varRefreshCmd": "cat(var_dic_list()) "
290 | }
291 | },
292 | "types_to_exclude": [
293 | "module",
294 | "function",
295 | "builtin_function_or_method",
296 | "instance",
297 | "_Feature"
298 | ],
299 | "window_display": false
300 | },
301 | "vscode": {
302 | "interpreter": {
303 | "hash": "b7d6b0d76025b9176285a6442c3dd6dd39bcfe7241029b7898b7106bd5e9b472"
304 | }
305 | }
306 | },
307 | "nbformat": 4,
308 | "nbformat_minor": 4
309 | }
310 |
--------------------------------------------------------------------------------
/tests/test_geom2d.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | """
4 | Created on Sun Jul 5 14:37:24 2020
5 |
6 | @author: corkep
7 | """
8 |
9 | from spatialmath.geom2d import *
10 | from spatialmath.pose2d import SE2
11 |
12 | import unittest
13 | import pytest
14 | import sys
15 | import numpy.testing as nt
16 | import spatialmath.base as smb
17 |
18 |
19 | class Polygon2Test(unittest.TestCase):
20 | # Primitives
21 | def test_constructor1(self):
22 | p = Polygon2([(1, 2), (3, 2), (2, 4)])
23 | self.assertIsInstance(p, Polygon2)
24 | self.assertEqual(len(p), 3)
25 | self.assertEqual(str(p), "Polygon2 with 4 vertices")
26 | nt.assert_array_equal(p.vertices(), np.array([[1, 3, 2], [2, 2, 4]]))
27 | nt.assert_array_equal(
28 | p.vertices(unique=False), np.array([[1, 3, 2, 1], [2, 2, 4, 2]])
29 | )
30 |
31 | def test_methods(self):
32 | p = Polygon2(np.array([[-1, 1, 1, -1], [-1, -1, 1, 1]]))
33 |
34 | self.assertEqual(p.area(), 4)
35 | self.assertEqual(p.moment(0, 0), 4)
36 | self.assertEqual(p.moment(1, 0), 0)
37 | self.assertEqual(p.moment(0, 1), 0)
38 | nt.assert_array_equal(p.centroid(), np.r_[0, 0])
39 |
40 | self.assertEqual(p.radius(), np.sqrt(2))
41 | nt.assert_array_equal(p.bbox(), np.r_[-1, -1, 1, 1])
42 |
43 | def test_contains(self):
44 | p = Polygon2(np.array([[-1, 1, 1, -1], [-1, -1, 1, 1]]))
45 | self.assertTrue(p.contains([0, 0], radius=1e-6))
46 | self.assertTrue(p.contains([1, 0], radius=1e-6))
47 | self.assertTrue(p.contains([-1, 0], radius=1e-6))
48 | self.assertTrue(p.contains([0, 1], radius=1e-6))
49 | self.assertTrue(p.contains([0, -1], radius=1e-6))
50 |
51 | self.assertFalse(p.contains([0, 1.1], radius=1e-6))
52 | self.assertFalse(p.contains([0, -1.1], radius=1e-6))
53 | self.assertFalse(p.contains([1.1, 0], radius=1e-6))
54 | self.assertFalse(p.contains([-1.1, 0], radius=1e-6))
55 |
56 | self.assertTrue(p.contains(np.r_[0, -1], radius=1e-6))
57 | self.assertFalse(p.contains(np.r_[0, 1.1], radius=1e-6))
58 |
59 | def test_transform(self):
60 | p = Polygon2(np.array([[-1, 1, 1, -1], [-1, -1, 1, 1]]))
61 |
62 | p = p.transformed(SE2(2, 3))
63 |
64 | self.assertEqual(p.area(), 4)
65 | self.assertEqual(p.moment(0, 0), 4)
66 | self.assertEqual(p.moment(1, 0), 8)
67 | self.assertEqual(p.moment(0, 1), 12)
68 | nt.assert_array_equal(p.centroid(), np.r_[2, 3])
69 |
70 | def test_intersect(self):
71 | p1 = Polygon2(np.array([[-1, 1, 1, -1], [-1, -1, 1, 1]]))
72 |
73 | p2 = p1.transformed(SE2(2, 3))
74 | self.assertFalse(p1.intersects(p2))
75 |
76 | p2 = p1.transformed(SE2(1, 1))
77 | self.assertTrue(p1.intersects(p2))
78 |
79 | self.assertTrue(p1.intersects(p1))
80 |
81 | def test_intersect_line(self):
82 | p = Polygon2(np.array([[-1, 1, 1, -1], [-1, -1, 1, 1]]))
83 |
84 | l = Line2.Join((-10, 0), (10, 0))
85 | self.assertTrue(p.intersects(l))
86 |
87 | l = Line2.Join((-10, 1.1), (10, 1.1))
88 | self.assertFalse(p.intersects(l))
89 |
90 | @pytest.mark.skipif(
91 | sys.platform.startswith("darwin") and sys.version_info < (3, 11),
92 | reason="tkinter bug with mac",
93 | )
94 | def test_plot(self):
95 | p = Polygon2(np.array([[-1, 1, 1, -1], [-1, -1, 1, 1]]))
96 | p.plot()
97 |
98 | p.animate(SE2(1, 2))
99 |
100 | def test_edges(self):
101 | p = Polygon2([(1, 2), (3, 2), (2, 4)])
102 | e = p.edges()
103 |
104 | e = list(e)
105 | nt.assert_equal(e[0], ((1, 2), (3, 2)))
106 | nt.assert_equal(e[1], ((3, 2), (2, 4)))
107 | nt.assert_equal(e[2], ((2, 4), (1, 2)))
108 |
109 | # p.move(SE2(0, 0, 0.7))
110 |
111 |
112 | class Line2Test(unittest.TestCase):
113 | def test_constructor(self):
114 | l = Line2([1, 2, 3])
115 | self.assertEqual(str(l), "Line2: [1. 2. 3.]")
116 |
117 | l = Line2.Join((0, 0), (1, 2))
118 | nt.assert_equal(l.line, [-2, 1, 0])
119 |
120 | l = Line2.General(2, 1)
121 | nt.assert_equal(l.line, [2, -1, 1])
122 |
123 | def test_contains(self):
124 | l = Line2.Join((0, 0), (1, 2))
125 |
126 | self.assertTrue(l.contains((0, 0)))
127 | self.assertTrue(l.contains((1, 2)))
128 | self.assertTrue(l.contains((2, 4)))
129 |
130 | def test_intersect(self):
131 | l1 = Line2.Join((0, 0), (2, 0)) # y = 0
132 | l2 = Line2.Join((0, 1), (2, 1)) # y = 1
133 | self.assertFalse(l1.intersect(l2))
134 |
135 | l2 = Line2.Join((2, 1), (2, -1)) # x = 2
136 | self.assertTrue(l1.intersect(l2))
137 |
138 | def test_intersect_segment(self):
139 | l1 = Line2.Join((0, 0), (2, 0)) # y = 0
140 | self.assertFalse(l1.intersect_segment((2, 1), (2, 3)))
141 | self.assertTrue(l1.intersect_segment((2, 1), (2, -1)))
142 |
143 |
144 | class EllipseTest(unittest.TestCase):
145 | def test_constructor(self):
146 | E = np.array([[1, 1], [1, 3]])
147 | e = Ellipse(E=E)
148 | nt.assert_almost_equal(e.E, E)
149 | nt.assert_almost_equal(e.centre, [0, 0])
150 | self.assertAlmostEqual(e.theta, 1.1780972450961724)
151 |
152 | e = Ellipse(radii=(1, 2), theta=0)
153 | nt.assert_almost_equal(e.E, np.diag([1, 0.25]))
154 | nt.assert_almost_equal(e.centre, [0, 0])
155 | nt.assert_almost_equal(e.radii, [1, 2])
156 | self.assertAlmostEqual(e.theta, 0)
157 |
158 | e = Ellipse(radii=(1, 2), theta=np.pi / 2)
159 | nt.assert_almost_equal(e.E, np.diag([0.25, 1]))
160 | nt.assert_almost_equal(e.centre, [0, 0])
161 | nt.assert_almost_equal(e.radii, [2, 1])
162 | self.assertAlmostEqual(e.theta, np.pi / 2)
163 |
164 | E = np.array([[1, 1], [1, 3]])
165 | e = Ellipse(E=E, centre=[3, 4])
166 | nt.assert_almost_equal(e.E, E)
167 | nt.assert_almost_equal(e.centre, [3, 4])
168 | self.assertAlmostEqual(e.theta, 1.1780972450961724)
169 |
170 | e = Ellipse(radii=(1, 2), theta=0, centre=[3, 4])
171 | nt.assert_almost_equal(e.E, np.diag([1, 0.25]))
172 | nt.assert_almost_equal(e.centre, [3, 4])
173 | nt.assert_almost_equal(e.radii, [1, 2])
174 | self.assertAlmostEqual(e.theta, 0)
175 |
176 | def test_Polynomial(self):
177 | e = Ellipse.Polynomial([2, 3, 1, 0, 0, -1])
178 | nt.assert_almost_equal(e.E, np.array([[2, 0.5], [0.5, 3]]))
179 | nt.assert_almost_equal(e.centre, [0, 0])
180 |
181 | def test_FromPerimeter(self):
182 | eref = Ellipse(radii=(1, 2), theta=0, centre=[0, 0])
183 | p = eref.points()
184 |
185 | e = Ellipse.FromPerimeter(p)
186 | nt.assert_almost_equal(e.radii, eref.radii)
187 | nt.assert_almost_equal(e.centre, eref.centre)
188 | nt.assert_almost_equal(e.theta, eref.theta)
189 |
190 | ##
191 | eref = Ellipse(radii=(1, 2), theta=0, centre=[3, 4])
192 | p = eref.points()
193 |
194 | e = Ellipse.FromPerimeter(p)
195 | nt.assert_almost_equal(e.radii, eref.radii)
196 | nt.assert_almost_equal(e.centre, eref.centre)
197 | nt.assert_almost_equal(e.theta, eref.theta)
198 |
199 | ##
200 | eref = Ellipse(radii=(1, 2), theta=np.pi / 4, centre=[3, 4])
201 | p = eref.points()
202 |
203 | e = Ellipse.FromPerimeter(p)
204 | nt.assert_almost_equal(e.radii, eref.radii)
205 | nt.assert_almost_equal(e.centre, eref.centre)
206 | nt.assert_almost_equal(e.theta, eref.theta)
207 |
208 | def test_FromPoints(self):
209 | eref = Ellipse(radii=(1, 2), theta=np.pi / 2, centre=(3, 4))
210 | rng = np.random.default_rng(0)
211 |
212 | # create 200 random points inside the ellipse
213 | x = []
214 | while len(x) < 200:
215 | p = rng.uniform(low=1, high=6, size=(2, 1))
216 | if eref.contains(p):
217 | x.append(p)
218 | x = np.hstack(x) # create 2 x 50 array
219 |
220 | e = Ellipse.FromPoints(x)
221 | nt.assert_almost_equal(e.radii, eref.radii, decimal=1)
222 | nt.assert_almost_equal(e.centre, eref.centre, decimal=1)
223 | nt.assert_almost_equal(e.theta, eref.theta, decimal=1)
224 |
225 | def test_misc(self):
226 | e = Ellipse(radii=(1, 2), theta=np.pi / 2)
227 | self.assertIsInstance(str(e), str)
228 |
229 | self.assertAlmostEqual(e.area, np.pi * 2)
230 |
231 | e = Ellipse(radii=(1, 2), theta=0)
232 | self.assertTrue(e.contains((0, 0)))
233 | self.assertTrue(e.contains((1, 0)))
234 | self.assertTrue(e.contains((-1, 0)))
235 | self.assertTrue(e.contains((0, 2)))
236 | self.assertTrue(e.contains((0, -2)))
237 |
238 | self.assertFalse(e.contains((1.1, 0)))
239 | self.assertFalse(e.contains((-1.1, 0)))
240 | self.assertFalse(e.contains((0, 2.1)))
241 | self.assertFalse(e.contains((0, -2.1)))
242 |
243 | self.assertEqual(e.contains(np.array([[0, 0], [3, 3]]).T), [True, False])
244 |
245 |
246 | if __name__ == "__main__":
247 | unittest.main()
248 |
--------------------------------------------------------------------------------
/spatialmath/timing.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding", t)
3 | """
4 | Created on Fri Apr 10 14:22:36 2020
5 |
6 | @author", t)
7 | """
8 |
9 |
10 | import timeit
11 | from ansitable import ANSITable, Column
12 |
13 | N = 100000
14 |
15 | table = ANSITable(
16 | Column("Operation", headalign="^"),
17 | Column("Time (μs)", headalign="^", fmt="{:.2f}"),
18 | border="thick",
19 | )
20 |
21 |
22 | def result(op, t):
23 | global table
24 |
25 | table.row(op, t / N * 1e6)
26 |
27 |
28 | # ------------------------------------------------------------------------- #
29 |
30 | # transforms_setup = '''
31 | # from spatialmath import SE3
32 | # from spatialmath import base
33 |
34 | # import numpy as np
35 | # from collections import namedtuple
36 | # Rt = namedtuple('Rt', 'R t')
37 | # X1 = SE3.Rand()
38 | # X2 = SE3.Rand()
39 | # T1 = X1.A
40 | # T2 = X2.A
41 | # R1 = base.t2r(T1)
42 | # R2 = base.t2r(T2)
43 | # t1 = base.transl(T1)
44 | # t2 = base.transl(T2)
45 | # Rt1 = Rt(R1, t1)
46 | # Rt2 = Rt(R2, t2)
47 | # v = np.r_[1,2,3]
48 | # v2 = np.r_[1,2,3, 1]
49 | # '''
50 | # t = timeit.timeit(stmt='base.getvector(0.2)', setup=transforms_setup, number=N)
51 | # result("getvector(x)", t)
52 |
53 | # t = timeit.timeit(stmt='base.rotx(0.2, unit="rad")', setup=transforms_setup, number=N)
54 | # result("base.rotx", t)
55 |
56 | # t = timeit.timeit(stmt='base.trotx(0.2, unit="rad")', setup=transforms_setup, number=N)
57 | # result("base.trotx", t)
58 |
59 | # t = timeit.timeit(stmt='base.t2r(T1)', setup=transforms_setup, number=N)
60 | # result("base.t2r", t)
61 |
62 | # t = timeit.timeit(stmt='base.r2t(R1)', setup=transforms_setup, number=N)
63 | # result("base.r2t", t)
64 |
65 | # t = timeit.timeit(stmt='T1 @ T2', setup=transforms_setup, number=N)
66 | # result("4x4 @", t)
67 |
68 | # t = timeit.timeit(stmt='T1[:3,:3] @ T2[:3,:3] + T1[:3,:3] @ T2[:3,3]', setup=transforms_setup, number=N)
69 | # result("R1*R2, R1*t", t)
70 |
71 | # t = timeit.timeit(stmt='(Rt1.R @ Rt2.R, Rt1.R @ Rt2.t)', setup=transforms_setup, number=N)
72 | # result("T1 * T2 (R, t)", t)
73 |
74 | # t = timeit.timeit(stmt='base.trinv(T1)', setup=transforms_setup, number=N)
75 | # result("base.trinv", t)
76 |
77 | # t = timeit.timeit(stmt='(Rt1.R.T, -Rt1.R.T @ Rt1.t)', setup=transforms_setup, number=N)
78 | # result("base.trinv (R,t)", t)
79 |
80 | # t = timeit.timeit(stmt='np.linalg.inv(T1)', setup=transforms_setup, number=N)
81 | # result("np.linalg.inv", t)
82 |
83 | # t = timeit.timeit(stmt='T1 @ v2', setup=transforms_setup, number=N)
84 | # result("(4,4) * (4,)", t)
85 |
86 | # # ------------------------------------------------------------------------- #
87 | # table.rule()
88 |
89 | # t = timeit.timeit(stmt='SE3()', setup=transforms_setup, number=N)
90 | # result("SE3()", t)
91 |
92 | # t = timeit.timeit(stmt='SE3.Rx(0.2)', setup=transforms_setup, number=N)
93 | # result("SE3.Rx()", t)
94 |
95 | # t = timeit.timeit(stmt='T1[:3,:3]', setup=transforms_setup, number=N)
96 | # result("T1[:3,:3]", t)
97 |
98 | # t = timeit.timeit(stmt='X1.A', setup=transforms_setup, number=N)
99 | # result("SE3.A", t)
100 |
101 | # t = timeit.timeit(stmt='SE3(T1)', setup=transforms_setup, number=N)
102 | # result("SE3(T1)", t)
103 |
104 | # t = timeit.timeit(stmt='SE3(T1, check=False)', setup=transforms_setup, number=N)
105 | # result("SE3(T1 check=False)", t)
106 |
107 | # t = timeit.timeit(stmt='SE3([T1], check=False)', setup=transforms_setup, number=N)
108 | # result("SE3([T1])", t)
109 |
110 | # t = timeit.timeit(stmt='X1 * X2', setup=transforms_setup, number=N)
111 | # result("SE3 * SE3", t)
112 |
113 | # t = timeit.timeit(stmt='X1.inv()', setup=transforms_setup, number=N)
114 | # result("SE3.inv", t)
115 |
116 | # t = timeit.timeit(stmt='X1 * v', setup=transforms_setup, number=N)
117 | # result("SE3 * v", t)
118 |
119 | # t = timeit.timeit(stmt='a = X1.log()', setup=transforms_setup, number=N)
120 | # result("SE3.log()", t)
121 |
122 | # # ------------------------------------------------------------------------- #
123 | # quat_setup = '''
124 | # from spatialmath import base
125 | # from spatialmath import UnitQuaternion
126 | # import numpy as np
127 | # q1 = base.rand()
128 | # q2 = base.rand()
129 | # v = np.r_[1,2,3]
130 | # Q1 = UnitQuaternion.Rx(0.2)
131 | # Q2 = UnitQuaternion.Ry(0.3)
132 | # '''
133 | # table.rule()
134 |
135 | # t = timeit.timeit(stmt='a = UnitQuaternion()', setup=quat_setup, number=N)
136 | # result("UnitQuaternion() ", t)
137 |
138 | # t = timeit.timeit(stmt='a = UnitQuaternion.Rx(0.2)', setup=quat_setup, number=N)
139 | # result("UnitQuaternion.Rx ", t)
140 |
141 | # t = timeit.timeit(stmt='a = Q1 * Q2', setup=quat_setup, number=N)
142 | # result("UnitQuaternion * UnitQuaternion", t)
143 |
144 | # t = timeit.timeit(stmt='a = Q1 * v', setup=quat_setup, number=N)
145 | # result("UnitQuaternion * v", t)
146 |
147 | # t = timeit.timeit(stmt='a = base.qqmul(q1,q2)', setup=quat_setup, number=N)
148 | # result("base.qqmul", t)
149 |
150 | # t = timeit.timeit(stmt='a = base.qvmul(q1,v)', setup=quat_setup, number=N)
151 | # result("base.qvmul", t)
152 |
153 |
154 | # # ------------------------------------------------------------------------- #
155 | # twist_setup = '''
156 | # from spatialmath import SE3, Twist3
157 | # from spatialmath import base
158 | # import numpy as np
159 | # from math import cos
160 | # S1 = SE3.Rand().Twist3()
161 | # S2 = SE3.Rand().Twist3()
162 | # X1 = SE3.Rand()
163 | # T1 = X1.A
164 | # A1 = X1.Ad()
165 | # se3 = S1.se3()
166 | # s = np.r_[1,2,3,4,5,6]
167 | # v = np.r_[1,2,3]
168 | # '''
169 | # table.rule()
170 | # t = timeit.timeit(stmt='a = Twist3()', setup=twist_setup, number=N)
171 | # result("Twist3()", t)
172 |
173 | # t = timeit.timeit(stmt='a = X1.Twist3()', setup=twist_setup, number=N)
174 | # result("SE3.Twist3()", t)
175 |
176 | # t = timeit.timeit(stmt='a = S1 * S2', setup=twist_setup, number=N)
177 | # result("Twist3 * Twist3", t)
178 |
179 | # t = timeit.timeit(stmt='a = S1.inv()', setup=twist_setup, number=N)
180 | # result("Twist3.inv()", t)
181 |
182 | # t = timeit.timeit(stmt='a = S1.Ad()', setup=twist_setup, number=N)
183 | # result("Twist3.Ad()", t)
184 |
185 | # t = timeit.timeit(stmt='a = S1.exp(1)', setup=twist_setup, number=N)
186 | # result("Twist3.Exp()", t)
187 |
188 | # t = timeit.timeit(stmt='a = base.skewa(v)', setup=twist_setup, number=N)
189 | # result("skew", t)
190 |
191 | # t = timeit.timeit(stmt='a = base.skewa(s)', setup=twist_setup, number=N)
192 | # result("skewa", t)
193 |
194 | # t = timeit.timeit(stmt='a = base.vexa(se3)', setup=twist_setup, number=N)
195 | # result("vexa", t)
196 |
197 | # t = timeit.timeit(stmt='a = base.trlog(T1)', setup=twist_setup, number=N)
198 | # result("trlog", t)
199 |
200 | # t = timeit.timeit(stmt='a = base.trlog(T1, twist=True)', setup=twist_setup, number=N)
201 | # result("trlog as twist", t)
202 |
203 | # t = timeit.timeit(stmt='a = base.trexp(se3)', setup=twist_setup, number=N)
204 | # result("trexp", t)
205 |
206 | # t = timeit.timeit(stmt='a = A1 @ s', setup=twist_setup, number=N)
207 | # result("(6,6) * (6,)", t)
208 |
209 | # t = timeit.timeit(stmt='a = base.rodrigues(v)', setup=twist_setup, number=N)
210 | # result("rodrigues", t)
211 |
212 | # t = timeit.timeit(stmt='a = cos(0.3)', setup=twist_setup, number=N)
213 | # result("math.cos", t)
214 |
215 | # t = timeit.timeit(stmt='a = np.cos(0.3)', setup=twist_setup, number=N)
216 | # result("np.cos", t)
217 |
218 | # ------------------------------------------------------------------------- #
219 | misc_setup = """
220 | from spatialmath import base
221 | import numpy as np
222 | s = np.r_[1.0,2,3,4,5,6]
223 | s3 = np.r_[1.0,2,3]
224 | a = np.r_[1.0, 2.0, 3.0]
225 | b = np.r_[-5.0, 4.0, 3.0]
226 |
227 | A = np.random.randn(6,6)
228 | As = (A + A.T) / 2
229 | bb = np.random.randn(6)
230 | """
231 | table.rule()
232 |
233 | t = timeit.timeit(stmt="c = np.linalg.inv(As)", setup=misc_setup, number=N)
234 | result("np.inv(As)", t)
235 |
236 | t = timeit.timeit(stmt="c = np.linalg.pinv(As)", setup=misc_setup, number=N)
237 | result("np.pinv(As)", t)
238 |
239 | t = timeit.timeit(stmt="c = np.linalg.solve(As, bb)", setup=misc_setup, number=N)
240 | result("np.solve(As, b)", t)
241 |
242 | t = timeit.timeit(stmt="c = np.cross(a,b)", setup=misc_setup, number=N)
243 | result("np.cross()", t)
244 |
245 | t = timeit.timeit(stmt="c = base.cross(a,b)", setup=misc_setup, number=N)
246 | result("cross()", t)
247 |
248 | t = timeit.timeit(stmt="a = np.inner(s,s).sum()", setup=misc_setup, number=N)
249 | result("inner()", t)
250 |
251 | t = timeit.timeit(stmt="a = np.linalg.norm(s) ** 2", setup=misc_setup, number=N)
252 | result("np.norm**2", t)
253 |
254 | t = timeit.timeit(stmt="a = base.normsq(s)", setup=misc_setup, number=N)
255 | result("base.normsq", t)
256 |
257 | t = timeit.timeit(stmt="a = (s ** 2).sum()", setup=misc_setup, number=N)
258 | result("s**2.sum()", t)
259 |
260 | t = timeit.timeit(stmt="a = np.sum(s ** 2)", setup=misc_setup, number=N)
261 | result("np.sum(s ** 2)", t)
262 |
263 | t = timeit.timeit(stmt="a = np.linalg.norm(s)", setup=misc_setup, number=N)
264 | result("np.norm(R6)", t)
265 | t = timeit.timeit(stmt="a = base.norm(s)", setup=misc_setup, number=N)
266 | result("base.norm(R6)", t)
267 |
268 | t = timeit.timeit(stmt="a = np.linalg.norm(s3)", setup=misc_setup, number=N)
269 | result("np.norm(R3)", t)
270 | t = timeit.timeit(stmt="a = base.norm(s3)", setup=misc_setup, number=N)
271 | result("base.norm(R3)", t)
272 |
273 |
274 | table.print()
275 |
--------------------------------------------------------------------------------
/tests/base/test_quaternions.py:
--------------------------------------------------------------------------------
1 | # This file is part of the SpatialMath toolbox for Python
2 | # https://github.com/petercorke/spatialmath-python
3 | #
4 | # MIT License
5 | #
6 | # Copyright (c) 1993-2020 Peter Corke
7 | #
8 | # Permission is hereby granted, free of charge, to any person obtaining a copy
9 | # of this software and associated documentation files (the "Software"), to deal
10 | # in the Software without restriction, including without limitation the rights
11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | # copies of the Software, and to permit persons to whom the Software is
13 | # furnished to do so, subject to the following conditions:
14 | #
15 | # The above copyright notice and this permission notice shall be included in all
16 | # copies or substantial portions of the Software.
17 | #
18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | # SOFTWARE.
25 |
26 | # Contributors:
27 | #
28 | # 1. Luis Fernando Lara Tobar and Peter Corke, 2008
29 | # 2. Josh Carrigg Hodson, Aditya Dua, Chee Ho Chan, 2017 (robopy)
30 | # 3. Peter Corke, 2020
31 |
32 | import numpy.testing as nt
33 | import unittest
34 |
35 | from spatialmath.base.vectors import *
36 | import spatialmath.base as tr
37 | from spatialmath.base.quaternions import *
38 | import spatialmath as sm
39 | import io
40 |
41 |
42 | class TestQuaternion(unittest.TestCase):
43 | def test_ops(self):
44 | nt.assert_array_almost_equal(qeye(), np.r_[1, 0, 0, 0])
45 |
46 | nt.assert_array_almost_equal(qpure(np.r_[1, 2, 3]), np.r_[0, 1, 2, 3])
47 | nt.assert_array_almost_equal(qpure([1, 2, 3]), np.r_[0, 1, 2, 3])
48 | nt.assert_array_almost_equal(qpure((1, 2, 3)), np.r_[0, 1, 2, 3])
49 |
50 | nt.assert_equal(qnorm(np.r_[1, 2, 3, 4]), math.sqrt(30))
51 | nt.assert_equal(qnorm([1, 2, 3, 4]), math.sqrt(30))
52 | nt.assert_equal(qnorm((1, 2, 3, 4)), math.sqrt(30))
53 |
54 | nt.assert_array_almost_equal(
55 | qunit(np.r_[1, 2, 3, 4]), np.r_[1, 2, 3, 4] / math.sqrt(30)
56 | )
57 | nt.assert_array_almost_equal(
58 | qunit([1, 2, 3, 4]), np.r_[1, 2, 3, 4] / math.sqrt(30)
59 | )
60 |
61 | nt.assert_array_almost_equal(
62 | qqmul(np.r_[1, 2, 3, 4], np.r_[5, 6, 7, 8]), np.r_[-60, 12, 30, 24]
63 | )
64 | nt.assert_array_almost_equal(
65 | qqmul([1, 2, 3, 4], [5, 6, 7, 8]), np.r_[-60, 12, 30, 24]
66 | )
67 | nt.assert_array_almost_equal(
68 | qqmul(np.r_[1, 2, 3, 4], np.r_[1, 2, 3, 4]), np.r_[-28, 4, 6, 8]
69 | )
70 |
71 | nt.assert_array_almost_equal(
72 | qmatrix(np.r_[1, 2, 3, 4]) @ np.r_[5, 6, 7, 8], np.r_[-60, 12, 30, 24]
73 | )
74 | nt.assert_array_almost_equal(
75 | qmatrix([1, 2, 3, 4]) @ np.r_[5, 6, 7, 8], np.r_[-60, 12, 30, 24]
76 | )
77 | nt.assert_array_almost_equal(
78 | qmatrix(np.r_[1, 2, 3, 4]) @ np.r_[1, 2, 3, 4], np.r_[-28, 4, 6, 8]
79 | )
80 |
81 | nt.assert_array_almost_equal(qpow(np.r_[1, 2, 3, 4], 0), np.r_[1, 0, 0, 0])
82 | nt.assert_array_almost_equal(qpow(np.r_[1, 2, 3, 4], 1), np.r_[1, 2, 3, 4])
83 | nt.assert_array_almost_equal(qpow([1, 2, 3, 4], 1), np.r_[1, 2, 3, 4])
84 | nt.assert_array_almost_equal(qpow(np.r_[1, 2, 3, 4], 2), np.r_[-28, 4, 6, 8])
85 | nt.assert_array_almost_equal(qpow(np.r_[1, 2, 3, 4], -1), np.r_[1, -2, -3, -4])
86 | nt.assert_array_almost_equal(
87 | qpow(np.r_[1, 2, 3, 4], -2), np.r_[-28, -4, -6, -8]
88 | )
89 |
90 | nt.assert_equal(qisequal(np.r_[1, 2, 3, 4], np.r_[1, 2, 3, 4]), True)
91 | nt.assert_equal(qisequal(np.r_[1, 2, 3, 4], np.r_[5, 6, 7, 8]), False)
92 | nt.assert_equal(
93 | qisequal(
94 | np.r_[1, 1, 0, 0] / math.sqrt(2),
95 | np.r_[-1, -1, 0, 0] / math.sqrt(2),
96 | unitq=True,
97 | ),
98 | True,
99 | )
100 | nt.assert_equal(isunitvec(qrand()), True)
101 |
102 | def test_display(self):
103 | s = q2str(np.r_[1, 2, 3, 4])
104 | nt.assert_equal(isinstance(s, str), True)
105 | nt.assert_equal(s, " 1.0000 < 2.0000, 3.0000, 4.0000 >")
106 |
107 | s = q2str([1, 2, 3, 4])
108 | nt.assert_equal(s, " 1.0000 < 2.0000, 3.0000, 4.0000 >")
109 |
110 | s = q2str([1, 2, 3, 4], delim=("<<", ">>"))
111 | nt.assert_equal(s, " 1.0000 << 2.0000, 3.0000, 4.0000 >>")
112 |
113 | s = q2str([1, 2, 3, 4], fmt="{:20.6f}")
114 | nt.assert_equal(
115 | s,
116 | " 1.000000 < 2.000000, 3.000000, 4.000000 >",
117 | )
118 |
119 | # would be nicer to do this with redirect_stdout() from contextlib but that
120 | # fails because file=sys.stdout is maybe assigned at compile time, so when
121 | # contextlib changes sys.stdout, qprint() doesn't see it
122 |
123 | f = io.StringIO()
124 | qprint(np.r_[1, 2, 3, 4], file=f)
125 | nt.assert_equal(f.getvalue().rstrip(), " 1.0000 < 2.0000, 3.0000, 4.0000 >")
126 |
127 | def test_rotation(self):
128 | # rotation matrix to quaternion
129 | nt.assert_array_almost_equal(r2q(tr.rotx(180, "deg")), np.r_[0, 1, 0, 0])
130 | nt.assert_array_almost_equal(r2q(tr.roty(180, "deg")), np.r_[0, 0, 1, 0])
131 | nt.assert_array_almost_equal(r2q(tr.rotz(180, "deg")), np.r_[0, 0, 0, 1])
132 |
133 | # quaternion to rotation matrix
134 | nt.assert_array_almost_equal(q2r(np.r_[0, 1, 0, 0]), tr.rotx(180, "deg"))
135 | nt.assert_array_almost_equal(q2r(np.r_[0, 0, 1, 0]), tr.roty(180, "deg"))
136 | nt.assert_array_almost_equal(q2r(np.r_[0, 0, 0, 1]), tr.rotz(180, "deg"))
137 |
138 | nt.assert_array_almost_equal(q2r([0, 1, 0, 0]), tr.rotx(180, "deg"))
139 | nt.assert_array_almost_equal(q2r([0, 0, 1, 0]), tr.roty(180, "deg"))
140 | nt.assert_array_almost_equal(q2r([0, 0, 0, 1]), tr.rotz(180, "deg"))
141 |
142 | # quaternion - vector product
143 | nt.assert_array_almost_equal(
144 | qvmul(np.r_[0, 1, 0, 0], np.r_[0, 0, 1]), np.r_[0, 0, -1]
145 | )
146 | nt.assert_array_almost_equal(qvmul([0, 1, 0, 0], [0, 0, 1]), np.r_[0, 0, -1])
147 |
148 | large_rotation = math.pi + 0.01
149 | q1 = r2q(tr.rotx(large_rotation), shortest=False)
150 | q2 = r2q(tr.rotx(large_rotation), shortest=True)
151 | self.assertLess(q1[0], 0)
152 | self.assertGreater(q2[0], 0)
153 | self.assertTrue(qisequal(q1=q1, q2=q2, unitq=True))
154 |
155 | def test_slerp(self):
156 | q1 = np.r_[0, 1, 0, 0]
157 | q2 = np.r_[0, 0, 1, 0]
158 |
159 | nt.assert_array_almost_equal(qslerp(q1, q2, 0), q1)
160 | nt.assert_array_almost_equal(qslerp(q1, q2, 1), q2)
161 | nt.assert_array_almost_equal(
162 | qslerp(q1, q2, 0.5), np.r_[0, 1, 1, 0] / math.sqrt(2)
163 | )
164 |
165 | q1 = [0, 1, 0, 0]
166 | q2 = [0, 0, 1, 0]
167 |
168 | nt.assert_array_almost_equal(qslerp(q1, q2, 0), q1)
169 | nt.assert_array_almost_equal(qslerp(q1, q2, 1), q2)
170 | nt.assert_array_almost_equal(
171 | qslerp(q1, q2, 0.5), np.r_[0, 1, 1, 0] / math.sqrt(2)
172 | )
173 |
174 | nt.assert_array_almost_equal(
175 | qslerp(r2q(tr.rotx(-0.3)), r2q(tr.rotx(0.3)), 0.5), np.r_[1, 0, 0, 0]
176 | )
177 | nt.assert_array_almost_equal(
178 | qslerp(r2q(tr.roty(0.3)), r2q(tr.roty(0.5)), 0.5), r2q(tr.roty(0.4))
179 | )
180 |
181 | def test_rotx(self):
182 | pass
183 |
184 | def test_r2q(self):
185 | # null rotation case
186 | R = np.eye(3)
187 | nt.assert_array_almost_equal(r2q(R), [1, 0, 0, 0])
188 |
189 | R = tr.rotx(np.pi / 2)
190 | nt.assert_array_almost_equal(r2q(R), np.r_[1, 1, 0, 0] / np.sqrt(2))
191 |
192 | R = tr.rotx(-np.pi / 2)
193 | nt.assert_array_almost_equal(r2q(R), np.r_[1, -1, 0, 0] / np.sqrt(2))
194 |
195 | R = tr.rotx(np.pi)
196 | nt.assert_array_almost_equal(r2q(R), np.r_[0, 1, 0, 0])
197 |
198 | R = tr.rotx(-np.pi)
199 | nt.assert_array_almost_equal(r2q(R), np.r_[0, 1, 0, 0])
200 |
201 | # ry
202 | R = tr.roty(np.pi / 2)
203 | nt.assert_array_almost_equal(r2q(R), np.r_[1, 0, 1, 0] / np.sqrt(2))
204 |
205 | R = tr.roty(-np.pi / 2)
206 | nt.assert_array_almost_equal(r2q(R), np.r_[1, 0, -1, 0] / np.sqrt(2))
207 |
208 | R = tr.roty(np.pi)
209 | nt.assert_array_almost_equal(r2q(R), np.r_[0, 0, 1, 0])
210 |
211 | R = tr.roty(-np.pi)
212 | nt.assert_array_almost_equal(r2q(R), np.r_[0, 0, 1, 0])
213 |
214 | # rz
215 | R = tr.rotz(np.pi / 2)
216 | nt.assert_array_almost_equal(r2q(R), np.r_[1, 0, 0, 1] / np.sqrt(2))
217 |
218 | R = tr.rotz(-np.pi / 2)
219 | nt.assert_array_almost_equal(r2q(R), np.r_[1, 0, 0, -1] / np.sqrt(2))
220 |
221 | R = tr.rotz(np.pi)
222 | nt.assert_array_almost_equal(r2q(R), np.r_[0, 0, 0, 1])
223 |
224 | R = tr.rotz(-np.pi)
225 | nt.assert_array_almost_equal(r2q(R), np.r_[0, 0, 0, 1])
226 |
227 | # github issue case
228 | R = np.array([[0, -1, 0], [-1, 0, 0], [0, 0, -1]])
229 | nt.assert_array_almost_equal(r2q(R), np.r_[0, 1, -1, 0] / np.sqrt(2))
230 |
231 | r1 = sm.SE3.Rx(0.1)
232 | q1a = np.array([9.987503e-01, 4.997917e-02, 0.000000e00, 2.775558e-17])
233 | q1b = np.array([4.997917e-02, 0.000000e00, 2.775558e-17, 9.987503e-01])
234 |
235 | nt.assert_array_almost_equal(q1a, r2q(r1.R))
236 | nt.assert_array_almost_equal(q1a, r2q(r1.R, order="sxyz"))
237 | nt.assert_array_almost_equal(q1b, r2q(r1.R, order="xyzs"))
238 |
239 | with self.assertRaises(ValueError):
240 | nt.assert_array_almost_equal(q1a, r2q(r1.R, order="aaa"))
241 |
242 | def test_qangle(self):
243 | # Test function that calculates angle between quaternions
244 | q1 = [1.0, 0, 0, 0]
245 | q2 = [1 / np.sqrt(2), 0, 1 / np.sqrt(2), 0] # 90deg rotation about y-axis
246 | nt.assert_almost_equal(qangle(q1, q2), np.pi / 2)
247 |
248 | q1 = [1.0, 0, 0, 0]
249 | q2 = [1 / np.sqrt(2), 1 / np.sqrt(2), 0, 0] # 90deg rotation about x-axis
250 | nt.assert_almost_equal(qangle(q1, q2), np.pi / 2)
251 |
252 |
253 | if __name__ == "__main__":
254 | unittest.main()
255 |
--------------------------------------------------------------------------------
/tests/test_geom3d.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | """
4 | Created on Sun Jul 5 14:37:24 2020
5 |
6 | @author: corkep
7 | """
8 |
9 | from spatialmath.geom3d import *
10 | from spatialmath.pose3d import SE3
11 |
12 | import unittest
13 | import numpy.testing as nt
14 | import spatialmath.base as base
15 | import pytest
16 | import sys
17 |
18 |
19 | class Line3Test(unittest.TestCase):
20 | # Primitives
21 | def test_constructor1(self):
22 | # construct from 6-vector
23 |
24 | with self.assertRaises(ValueError):
25 | L = Line3([1, 2, 3, 4, 5, 6], check=True)
26 |
27 | L = Line3([1, 2, 3, 4, 5, 6], check=False)
28 | self.assertIsInstance(L, Line3)
29 | nt.assert_array_almost_equal(L.v, np.r_[1, 2, 3])
30 | nt.assert_array_almost_equal(L.w, np.r_[4, 5, 6])
31 |
32 | # construct from object
33 | L2 = Line3(L, check=False)
34 | self.assertIsInstance(L, Line3)
35 | nt.assert_array_almost_equal(L2.v, np.r_[1, 2, 3])
36 | nt.assert_array_almost_equal(L2.w, np.r_[4, 5, 6])
37 |
38 | # construct from point and direction
39 | L = Line3.PointDir([1, 2, 3], [4, 5, 6])
40 | self.assertTrue(L.contains([1, 2, 3]))
41 | nt.assert_array_almost_equal(L.uw, base.unitvec([4, 5, 6]))
42 |
43 | def test_vec(self):
44 | # verify double
45 | L = Line3([1, 2, 3, 4, 5, 6], check=False)
46 | nt.assert_array_almost_equal(L.vec, np.r_[1, 2, 3, 4, 5, 6])
47 |
48 | def test_constructor2(self):
49 | # 2, point constructor
50 | P = np.r_[2, 3, 7]
51 | Q = np.r_[2, 1, 0]
52 | L = Line3.Join(P, Q)
53 | nt.assert_array_almost_equal(L.w, P - Q)
54 | nt.assert_array_almost_equal(L.v, np.cross(P - Q, Q))
55 |
56 | # TODO, all combos of list and ndarray
57 | # test all possible input shapes
58 | # L2, = Line3(P, Q)
59 | # self.assertEqual(double(L2), double(L))
60 | # L2, = Line3(P, Q')
61 | # self.assertEqual(double(L2), double(L))
62 | # L2, = Line3(P', Q')
63 | # self.assertEqual(double(L2), double(L))
64 | # L2, = Line3(P, Q)
65 | # self.assertEqual(double(L2), double(L))
66 |
67 | # # planes constructor
68 | # P = [10, 11, 12]'; w = [1, 2, 3]
69 | # L = Line3.PointDir(P, w)
70 | # self.assertEqual(double(L), [cross(w,P) w]'); %FAIL
71 | # L2, = Line3.PointDir(P', w)
72 | # self.assertEqual(double(L2), double(L))
73 | # L2, = Line3.PointDir(P, w')
74 | # self.assertEqual(double(L2), double(L))
75 | # L2, = Line3.PointDir(P', w')
76 | # self.assertEqual(double(L2), double(L))
77 |
78 | def test_pp(self):
79 | # validate pp and ppd
80 | L = Line3.Join([-1, 1, 2], [1, 1, 2])
81 | nt.assert_array_almost_equal(L.pp, np.r_[0, 1, 2])
82 | self.assertEqual(L.ppd, math.sqrt(5))
83 |
84 | # validate pp
85 | self.assertTrue(L.contains(L.pp))
86 |
87 | def test_contains(self):
88 | P = [2, 3, 7]
89 | Q = [2, 1, 0]
90 | L = Line3.Join(P, Q)
91 |
92 | # validate contains
93 | self.assertTrue(L.contains([2, 3, 7]))
94 | self.assertTrue(L.contains([2, 1, 0]))
95 | self.assertFalse(L.contains([2, 1, 4]))
96 |
97 | def test_closest(self):
98 | P = [2, 3, 7]
99 | Q = [2, 1, 0]
100 | L = Line3.Join(P, Q)
101 |
102 | p, d = L.closest_to_point(P)
103 | nt.assert_array_almost_equal(p, P)
104 | self.assertAlmostEqual(d, 0)
105 |
106 | # validate closest with given points and origin
107 | p, d = L.closest_to_point(Q)
108 | nt.assert_array_almost_equal(p, Q)
109 | self.assertAlmostEqual(d, 0)
110 |
111 | L = Line3.Join([-1, 1, 2], [1, 1, 2])
112 | p, d = L.closest_to_point([0, 1, 2])
113 | nt.assert_array_almost_equal(p, np.r_[0, 1, 2])
114 | self.assertAlmostEqual(d, 0)
115 |
116 | p, d = L.closest_to_point([5, 1, 2])
117 | nt.assert_array_almost_equal(p, np.r_[5, 1, 2])
118 | self.assertAlmostEqual(d, 0)
119 |
120 | p, d = L.closest_to_point([0, 0, 0])
121 | nt.assert_array_almost_equal(p, L.pp)
122 | self.assertEqual(d, L.ppd)
123 |
124 | p, d = L.closest_to_point([5, 1, 0])
125 | nt.assert_array_almost_equal(p, [5, 1, 2])
126 | self.assertAlmostEqual(d, 2)
127 |
128 | @pytest.mark.skipif(
129 | sys.platform.startswith("darwin") and sys.version_info < (3, 11),
130 | reason="tkinter bug with mac",
131 | )
132 | def test_plot(self):
133 | P = [2, 3, 7]
134 | Q = [2, 1, 0]
135 | L = Line3.Join(P, Q)
136 |
137 | fig = plt.figure()
138 | ax = fig.add_subplot(111, projection="3d", proj_type="ortho")
139 | ax.set_xlim3d(-10, 10)
140 | ax.set_ylim3d(-10, 10)
141 | ax.set_zlim3d(-10, 10)
142 |
143 | L.plot(color="red", linewidth=2)
144 |
145 | def test_eq(self):
146 | w = np.r_[1, 2, 3]
147 | P = np.r_[-2, 4, 3]
148 |
149 | L1 = Line3.Join(P, P + w)
150 | L2 = Line3.Join(P + 2 * w, P + 5 * w)
151 | L3 = Line3.Join(P + np.r_[1, 0, 0], P + w)
152 |
153 | self.assertTrue(L1 == L2)
154 | self.assertFalse(L1 == L3)
155 |
156 | self.assertFalse(L1 != L2)
157 | self.assertTrue(L1 != L3)
158 |
159 | def test_skew(self):
160 | P = [2, 3, 7]
161 | Q = [2, 1, 0]
162 | L = Line3.Join(P, Q)
163 |
164 | m = L.skew()
165 |
166 | self.assertEqual(m.shape, (4, 4))
167 | nt.assert_array_almost_equal(m + m.T, np.zeros((4, 4)))
168 |
169 | def test_rmul(self):
170 | P = [1, 2, 0]
171 | Q = [1, 2, 10] # vertical line through (1,2)
172 | L = Line3.Join(P, Q)
173 |
174 | # check transformation by SE3
175 |
176 | L2 = SE3() * L
177 | p = L2.intersect_plane([0, 0, 1, 0])[0] # intersects z=0
178 | nt.assert_array_almost_equal(p, [1, 2, 0])
179 |
180 | L2 = SE3(2, 0, 0) * L # shift line in the x direction
181 | p = L2.intersect_plane([0, 0, 1, 0])[0] # intersects z=0
182 | nt.assert_array_almost_equal(p, [3, 2, 0])
183 |
184 | L2 = SE3(0, 2, 0) * L # shift line in the y direction
185 | p = L2.intersect_plane([0, 0, 1, 0])[0] # intersects z=0
186 | nt.assert_array_almost_equal(p, [1, 4, 0])
187 |
188 | L2 = SE3.Rx(np.pi / 2) * L # rotate line about x-axis, now horizontal
189 | nt.assert_array_almost_equal(L2.uw, [0, -1, 0])
190 |
191 | def test_parallel(self):
192 | L1 = Line3.PointDir([4, 5, 6], [1, 2, 3])
193 | L2 = Line3.PointDir([5, 5, 6], [1, 2, 3])
194 | L3 = Line3.PointDir([4, 5, 6], [3, 2, 1])
195 |
196 | # L1, || L2, but doesnt intersect
197 | # L1, intersects L3
198 |
199 | self.assertTrue(L1.isparallel(L1))
200 | self.assertTrue(L1 | L1)
201 |
202 | self.assertTrue(L1.isparallel(L2))
203 | self.assertTrue(L1 | L2)
204 | self.assertTrue(L2.isparallel(L1))
205 | self.assertTrue(L2 | L1)
206 | self.assertFalse(L1.isparallel(L3))
207 | self.assertFalse(L1 | L3)
208 |
209 | def test_intersect(self):
210 | L1 = Line3.PointDir([4, 5, 6], [1, 2, 3])
211 | L2 = Line3.PointDir([5, 5, 6], [1, 2, 3])
212 | L3 = Line3.PointDir([4, 5, 6], [0, 0, 1])
213 | L4 = Line3.PointDir([5, 5, 6], [1, 0, 0])
214 |
215 | # L1, || L2, but doesnt intersect
216 | # L3, intersects L4
217 | self.assertFalse(
218 | L1 ^ L2,
219 | )
220 |
221 | self.assertTrue(
222 | L3 ^ L4,
223 | )
224 |
225 | def test_commonperp(self):
226 | L1 = Line3.PointDir([4, 5, 6], [0, 0, 1])
227 | L2 = Line3.PointDir([6, 5, 6], [0, 1, 0])
228 |
229 | self.assertFalse(L1 | L2)
230 | self.assertFalse(L1 ^ L2)
231 |
232 | self.assertEqual(L1.distance(L2), 2)
233 |
234 | L = L1.commonperp(L2) # common perp intersects both lines
235 |
236 | self.assertTrue(L ^ L1)
237 | self.assertTrue(L ^ L2)
238 |
239 | def test_line(self):
240 | # mindist
241 | # intersect
242 | # char
243 | # intersect_volume
244 | # mindist
245 | # mtimes
246 | # or
247 | # side
248 | pass
249 |
250 | def test_contains(self):
251 | P = [2, 3, 7]
252 | Q = [2, 1, 0]
253 | L = Line3.Join(P, Q)
254 |
255 | self.assertTrue(L.contains(L.point(0)))
256 | self.assertTrue(L.contains(L.point(1)))
257 | self.assertTrue(L.contains(L.point(-1)))
258 |
259 | def test_point(self):
260 | P = [2, 3, 7]
261 | Q = [2, 1, 0]
262 | L = Line3.Join(P, Q)
263 |
264 | nt.assert_array_almost_equal(L.point(0).flatten(), L.pp)
265 |
266 | for x in (-2, 0, 3):
267 | nt.assert_array_almost_equal(L.lam(L.point(x)), x)
268 |
269 | def test_char(self):
270 | P = [2, 3, 7]
271 | Q = [2, 1, 0]
272 | L = Line3.Join(P, Q)
273 |
274 | s = str(L)
275 | self.assertIsInstance(s, str)
276 |
277 | def test_plane(self):
278 | xyplane = [0, 0, 1, 0]
279 | xzplane = [0, 1, 0, 0]
280 | L = Line3.TwoPlanes(xyplane, xzplane) # x axis
281 | nt.assert_array_almost_equal(L.vec, np.r_[0, 0, 0, -1, 0, 0])
282 |
283 | L = Line3.Join([-1, 2, 3], [1, 2, 3])
284 | # line at y=2,z=3
285 | x6 = [1, 0, 0, -6] # x = 6
286 |
287 | # plane_intersect
288 | p, lam = L.intersect_plane(x6)
289 | nt.assert_array_almost_equal(p, np.r_[6, 2, 3])
290 | nt.assert_array_almost_equal(L.point(lam).flatten(), np.r_[6, 2, 3])
291 |
292 | x6s = Plane3.PointNormal(n=[1, 0, 0], p=[6, 0, 0])
293 | p, lam = L.intersect_plane(x6s)
294 | nt.assert_array_almost_equal(p, np.r_[6, 2, 3])
295 |
296 | nt.assert_array_almost_equal(L.point(lam).flatten(), np.r_[6, 2, 3])
297 |
298 | def test_methods(self):
299 | # intersection
300 | px = Line3.Join([0, 0, 0], [1, 0, 0])
301 | # x-axis
302 | py = Line3.Join([0, 0, 0], [0, 1, 0])
303 | # y-axis
304 | px1 = Line3.Join([0, 1, 0], [1, 1, 0])
305 | # offset x-axis
306 |
307 | self.assertEqual(px.ppd, 0)
308 | self.assertEqual(px1.ppd, 1)
309 | nt.assert_array_almost_equal(px1.pp, [0, 1, 0])
310 |
311 | px.intersects(px)
312 | px.intersects(py)
313 | px.intersects(px1)
314 |
315 | # def test_intersect(self):
316 | # px = Line3([0, 0, 0], [1, 0, 0]); # x-axis
317 | # py = Line3([0, 0, 0], [0, 1, 0]); # y-axis
318 | #
319 | # plane.d = [1, 0, 0]; plane.p = 2; # plane x=2
320 | #
321 | # px.intersect_plane(plane)
322 | # py.intersect_plane(plane)
323 |
324 |
325 | if __name__ == "__main__":
326 | unittest.main()
327 |
--------------------------------------------------------------------------------
/tests/base/test_transforms2d.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | """
4 | Created on Fri Apr 10 14:19:04 2020
5 |
6 | @author: corkep
7 |
8 | """
9 |
10 | import numpy as np
11 | import numpy.testing as nt
12 | import unittest
13 | from math import pi
14 | import math
15 | from scipy.linalg import logm
16 | import pytest
17 | import sys
18 |
19 | from spatialmath.base.transforms2d import *
20 | from spatialmath.base.transformsNd import (
21 | isR,
22 | t2r,
23 | r2t,
24 | rt2tr,
25 | skew,
26 | vexa,
27 | skewa,
28 | homtrans,
29 | )
30 |
31 | import matplotlib.pyplot as plt
32 |
33 |
34 | class Test2D(unittest.TestCase):
35 | def test_rot2(self):
36 | R = np.array([[1, 0], [0, 1]])
37 | nt.assert_array_almost_equal(rot2(0), R)
38 | nt.assert_array_almost_equal(rot2(0, unit="rad"), R)
39 | nt.assert_array_almost_equal(rot2(0, unit="deg"), R)
40 | nt.assert_array_almost_equal(rot2(0, "deg"), R)
41 | nt.assert_almost_equal(np.linalg.det(rot2(0)), 1)
42 |
43 | R = np.array([[0, -1], [1, 0]])
44 | nt.assert_array_almost_equal(rot2(pi / 2), R)
45 | nt.assert_array_almost_equal(rot2(pi / 2, unit="rad"), R)
46 | nt.assert_array_almost_equal(rot2(90, unit="deg"), R)
47 | nt.assert_array_almost_equal(rot2(90, "deg"), R)
48 | nt.assert_almost_equal(np.linalg.det(rot2(pi / 2)), 1)
49 |
50 | R = np.array([[-1, 0], [0, -1]])
51 | nt.assert_array_almost_equal(rot2(pi), R)
52 | nt.assert_array_almost_equal(rot2(pi, unit="rad"), R)
53 | nt.assert_array_almost_equal(rot2(180, unit="deg"), R)
54 | nt.assert_array_almost_equal(rot2(180, "deg"), R)
55 | nt.assert_almost_equal(np.linalg.det(rot2(pi)), 1)
56 |
57 | def test_trot2(self):
58 | nt.assert_array_almost_equal(
59 | trot2(pi / 2, t=[3, 4]), np.array([[0, -1, 3], [1, 0, 4], [0, 0, 1]])
60 | )
61 | nt.assert_array_almost_equal(
62 | trot2(pi / 2, t=(3, 4)), np.array([[0, -1, 3], [1, 0, 4], [0, 0, 1]])
63 | )
64 | nt.assert_array_almost_equal(
65 | trot2(pi / 2, t=np.array([3, 4])),
66 | np.array([[0, -1, 3], [1, 0, 4], [0, 0, 1]]),
67 | )
68 |
69 | def test_Rt(self):
70 | nt.assert_array_almost_equal(rot2(0.3), t2r(trot2(0.3)))
71 | nt.assert_array_almost_equal(trot2(0.3), r2t(rot2(0.3)))
72 |
73 | R = rot2(0.2)
74 | t = [1, 2]
75 | T = rt2tr(R, t)
76 | nt.assert_array_almost_equal(t2r(T), R)
77 | nt.assert_array_almost_equal(transl2(T), np.array(t))
78 | # TODO
79 |
80 | def test_trlog2(self):
81 | R = rot2(0.5)
82 | nt.assert_array_almost_equal(trlog2(R), skew(0.5))
83 |
84 | nt.assert_array_almost_equal(trlog2(R, twist=True), 0.5)
85 |
86 | T = transl2(1, 2) @ trot2(0.5)
87 | nt.assert_array_almost_equal(trlog2(T), logm(T))
88 |
89 | nt.assert_array_almost_equal(trlog2(T, twist=True), vexa(logm(T)))
90 |
91 | def test_trexp2(self):
92 | R = trexp2(skew(0.5))
93 | nt.assert_array_almost_equal(R, rot2(0.5))
94 |
95 | T = transl2(1, 2) @ trot2(0.5)
96 | nt.assert_array_almost_equal(trexp2(logm(T)), T)
97 |
98 | def test_trnorm2(self):
99 | R = rot2(0.4)
100 | R = np.round(R, 3) # approx SO(2)
101 | R = trnorm2(R)
102 | self.assertTrue(isrot2(R, check=True))
103 |
104 | R = rot2(0.4)
105 | R = np.round(R, 3) # approx SO(2)
106 | T = rt2tr(R, [3, 4])
107 |
108 | T = trnorm2(T)
109 | self.assertTrue(ishom2(T, check=True))
110 | nt.assert_almost_equal(T[:2, 2], [3, 4])
111 |
112 | def test_transl2(self):
113 | nt.assert_array_almost_equal(
114 | transl2(1, 2), np.array([[1, 0, 1], [0, 1, 2], [0, 0, 1]])
115 | )
116 | nt.assert_array_almost_equal(
117 | transl2([1, 2]), np.array([[1, 0, 1], [0, 1, 2], [0, 0, 1]])
118 | )
119 |
120 | def test_pos2tr2(self):
121 | nt.assert_array_almost_equal(
122 | pos2tr2(1, 2), np.array([[1, 0, 1], [0, 1, 2], [0, 0, 1]])
123 | )
124 | nt.assert_array_almost_equal(
125 | transl2([1, 2]), np.array([[1, 0, 1], [0, 1, 2], [0, 0, 1]])
126 | )
127 | nt.assert_array_almost_equal(tr2pos2(pos2tr2(1, 2)), np.array([1, 2]))
128 |
129 | def test_tr2jac2(self):
130 | T = trot2(0.3, t=[4, 5])
131 | jac2 = tr2jac2(T)
132 | nt.assert_array_almost_equal(jac2[:2, :2], smb.t2r(T))
133 | nt.assert_array_almost_equal(jac2[:3, 2], np.array([0, 0, 1]))
134 | nt.assert_array_almost_equal(jac2[2, :3], np.array([0, 0, 1]))
135 |
136 | def test_xyt2tr(self):
137 | T = xyt2tr([1, 2, 0])
138 | nt.assert_array_almost_equal(T, transl2(1, 2))
139 |
140 | T = xyt2tr([1, 2, 0.2])
141 | nt.assert_array_almost_equal(T, rt2tr(rot2(0.2), [1, 2]))
142 |
143 | def test_trinv2(self):
144 | T = rt2tr(rot2(0.2), [1, 2])
145 | nt.assert_array_almost_equal(trinv2(T) @ T, np.eye(3))
146 |
147 | def test_tradjoint2(self):
148 | T = xyt2tr([1, 2, 0.2])
149 | X = [1, 2, 3]
150 | nt.assert_almost_equal(tradjoint2(T) @ X, vexa(T @ skewa(X) @ trinv2(T)))
151 |
152 | def test_points2tr2(self):
153 | p1 = np.random.uniform(size=(2, 5))
154 | T = xyt2tr([1, 2, 0.2])
155 | p2 = homtrans(T, p1)
156 | T2 = points2tr2(p1, p2)
157 | nt.assert_almost_equal(T, T2)
158 |
159 | def test_icp2d(self):
160 | p1 = np.random.uniform(size=(2, 30))
161 | T = xyt2tr([1, 2, 0.2])
162 |
163 | p2 = homtrans(T, p1)
164 | k = np.random.permutation(p2.shape[1])
165 | p2 = p2[:, k]
166 |
167 | T2 = ICP2d(p2, p1, T=xyt2tr([1, 2, 0.2]))
168 | nt.assert_almost_equal(T, T2)
169 |
170 | def test_print2(self):
171 | T = transl2(1, 2) @ trot2(0.3)
172 |
173 | s = trprint2(T, file=None)
174 | self.assertIsInstance(s, str)
175 | self.assertEqual(len(s), 15)
176 |
177 | def test_checks(self):
178 | # 2D case, with rotation matrix
179 | R = np.eye(2)
180 | nt.assert_equal(isR(R), True)
181 | nt.assert_equal(isrot2(R), True)
182 |
183 | nt.assert_equal(ishom2(R), False)
184 | nt.assert_equal(isrot2(R, True), True)
185 |
186 | nt.assert_equal(ishom2(R, True), False)
187 |
188 | # 2D case, invalid rotation matrix
189 | R = np.array([[1, 1], [0, 1]])
190 | nt.assert_equal(isR(R), False)
191 | nt.assert_equal(isrot2(R), True)
192 | nt.assert_equal(ishom2(R), False)
193 | nt.assert_equal(isrot2(R, True), False)
194 | nt.assert_equal(ishom2(R, True), False)
195 |
196 | # 2D case, with homogeneous transformation matrix
197 | T = np.array([[1, 0, 3], [0, 1, 4], [0, 0, 1]])
198 | nt.assert_equal(isR(T), False)
199 | nt.assert_equal(isrot2(T), False)
200 |
201 | nt.assert_equal(ishom2(T), True)
202 | nt.assert_equal(isrot2(T, True), False)
203 |
204 | nt.assert_equal(ishom2(T, True), True)
205 |
206 | # 2D case, invalid rotation matrix
207 | T = np.array([[1, 1, 3], [0, 1, 4], [0, 0, 1]])
208 | nt.assert_equal(isR(T), False)
209 | nt.assert_equal(isrot2(T), False)
210 |
211 | nt.assert_equal(ishom2(T), True)
212 | nt.assert_equal(isrot2(T, True), False)
213 |
214 | nt.assert_equal(ishom2(T, True), False)
215 |
216 | # 2D case, invalid bottom row
217 | T = np.array([[1, 1, 3], [0, 1, 4], [9, 0, 1]])
218 | nt.assert_equal(isR(T), False)
219 | nt.assert_equal(isrot2(T), False)
220 |
221 | nt.assert_equal(ishom2(T), True)
222 | nt.assert_equal(isrot2(T, True), False)
223 |
224 | nt.assert_equal(ishom2(T, True), False)
225 |
226 | def test_trinterp2(self):
227 | R0 = rot2(-0.3)
228 | R1 = rot2(0.3)
229 |
230 | nt.assert_array_almost_equal(trinterp2(start=None, end=R1, s=0), np.eye(2))
231 | nt.assert_array_almost_equal(trinterp2(start=None, end=R1, s=1), R1)
232 | nt.assert_array_almost_equal(
233 | trinterp2(start=None, end=R1, s=0.5), rot2(0.3 / 2)
234 | )
235 |
236 | nt.assert_array_almost_equal(trinterp2(start=None, end=R1, s=0), np.eye(2))
237 | nt.assert_array_almost_equal(trinterp2(start=None, end=R1, s=1), R1)
238 | nt.assert_array_almost_equal(
239 | trinterp2(start=None, end=R1, s=0.5), rot2(0.3 / 2)
240 | )
241 |
242 | T0 = trot2(-0.3)
243 | T1 = trot2(0.3)
244 |
245 | nt.assert_array_almost_equal(trinterp2(start=T0, end=T1, s=0), T0)
246 | nt.assert_array_almost_equal(trinterp2(start=T0, end=T1, s=1), T1)
247 | nt.assert_array_almost_equal(trinterp2(start=T0, end=T1, s=0.5), np.eye(3))
248 |
249 | nt.assert_array_almost_equal(trinterp2(start=None, end=T1, s=0), np.eye(3))
250 | nt.assert_array_almost_equal(trinterp2(start=None, end=T1, s=1), T1)
251 | nt.assert_array_almost_equal(
252 | trinterp2(start=None, end=T1, s=0.5), trot2(0.3 / 2)
253 | )
254 |
255 | T0 = transl2(-1, -2)
256 | T1 = transl2(1, 2)
257 |
258 | nt.assert_array_almost_equal(trinterp2(start=T0, end=T1, s=0), T0)
259 | nt.assert_array_almost_equal(trinterp2(start=T0, end=T1, s=1), T1)
260 | nt.assert_array_almost_equal(trinterp2(start=T0, end=T1, s=0.5), np.eye(3))
261 |
262 | T0 = transl2(-1, -2) @ trot2(-0.3)
263 | T1 = transl2(1, 2) @ trot2(0.3)
264 |
265 | nt.assert_array_almost_equal(trinterp2(start=T0, end=T1, s=0), T0)
266 | nt.assert_array_almost_equal(trinterp2(start=T0, end=T1, s=1), T1)
267 | nt.assert_array_almost_equal(trinterp2(start=T0, end=T1, s=0.5), np.eye(3))
268 |
269 | nt.assert_array_almost_equal(trinterp2(start=T0, end=T1, s=0), T0)
270 | nt.assert_array_almost_equal(trinterp2(start=T0, end=T1, s=1), T1)
271 | nt.assert_array_almost_equal(trinterp2(start=T0, end=T1, s=0.5), np.eye(3))
272 |
273 | nt.assert_array_almost_equal(trinterp2(start=None, end=T1, s=0), np.eye(3))
274 | nt.assert_array_almost_equal(trinterp2(start=None, end=T1, s=1), T1)
275 | nt.assert_array_almost_equal(
276 | trinterp2(start=None, end=T1, s=0.5), xyt2tr([0.5, 1, 0.15])
277 | )
278 |
279 | @pytest.mark.skipif(
280 | sys.platform.startswith("darwin") and sys.version_info < (3, 11),
281 | reason="tkinter bug with mac",
282 | )
283 | def test_plot(self):
284 | plt.figure()
285 | trplot2(transl2(1, 2), block=False, frame="A", rviz=True, width=1)
286 | trplot2(transl2(3, 1), block=False, color="red", arrow=True, width=3, frame="B")
287 | trplot2(
288 | transl2(4, 3) @ trot2(math.pi / 3), block=False, color="green", frame="c"
289 | )
290 | plt.close("all")
291 |
292 |
293 | # ---------------------------------------------------------------------------------------#
294 | if __name__ == "__main__":
295 | unittest.main()
296 |
--------------------------------------------------------------------------------