Skip to content
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

refactor warp to be consistent with WarpedView #23

Closed
wants to merge 1 commit into from
Closed
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
2 changes: 2 additions & 0 deletions src/ImageTransformations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ include("resizing.jl")
include("warp.jl")
include("warpedview.jl")

@inline _getindex(A, v::StaticVector) = A[convert(Tuple, v)...]

center{T,N}(img::AbstractArray{T,N}) = SVector{N}(map(_center, indices(img)))
_center(ind::AbstractUnitRange) = (first(ind)+last(ind))/2

Expand Down
72 changes: 33 additions & 39 deletions src/warp.jl
Original file line number Diff line number Diff line change
@@ -1,46 +1,24 @@
# None of these types are provided by this package, so the following line seems unclean
# @inline Base.getindex(A::AbstractExtrapolation, v::StaticVector) = A[convert(Tuple, v)...]
# furthermore it would be ambiguous with getindex(::Extrapolation, xs...) after https://github.com/tlycken/Interpolations.jl/pull/141
@inline _getindex(A, v::StaticVector) = A[convert(Tuple, v)...]

warp(img::AbstractArray, args...) = warp(interpolate(img, BSpline(Linear()), OnGrid()), args...)

@inline _dst_type{T<:Colorant,S}(::Type{T}, ::Type{S}) = ccolor(T, S)
@inline _dst_type{T<:Number,S}(::Type{T}, ::Type{S}) = T

function warp{T,S}(::Type{T}, img::AbstractArray{S}, args...)
TCol = _dst_type(T,S)
TNorm = eltype(TCol)
apad, pad = Interpolations.prefilter(TNorm, TCol, img, typeof(BSpline(Linear())), typeof(OnGrid()))
itp = Interpolations.BSplineInterpolation(TNorm, apad, BSpline(Linear()), OnGrid(), pad)
warp(itp, args...)
end

@compat const FloatLike{T<:AbstractFloat} = Union{T,AbstractGray{T}}
@compat const FloatColorant{T<:AbstractFloat} = Colorant{T}

# The default values used by extrapolation for off-domain points
@inline _default_fill{T<:FloatLike}(::Type{T}) = convert(T, NaN)
@inline _default_fill{T<:FloatColorant}(::Type{T}) = nan(T)
@inline _default_fill{T}(::Type{T}) = zero(T)

warp{T}(img::AbstractInterpolation{T}, tform, fill=_default_fill(T)) = warp(extrapolate(img, fill), tform)

"""
warp(img, tform) -> imgw
warp(img, tform, [fill]) -> imgw

Transform the coordinates of `img`, returning a new `imgw` satisfying
`imgw[x] = img[tform(x)]`. `tform` should be defined using
CoordinateTransformations.jl.
[CoordinateTransformations.jl](https://github.com/FugroRoames/CoordinateTransformations.jl).

# Interpolation scheme

At off-grid points, `imgw` is calculated by interpolation. The default
is linear interpolation, used when `img` is a plain array, and `NaN`
values are used to indicate locations for which `tform(x)` was outside
the bounds of the input `img`. For more control over the interpolation
scheme---and how beyond-the-edge points are handled---pass it in as an
`AbstractExtrapolation` from Interpolations.jl.
At off-grid points, `imgw` is calculated by interpolation. The
default is linear interpolation, which is used when `img` is a
plain array and the `img[tform(x)]` is inbound. In the case
`tform(x)` maps to indices outside the original `img`, the value
/ extrapolation scheme denoted by the optional parameter `fill`
(which defaults to `NaN`) is used to indicate locations for which
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe specify NaN, if the element type supports it, and 0 otherwise?

`tform(x)` was outside the bounds of the input `img`.

For more control over the interpolation scheme --- and how
beyond-the-edge points are handled --- pass it in as an
`AbstractExtrapolation` from
[Interpolations.jl](https://github.com/JuliaMath/Interpolations.jl).

# The meaning of the coordinates

Expand Down Expand Up @@ -87,15 +65,31 @@ julia> indices(imgr)
(Base.OneTo(906),Base.OneTo(905))
```
"""
function warp(img::AbstractExtrapolation, tform)
function warp{T,N}(img::AbstractArray{T,N}, args...)
itp = Interpolations.BSplineInterpolation{T,N,typeof(img),Interpolations.BSpline{Interpolations.Linear},OnGrid,0}(img)
warp(itp, args...)
end

# The default values used by extrapolation for off-domain points
@compat const FloatLike{T<:AbstractFloat} = Union{T,AbstractGray{T}}
@compat const FloatColorant{T<:AbstractFloat} = Colorant{T}
@inline _default_fill{T<:FloatLike}(::Type{T}) = convert(T, NaN)
@inline _default_fill{T<:FloatColorant}(::Type{T}) = nan(T)
@inline _default_fill{T}(::Type{T}) = zero(T)

function warp{T}(img::AbstractInterpolation{T}, tform, fill = _default_fill(T))
warp(extrapolate(img, fill), tform)
end

function warp{T}(img::AbstractExtrapolation{T}, tform)
inds = autorange(img, tform)
out = OffsetArray(Array{eltype(img)}(map(length, inds)), inds)
out = OffsetArray(Array{T}(map(length, inds)), inds)
warp!(out, img, tform)
end

function warp!(out, img::AbstractExtrapolation, tform)
tinv = inv(tform)
for I in CartesianRange(indices(out))
@inbounds for I in CartesianRange(indices(out))
out[I] = _getindex(img, tinv(SVector(I.I)))
end
out
Expand Down
14 changes: 6 additions & 8 deletions test/warp.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,12 @@ img_camera = testimage("camera")
@testset "warp" begin
imgr = @inferred(warp(img_camera, tfm))
@test indices(imgr) == ref_inds
@test eltype(imgr) == eltype(img_camera)

for T in (Float32,Gray,RGB) # TODO: remove this signature completely
imgr = @inferred(warp(T, img_camera, tfm))
@test indices(imgr) == ref_inds
@test eltype(imgr) <: T
end

imgr = @inferred(warp(Gray, img_camera, tfm))
imgr2 = warp(Gray, imgr, inv(tfm))
imgr = @inferred(warp(img_camera, tfm, 1))
@test eltype(imgr) == eltype(img_camera)
imgr2 = @inferred warp(imgr, inv(tfm))
@test eltype(imgr2) == eltype(img_camera)
# look the same but are not similar enough to pass test
# @test imgr2[indices(img_camera)...] ≈ img_camera
end
Expand Down Expand Up @@ -87,6 +84,7 @@ ref_img_pyramid_quad = Float64[
@testset "warp" begin
imgr = warp(img_pyramid, tfm1)
@test indices(imgr) == (0:6, 0:6)
@test eltype(imgr) == eltype(img_pyramid)
# Use map and === because of the NaNs
@test nearlysame(round.(Float64.(parent(imgr)),3), round.(ref_img_pyramid,3))

Expand Down