```@meta
DocTestSetup = quote
    using CoordinateTransformations
end
```
# CoordinateTransformations

[![Build Status](https://github.com/JuliaGeometry/CoordinateTransformations.jl/workflows/CI/badge.svg)](https://github.com/JuliaGeometry/CoordinateTransformations.jl/actions?query=workflow%3ACI)

**CoordinateTransformations** is a Julia package to manage simple or complex
networks of coordinate system transformations. Transformations can be easily
applied, inverted, composed, and differentiated (both with respect to the
input coordinates and with respect to transformation parameters such as rotation
angle). Transformations are designed to be light-weight and efficient enough
for, e.g., real-time graphical applications, while support for both explicit
and automatic differentiation makes it easy to perform optimization and
therefore ideal for computer vision applications such as SLAM (simultaneous
localization and mapping).

The package provide two main pieces of functionality

1. Primarily, an interface for defining `Transformation`s and applying
   (by calling), inverting (`inv()`), composing (`∘` or `compose()`) and
   differentiating (`transform_deriv()` and `transform_deriv_params()`) them.

2. A small set of built-in, composable, primitive transformations for
   transforming 2D and 3D points (optionally leveraging the *StaticArrays*
   and *Rotations* packages).

## Quick start

Let's translate a 3D point:
```julia
using CoordinateTransformations, Rotations, StaticArrays

x = SVector(1.0, 2.0, 3.0)  # SVector is provided by StaticArrays.jl
trans = Translation(3.5, 1.5, 0.0)

y = trans(x)
```

We can either apply different transformations in turn,
```julia
rot = LinearMap(RotX(0.3))  # Rotate 0.3 radians about X-axis, from Rotations.jl

z = trans(rot(x))
```
or build a composed transformation using the `∘` operator (accessible at the
REPL by typing `\circ` then tab):
```julia
composed = trans ∘ rot  # alternatively, use compose(trans, rot)

composed(x) == z
```
A composition of a `Translation` and a `LinearMap` results in an `AffineMap`.

We can invert the transformation:
```julia
composed_inv = inv(composed)

composed_inv(z) == x
```

For any transformation, we can shift the origin to a new point using `recenter`:
```julia
rot_around_x = recenter(rot, x)
```
Now `rot_around_x` is a rotation around the point `x = SVector(1.0, 2.0, 3.0)`.


Finally, we can construct a matrix describing how the components of `z`
differentiates with respect to components of `x`:
```julia
# In general, the transform may be non-linear, and thus we require
# the value of x to compute the derivative
∂z_∂x = transform_deriv(composed, x)
```

Or perhaps we want to know how `y` will change with respect to changes of
to the translation parameters:
```julia
∂y_∂θ = transform_deriv_params(trans, x)
```

## Interface

Transformations are derived from `Transformation`. As an example, we have
`Translation{T} <: Transformation`. A `Translation` will accept and translate
points in a variety of formats, such as `Vector` or `SVector`, but in general
your custom-defined `Transformation`s could transform any Julia object.

Transformations can be reversed using `inv(trans)`. They can be chained
together using the `∘` operator (`trans1 ∘ trans2`) or `compose` function (`compose(trans1, trans2)`).
In this case, `trans2` is applied first to the data, before `trans1`.
Composition may be intelligent, for instance by precomputing a new `Translation`
by summing the elements of two existing `Translation`s, and yet other
transformations may compose to the `IdentityTransformation`. But by default,
composition will result in a `ComposedTransformation` object which simply
dispatches to apply the transformations in the correct order.

Finally, the matrix describing how differentials propagate through a transform
can be calculated with the `transform_deriv(trans, x)` method. The derivatives
of how the output depends on the transformation parameters is accessed via
`transform_deriv_params(trans, x)`. Users currently have to overload these methods,
as no fall-back automatic differentiation is currently included. Alternatively,
all the built-in types and transformations are compatible with automatic differentiation
techniques, and can be parameterized by *DualNumbers*' `DualNumber` or *ForwardDiff*'s `Dual`.

## Built-in transformations

A small number of 2D and 3D coordinate systems and transformations are included.
We also have `IdentityTransformation` and `ComposedTransformation`, which allows us
to nest together arbitrary transformations to create a complex yet efficient
transformation chain.

### Coordinate types

The package accepts any `AbstractVector` type for Cartesian coordinates. For speed, we recommend
using a statically-sized container such as `SVector{N}` from *StaticArrays*.

We do provide a few specialist coordinate types. The `Polar(r, θ)` type is a 2D
polar representation of a point, and similarly in 3D we have defined
`Spherical(r, θ, ϕ)` and `Cylindrical(r, θ, z)`.

### Coordinate system transformations

Two-dimensional coordinates may be converted using these parameterless (singleton)
transformations:

1. [`PolarFromCartesian()`](@ref)
2. [`CartesianFromPolar()`](@ref)

Three-dimensional coordinates may be converted using these parameterless
transformations:

1. [`SphericalFromCartesian()`](@ref)
2. [`CartesianFromSpherical()`](@ref)
3. [`SphericalFromCylindrical()`](@ref)
4. [`CylindricalFromSpherical()`](@ref)
5. [`CartesianFromCylindrical()`](@ref)
6. [`CylindricalFromCartesian()`](@ref)

However, you may find it simpler to use the convenience constructors like
`Polar(SVector(1.0, 2.0))`.

### Translations

Translations can be be applied to Cartesian coordinates in arbitrary dimensions,
by e.g. `Translation(Δx, Δy)` or `Translation(Δx, Δy, Δz)` in 2D/3D, or by
`Translation(Δv)` in general (with `Δv` an `AbstractVector`). Compositions of
two `Translation`s will intelligently create a new `Translation` by adding the
translation vectors.

### Linear transformations

Linear transformations (a.k.a. linear maps), including rotations, can be
encapsulated in the `LinearMap` type, which is a simple wrapper of an
`AbstractMatrix`.

You are able to provide any matrix of your choosing, but your choice of type
will have a large effect on speed. For instance, if you know the dimensionality
of your points (e.g. 2D or 3D) you might consider a statically sized matrix
like `SMatrix` from *StaticArrays.jl*. We recommend performing 3D rotations
using those from *Rotations.jl* for their speed and flexibility. Scaling will
be efficient with Julia's built-in `UniformScaling`. Also note that compositions
of two `LinearMap`s will intelligently create a new `LinearMap` by multiplying
the transformation matrices.

### Affine maps

An Affine map encapsulates a more general set of transformation which are
defined by a composition of a translation and a linear transformation. An
`AffineMap` is constructed from an `AbstractVector` translation `v` and an
`AbstractMatrix` linear transformation `M`. It will perform the mapping
`x -> M*x + v`, but the order of addition and multiplication will be more obvious
(and controllable) if you construct it from a composition of a linear map
and a translation, e.g. `Translation(v) ∘ LinearMap(v)` (or any combination of
`LinearMap`, `Translation` and `AffineMap`).

`AffineMap`s can be constructed to fit point pairs `from_points => to_points`:

```jldoctest lsq; filter=[r"(2\.0|1\.9999\d+)" => "2.0", r"(0\.5|0\.49999\d+)" => "0.5", r"(0\.0|[ -]\d\.\d+e-\d\d)" => "0.0", r"(1\.0(?!0)|1\.0000\d+|0\.9999\d+)" => "1.0"]
julia> from_points = [[0, 0], [1, 0], [0, 1]];

julia> to_points   = [[1, 1], [3, 1], [1.5, 3]];

julia> AffineMap(from_points => to_points)
AffineMap([2.0 0.5; 0.0 2.0], [1.0, 1.0])
```

(You may get slightly different numerical values due to roundoff errors.) The points can be supplied as a collection of vectors or as a matrix with points as columns.

If you want to restrict the transformation to be rigid (rotation + translation)
or similar (rotation, translation, and scaling), use `kabsch` instead:

```jldoctest lsq; filter=[r"0\.9912\d+" => "0.9912", r"0\.1321\d+" => "0.1321", r"1\.4588\d+" => "1.4588", r"1\.3803\d+" => "1.3803"]
julia> rigid = kabsch(from_points => to_points)
AffineMap([0.9912279006826346 0.132163720091018; -0.1321637200910178 0.9912279006826348], [1.4588694597421157, 1.380311939802794])
```


### Perspective transformations

The perspective transformation maps real-space coordinates to those on a virtual
"screen" of one lesser dimension. For instance, this process is used to render
3D scenes to 2D images in computer generated graphics and games. It is an ideal
model of how a pinhole camera operates and is a good approximation of the modern
photography process.

The `PerspectiveMap()` command creates a `Transformation` to perform the
projective mapping. It can be applied individually, but is particularly
powerful when composed with an `AffineMap` containing the position and
orientation of the camera in your scene. For example, to transfer `points` in 3D
space to 2D `screen_points` giving their projected locations on a virtual camera
image, you might use the following code:

```julia
cam_transform = PerspectiveMap() ∘ inv(AffineMap(cam_rotation, cam_position))
screen_points = map(cam_transform, points)
```

There is also a `cameramap()` convenience function that can create a composed
transformation that includes the intrinsic scaling (e.g. focal length and pixel
size) and offset (defining which pixel is labeled `(0,0)`) of an imaging system.

## Acknowledgements

- The [Fugro Roames organization](https://github.com/FugroRoames)
