Skip to content

Commit ca76546

Browse files
Andy FerrisChris Foster
authored andcommitted
Added cameramap convenience function
Can now automatically compose together camera coordinates and image coordinate transformations (from origin, focal length, etc).
1 parent 3f51541 commit ca76546

File tree

5 files changed

+88
-6
lines changed

5 files changed

+88
-6
lines changed

src/CoordinateTransformations.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export SphericalFromCartesian, CartesianFromSpherical,
2828
# Common transformations
2929
export AbstractAffineMap
3030
export AffineMap, LinearMap, Translation
31-
export PerspectiveMap
31+
export PerspectiveMap, cameramap
3232

3333
include("core.jl")
3434
include("coordinatesystems.jl")

src/affine.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ abstract AbstractAffineMap <: Transformation
88
Construct the `Translation` transformation for translating Cartesian points by
99
an offset `v = (dx, dy, ...)`
1010
"""
11-
immutable Translation{V <: AbstractVector} <: AbstractAffineMap
11+
immutable Translation{V} <: AbstractAffineMap
1212
v::V
1313
end
1414
Translation(x::Tuple) = Translation(SVector(x))
@@ -39,9 +39,9 @@ end
3939
LinearMap(M)
4040
4141
A general linear transformation, constructed using `LinearMap(M)`
42-
for any `AbstractMatrix` `M`.
42+
for any matrix-like object `M`.
4343
"""
44-
immutable LinearMap{M <: AbstractMatrix} <: AbstractAffineMap
44+
immutable LinearMap{M} <: AbstractAffineMap
4545
m::M
4646
end
4747
Base.show(io::IO, trans::LinearMap) = print(io, "LinearMap($(trans.m))") # TODO make this output more petite
@@ -95,7 +95,7 @@ converted into an affine approximation by linearizing about a point `x` using
9595
9696
For transformations which are already affine, `x` may be omitted.
9797
"""
98-
immutable AffineMap{M <: AbstractMatrix, V <: AbstractVector} <: AbstractAffineMap
98+
immutable AffineMap{M, V} <: AbstractAffineMap
9999
m::M
100100
v::V
101101
end

src/core.jl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,15 @@ Base.show(io::IO, trans::ComposedTransformation) = print(io, "($(trans.t1) ∘ $
3737
trans.t1(trans.t2(x))
3838
end
3939

40+
function Base.:(==)(trans1::ComposedTransformation, trans2::ComposedTransformation)
41+
(trans1.t1 == trans2.t1) && (trans1.t2 == trans2.t2)
42+
end
43+
44+
function Base.isapprox(trans1::ComposedTransformation, trans2::ComposedTransformation; kwargs...)
45+
isapprox(trans1.t1, trans2.t1; kwargs...) && isapprox(trans1.t2, trans2.t2; kwargs...)
46+
end
47+
48+
4049
"""
4150
compose(trans1, trans2)
4251
trans1 ∘ trans2

src/perspective.jl

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,19 @@
44
Construct a perspective transformation. The persepective transformation takes,
55
e.g., a point in 3D space and "projects" it onto a 2D virtual screen of an ideal
66
pinhole camera (at distance `1` away from the camera). The camera is oriented
7-
towards the positive-Z axis (or in general, along the final dimension).
7+
towards the positive-Z axis (or in general, along the final dimension) and the
8+
sign of the `x` and `y` components is preserved for objects in front of the
9+
camera (objects behind the camera are also projected and therefore inverted - it
10+
is up to the user to cull these as necessary).
811
912
This transformation is designed to be used in composition with other coordinate
1013
transformations, defining e.g. the position and orientation of the camera. For
1114
example:
1215
1316
cam_transform = PerspectiveMap() ∘ inv(AffineMap(cam_rotation, cam_position))
1417
screen_points = map(cam_transform, points)
18+
19+
(see also `cameramap`)
1520
"""
1621
immutable PerspectiveMap <: Transformation
1722
end
@@ -24,3 +29,67 @@ end
2429
@inline function (::PerspectiveMap)(v::StaticVector)
2530
return pop(v) * inv(v[end])
2631
end
32+
33+
Base.@pure Base.isapprox(::PerspectiveMap, ::PerspectiveMap; kwargs...) = true
34+
35+
"""
36+
cameramap(property = value, ...)
37+
38+
Create a transformation that takes points in real space (e.g. 3D) and projects
39+
them through a perspective transformation onto the focal plane of an ideal
40+
(pinhole) camera with the given properties.
41+
42+
All properties are optional. Valid properties include:
43+
44+
* `focal_length` (in physical units)
45+
* `pixel_size` (in physical units) or `pixel_size_x` and `pixel_size_y`
46+
* `offset_x` and `offset_y` (in pixels)
47+
* `origin` (a vector) and `orientation` (a rotation matrix)
48+
49+
By default, the camera looks towards the postive-`z` axis from `(0,0,0)` and
50+
the sign of the `x` and `y` components is preserved for objects in front of the
51+
camera (objects behind the camera are also projected and therefor inverted - it
52+
is up to the user to cull these as necessary).
53+
54+
If `origin` and `orientation` are specified, the camera is translated to `origin`
55+
and rotated by `orientation` before the perspective map is applied.
56+
57+
(see also `PerspectiveMap`)
58+
"""
59+
function cameramap(;focal_length = nothing,
60+
pixel_size = nothing,
61+
pixel_size_x = nothing,
62+
pixel_size_y = nothing,
63+
offset_x = nothing,
64+
offset_y = nothing,
65+
origin = nothing,
66+
orientation = nothing)
67+
68+
trans = PerspectiveMap()
69+
70+
if pixel_size === nothing # (this form of if-else-end is handled well by the compiler... the author is looking forward to v0.6 where !(::Bool) is pure...)
71+
else
72+
pixel_size_x = pixel_size
73+
pixel_size_y = pixel_size
74+
end
75+
76+
# Apply camera rotations, if necessary
77+
if origin === nothing && orientation === nothing
78+
else
79+
trans = trans inv(AffineMap(orientation, origin))
80+
end
81+
82+
# Apply camera scaling, if necessary
83+
if isa(focal_length, Void) && isa(pixel_size_x, Void) && isa(pixel_size_y, Void)
84+
else
85+
trans = LinearMap(UniformScaling(focal_length/pixel_size)) trans
86+
end
87+
88+
# Apply pixel offset, if necessary
89+
if isa(offset_x, Void) && isa(offset_y, Void)
90+
else
91+
trans = Translation(SVector(-offset_x, -offset_y)) trans
92+
end
93+
94+
return trans
95+
end

test/runtests.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@ using Base.Test
33
using ForwardDiff: Dual, partials
44
using StaticArrays
55

6+
# See https://github.com/JuliaLang/julia/issues/18858
7+
Base.isapprox(a::UniformScaling, b::UniformScaling; kwargs...) = isapprox(a.λ, b.λ; kwargs...)
8+
69
@testset "CoordinateTransformations" begin
710

811
include("core.jl")
912
include("coordinatesystems.jl")
1013
include("affine.jl")
14+
include("perspective.jl")
1115

1216
end

0 commit comments

Comments
 (0)