|
1 |
| -# CoordinateTransformations.jl |
| 1 | +```@meta |
| 2 | +DocTestSetup = quote |
| 3 | + using CoordinateTransformations |
| 4 | +end |
| 5 | +``` |
| 6 | +# CoordinateTransformations |
| 7 | + |
| 8 | +[](https://github.com/JuliaGeometry/CoordinateTransformations.jl/actions?query=workflow%3ACI) |
| 9 | + |
| 10 | +**CoordinateTransformations** is a Julia package to manage simple or complex |
| 11 | +networks of coordinate system transformations. Transformations can be easily |
| 12 | +applied, inverted, composed, and differentiated (both with respect to the |
| 13 | +input coordinates and with respect to transformation parameters such as rotation |
| 14 | +angle). Transformations are designed to be light-weight and efficient enough |
| 15 | +for, e.g., real-time graphical applications, while support for both explicit |
| 16 | +and automatic differentiation makes it easy to perform optimization and |
| 17 | +therefore ideal for computer vision applications such as SLAM (simultaneous |
| 18 | +localization and mapping). |
| 19 | + |
| 20 | +The package provide two main pieces of functionality |
| 21 | + |
| 22 | +1. Primarily, an interface for defining `Transformation`s and applying |
| 23 | + (by calling), inverting (`inv()`), composing (`∘` or `compose()`) and |
| 24 | + differentiating (`transform_deriv()` and `transform_deriv_params()`) them. |
| 25 | + |
| 26 | +2. A small set of built-in, composable, primitive transformations for |
| 27 | + transforming 2D and 3D points (optionally leveraging the *StaticArrays* |
| 28 | + and *Rotations* packages). |
| 29 | + |
| 30 | +## Quick start |
| 31 | + |
| 32 | +Let's translate a 3D point: |
| 33 | +```julia |
| 34 | +using CoordinateTransformations, Rotations, StaticArrays |
| 35 | + |
| 36 | +x = SVector(1.0, 2.0, 3.0) # SVector is provided by StaticArrays.jl |
| 37 | +trans = Translation(3.5, 1.5, 0.0) |
| 38 | + |
| 39 | +y = trans(x) |
| 40 | +``` |
| 41 | + |
| 42 | +We can either apply different transformations in turn, |
| 43 | +```julia |
| 44 | +rot = LinearMap(RotX(0.3)) # Rotate 0.3 radians about X-axis, from Rotations.jl |
| 45 | + |
| 46 | +z = trans(rot(x)) |
| 47 | +``` |
| 48 | +or build a composed transformation using the `∘` operator (accessible at the |
| 49 | +REPL by typing `\circ` then tab): |
| 50 | +```julia |
| 51 | +composed = trans ∘ rot # alternatively, use compose(trans, rot) |
| 52 | + |
| 53 | +composed(x) == z |
| 54 | +``` |
| 55 | +A composition of a `Translation` and a `LinearMap` results in an `AffineMap`. |
| 56 | + |
| 57 | +We can invert the transformation: |
| 58 | +```julia |
| 59 | +composed_inv = inv(composed) |
| 60 | + |
| 61 | +composed_inv(z) == x |
| 62 | +``` |
| 63 | + |
| 64 | +For any transformation, we can shift the origin to a new point using `recenter`: |
| 65 | +```julia |
| 66 | +rot_around_x = recenter(rot, x) |
| 67 | +``` |
| 68 | +Now `rot_around_x` is a rotation around the point `x = SVector(1.0, 2.0, 3.0)`. |
| 69 | + |
| 70 | + |
| 71 | +Finally, we can construct a matrix describing how the components of `z` |
| 72 | +differentiates with respect to components of `x`: |
| 73 | +```julia |
| 74 | +# In general, the transform may be non-linear, and thus we require |
| 75 | +# the value of x to compute the derivative |
| 76 | +∂z_∂x = transform_deriv(composed, x) |
| 77 | +``` |
| 78 | + |
| 79 | +Or perhaps we want to know how `y` will change with respect to changes of |
| 80 | +to the translation parameters: |
| 81 | +```julia |
| 82 | +∂y_∂θ = transform_deriv_params(trans, x) |
| 83 | +``` |
| 84 | + |
| 85 | +## Interface |
| 86 | + |
| 87 | +Transformations are derived from `Transformation`. As an example, we have |
| 88 | +`Translation{T} <: Transformation`. A `Translation` will accept and translate |
| 89 | +points in a variety of formats, such as `Vector` or `SVector`, but in general |
| 90 | +your custom-defined `Transformation`s could transform any Julia object. |
| 91 | + |
| 92 | +Transformations can be reversed using `inv(trans)`. They can be chained |
| 93 | +together using the `∘` operator (`trans1 ∘ trans2`) or `compose` function (`compose(trans1, trans2)`). |
| 94 | +In this case, `trans2` is applied first to the data, before `trans1`. |
| 95 | +Composition may be intelligent, for instance by precomputing a new `Translation` |
| 96 | +by summing the elements of two existing `Translation`s, and yet other |
| 97 | +transformations may compose to the `IdentityTransformation`. But by default, |
| 98 | +composition will result in a `ComposedTransformation` object which simply |
| 99 | +dispatches to apply the transformations in the correct order. |
| 100 | + |
| 101 | +Finally, the matrix describing how differentials propagate through a transform |
| 102 | +can be calculated with the `transform_deriv(trans, x)` method. The derivatives |
| 103 | +of how the output depends on the transformation parameters is accessed via |
| 104 | +`transform_deriv_params(trans, x)`. Users currently have to overload these methods, |
| 105 | +as no fall-back automatic differentiation is currently included. Alternatively, |
| 106 | +all the built-in types and transformations are compatible with automatic differentiation |
| 107 | +techniques, and can be parameterized by *DualNumbers*' `DualNumber` or *ForwardDiff*'s `Dual`. |
| 108 | + |
| 109 | +## Built-in transformations |
| 110 | + |
| 111 | +A small number of 2D and 3D coordinate systems and transformations are included. |
| 112 | +We also have `IdentityTransformation` and `ComposedTransformation`, which allows us |
| 113 | +to nest together arbitrary transformations to create a complex yet efficient |
| 114 | +transformation chain. |
| 115 | + |
| 116 | +### Coordinate types |
| 117 | + |
| 118 | +The package accepts any `AbstractVector` type for Cartesian coordinates. For speed, we recommend |
| 119 | +using a statically-sized container such as `SVector{N}` from *StaticArrays*. |
| 120 | + |
| 121 | +We do provide a few specialist coordinate types. The `Polar(r, θ)` type is a 2D |
| 122 | +polar representation of a point, and similarly in 3D we have defined |
| 123 | +`Spherical(r, θ, ϕ)` and `Cylindrical(r, θ, z)`. |
| 124 | + |
| 125 | +### Coordinate system transformations |
| 126 | + |
| 127 | +Two-dimensional coordinates may be converted using these parameterless (singleton) |
| 128 | +transformations: |
| 129 | + |
| 130 | +1. [`PolarFromCartesian()`](@ref) |
| 131 | +2. [`CartesianFromPolar()`](@ref) |
| 132 | + |
| 133 | +Three-dimensional coordinates may be converted using these parameterless |
| 134 | +transformations: |
| 135 | + |
| 136 | +1. [`SphericalFromCartesian()`](@ref) |
| 137 | +2. [`CartesianFromSpherical()`](@ref) |
| 138 | +3. [`SphericalFromCylindrical()`](@ref) |
| 139 | +4. [`CylindricalFromSpherical()`](@ref) |
| 140 | +5. [`CartesianFromCylindrical()`](@ref) |
| 141 | +6. [`CylindricalFromCartesian()`](@ref) |
| 142 | + |
| 143 | +However, you may find it simpler to use the convenience constructors like |
| 144 | +`Polar(SVector(1.0, 2.0))`. |
| 145 | + |
| 146 | +### Translations |
| 147 | + |
| 148 | +Translations can be be applied to Cartesian coordinates in arbitrary dimensions, |
| 149 | +by e.g. `Translation(Δx, Δy)` or `Translation(Δx, Δy, Δz)` in 2D/3D, or by |
| 150 | +`Translation(Δv)` in general (with `Δv` an `AbstractVector`). Compositions of |
| 151 | +two `Translation`s will intelligently create a new `Translation` by adding the |
| 152 | +translation vectors. |
| 153 | + |
| 154 | +### Linear transformations |
| 155 | + |
| 156 | +Linear transformations (a.k.a. linear maps), including rotations, can be |
| 157 | +encapsulated in the `LinearMap` type, which is a simple wrapper of an |
| 158 | +`AbstractMatrix`. |
| 159 | + |
| 160 | +You are able to provide any matrix of your choosing, but your choice of type |
| 161 | +will have a large effect on speed. For instance, if you know the dimensionality |
| 162 | +of your points (e.g. 2D or 3D) you might consider a statically sized matrix |
| 163 | +like `SMatrix` from *StaticArrays.jl*. We recommend performing 3D rotations |
| 164 | +using those from *Rotations.jl* for their speed and flexibility. Scaling will |
| 165 | +be efficient with Julia's built-in `UniformScaling`. Also note that compositions |
| 166 | +of two `LinearMap`s will intelligently create a new `LinearMap` by multiplying |
| 167 | +the transformation matrices. |
| 168 | + |
| 169 | +### Affine maps |
| 170 | + |
| 171 | +An Affine map encapsulates a more general set of transformation which are |
| 172 | +defined by a composition of a translation and a linear transformation. An |
| 173 | +`AffineMap` is constructed from an `AbstractVector` translation `v` and an |
| 174 | +`AbstractMatrix` linear transformation `M`. It will perform the mapping |
| 175 | +`x -> M*x + v`, but the order of addition and multiplication will be more obvious |
| 176 | +(and controllable) if you construct it from a composition of a linear map |
| 177 | +and a translation, e.g. `Translation(v) ∘ LinearMap(v)` (or any combination of |
| 178 | +`LinearMap`, `Translation` and `AffineMap`). |
| 179 | + |
| 180 | +`AffineMap`s can be constructed to fit point pairs `from_points => to_points`: |
| 181 | + |
| 182 | +```jldoctest |
| 183 | +julia> from_points = [[0, 0], [1, 0], [0, 1]]; |
| 184 | +
|
| 185 | +julia> to_points = [[1, 1], [3, 1], [1.5, 3]]; |
| 186 | +
|
| 187 | +julia> AffineMap(from_points => to_points) |
| 188 | +AffineMap([1.9999999999999996 0.5; -5.551115123125783e-16 2.0], [0.9999999999999999, 1.0000000000000002]) |
| 189 | +``` |
| 190 | + |
| 191 | +The points can be supplied as a collection of vectors or as a matrix with points as columns. |
| 192 | + |
| 193 | +### Perspective transformations |
| 194 | + |
| 195 | +The perspective transformation maps real-space coordinates to those on a virtual |
| 196 | +"screen" of one lesser dimension. For instance, this process is used to render |
| 197 | +3D scenes to 2D images in computer generated graphics and games. It is an ideal |
| 198 | +model of how a pinhole camera operates and is a good approximation of the modern |
| 199 | +photography process. |
| 200 | + |
| 201 | +The `PerspectiveMap()` command creates a `Transformation` to perform the |
| 202 | +projective mapping. It can be applied individually, but is particularly |
| 203 | +powerful when composed with an `AffineMap` containing the position and |
| 204 | +orientation of the camera in your scene. For example, to transfer `points` in 3D |
| 205 | +space to 2D `screen_points` giving their projected locations on a virtual camera |
| 206 | +image, you might use the following code: |
| 207 | + |
| 208 | +```julia |
| 209 | +cam_transform = PerspectiveMap() ∘ inv(AffineMap(cam_rotation, cam_position)) |
| 210 | +screen_points = map(cam_transform, points) |
| 211 | +``` |
| 212 | + |
| 213 | +There is also a `cameramap()` convenience function that can create a composed |
| 214 | +transformation that includes the intrinsic scaling (e.g. focal length and pixel |
| 215 | +size) and offset (defining which pixel is labeled `(0,0)`) of an imaging system. |
| 216 | + |
| 217 | +## Acknowledgements |
| 218 | + |
| 219 | +- The [Fugro Roames organization](https://github.com/FugroRoames) |
0 commit comments