Skip to content

Deploy documentation #95

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 17 additions & 18 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,20 @@ jobs:
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

# Enable the below for Documenter build
# docs:
# name: Documentation
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v1
# - uses: julia-actions/setup-julia@latest
# with:
# version: '1.3'
# - run: julia --project=docs -e '
# using Pkg;
# Pkg.develop(PackageSpec(; path=pwd()));
# Pkg.instantiate();'
# - run: julia --project=docs docs/make.jl
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# # Needed due to https://github.com/JuliaDocs/Documenter.jl/issues/1177
# DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }}
docs:
name: Documentation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: julia-actions/setup-julia@latest
with:
version: '1'
- uses: julia-actions/cache@v1
- name: Install dependencies
run: julia --project=docs -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate();'
- name: Build and deploy
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Needed due to https://github.com/JuliaDocs/Documenter.jl/issues/1177
DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }}
run: julia --project=docs docs/make.jl
16 changes: 15 additions & 1 deletion docs/make.jl
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
using Documenter
using CoordinateTransformations
using Documenter
using Documenter.Remotes: GitHub

DocMeta.setdocmeta!(
CoordinateTransformations,
:DocTestSetup,
:(using CoordinateTransformations),
recursive = true,
)

makedocs(
sitename = "CoordinateTransformations.jl",
modules = [CoordinateTransformations],
repo = GitHub("JuliaGeometry/CoordinateTransformations.jl"),
pages = [
"Introduction" => "index.md",
"API" => "api.md",
],
)

deploydocs(
repo = "github.com/JuliaGeometry/CoordinateTransformations.jl.git"
)
48 changes: 44 additions & 4 deletions docs/src/api.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,47 @@
# CoordinateTransformations.jl documentation
# API Reference

## API Reference
## Transformations
```@docs
Transformation
CoordinateTransformations.ComposedTransformation
IdentityTransformation
PerspectiveMap
inv
cameramap
compose
recenter
transform_deriv
transform_deriv_params
```

## Affine maps
```@docs
AbstractAffineMap
AffineMap
AffineMap(::Transformation, ::Any)
AffineMap(::Pair)
LinearMap
Translation
```

## 2D Coordinates
```@docs
Polar
PolarFromCartesian
CartesianFromPolar
```

## 3D Coordinates
```@docs
Cylindrical
Spherical
```

```@autodocs
Modules = [CoordinateTransformations]
```@docs
CartesianFromCylindrical
CartesianFromSpherical
CylindricalFromCartesian
CylindricalFromSpherical
SphericalFromCartesian
SphericalFromCylindrical
```
220 changes: 219 additions & 1 deletion docs/src/index.md
Original file line number Diff line number Diff line change
@@ -1 +1,219 @@
# CoordinateTransformations.jl
```@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
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([1.9999999999999996 0.5; -5.551115123125783e-16 2.0], [0.9999999999999999, 1.0000000000000002])
```

The points can be supplied as a collection of vectors or as a matrix with points as columns.

### 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)
8 changes: 7 additions & 1 deletion src/affine.jl
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
"""
abstract type AbstractAffineMap <: Transformation

`AbstractAffineMap` is the supertype of all affine transformations,
including linear transformations.
"""
abstract type AbstractAffineMap <: Transformation end

"""
Expand Down Expand Up @@ -114,7 +120,7 @@ end
"""
AffineMap(trans::Transformation, x0)

Create an Affine transformation corresponding to the differential transformation
Create an affine transformation corresponding to the differential transformation
of `x0 + dx` according to `trans`, i.e. the Affine transformation that is
locally most accurate in the vicinity of `x0`.
"""
Expand Down
Loading
Loading