Skip to content

Commit a2c7197

Browse files
authored
Construct affine transforms from point-pairs (#87)
Given sets of point pairs `from_points => to_points`, compute the affine transformation that best reproduces the observed mapping. Closes #30
1 parent 98d89f3 commit a2c7197

File tree

3 files changed

+48
-0
lines changed

3 files changed

+48
-0
lines changed

README.md

+13
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,19 @@ defined by a composition of a translation and a linear transformation. An
171171
and a translation, e.g. `Translation(v) ∘ LinearMap(v)` (or any combination of
172172
`LinearMap`, `Translation` and `AffineMap`).
173173

174+
`AffineMap`s can be constructed to fit point pairs `from_points => to_points`:
175+
176+
```julia
177+
julia> from_points = [[0, 0], [1, 0], [0, 1]];
178+
179+
julia> to_points = [[1, 1], [3, 1], [1.5, 3]];
180+
181+
julia> AffineMap(from_points => to_points)
182+
AffineMap([1.9999999999999996 0.4999999999999999; -5.551115123125783e-16 2.0], [0.9999999999999999, 1.0000000000000002])
183+
```
184+
185+
The points can be supplied as a collection of vectors or as a matrix with points as columns.
186+
174187
#### Perspective transformations
175188

176189
The perspective transformation maps real-space coordinates to those on a virtual

src/affine.jl

+19
Original file line numberDiff line numberDiff line change
@@ -209,3 +209,22 @@ recenter(trans::AbstractMatrix, origin::Union{AbstractVector, Tuple}) = recenter
209209

210210
transform_deriv(trans::AffineMap, x) = trans.linear
211211
# TODO transform_deriv_params
212+
213+
"""
214+
AffineMap(from_points => to_points) → trans
215+
216+
Create an Affine transformation that approximately maps the `from` points to the `to` points.
217+
At least `n+1` non-degenerate points are required to map an `n`-dimensional space.
218+
If there are more points than this, the transformation will be over-determined and a least-squares
219+
solution will be computed.
220+
"""
221+
function AffineMap((from_points,to_points)::Pair)
222+
M = column_matrix(to_points) * pinv(column_matrix(from_points, 1))
223+
AffineMap(M[:, 1:end-1], M[:, end])
224+
end
225+
226+
column_matrix(points::AbstractMatrix) = points
227+
column_matrix(points) = reduce(hcat, points)
228+
229+
column_matrix(points::AbstractMatrix, lastval) = vcat(points, fill(lastval, 1, axes(points, 2)))
230+
column_matrix(points, lastval) = reduce(hcat, [vcat(point, lastval) for point in points])

test/affine.jl

+16
Original file line numberDiff line numberDiff line change
@@ -129,4 +129,20 @@ end
129129
@test expected.origin result.origin
130130
@test expected.direction result.direction
131131
end
132+
133+
@testset "construction from points" begin
134+
M = [1.0 2.0; 3.0 4.0]
135+
v = [-1.0, 1.0]
136+
A = AffineMap(M,v)
137+
from_points = [[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]]
138+
to_points = map(A, from_points)
139+
A2 = AffineMap(from_points => to_points)
140+
@test A2 A
141+
A2 = AffineMap(reduce(hcat, from_points) => reduce(hcat, to_points))
142+
@test A2 A
143+
from_points = ([0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0])
144+
to_points = map(A, from_points)
145+
A2 = AffineMap(from_points => to_points)
146+
@test A2 A
147+
end
132148
end

0 commit comments

Comments
 (0)