Skip to content

Commit

Permalink
Merge pull request #116 from JuliaImages/jc/rebox
Browse files Browse the repository at this point in the history
- upgrade legacy methods with cleaner keyword alternatives
- rewrite/update the docstring of `warp` and related functions
  • Loading branch information
johnnychen94 authored May 20, 2021
2 parents 9257562 + 3fe10ed commit eb94fc2
Show file tree
Hide file tree
Showing 14 changed files with 616 additions and 476 deletions.
2 changes: 0 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ version = "0.8.12"
AxisAlgorithms = "13072b0f-2c55-5437-9ae7-d433b7a33950"
ColorVectorSpace = "c3611d14-8923-5661-9e6a-0046d554d3a4"
CoordinateTransformations = "150eb455-5306-5404-9cee-2592286d6298"
IdentityRanges = "bbac6d45-d8f3-5730-bfe4-7a449cd117ca"
ImageCore = "a09fc81d-aa75-5fe9-8630-4744c3626534"
Interpolations = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59"
OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
Expand All @@ -17,7 +16,6 @@ StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
AxisAlgorithms = "1.0"
ColorVectorSpace = "0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9"
CoordinateTransformations = "0.5, 0.6"
IdentityRanges = "0.3"
ImageCore = "0.8.1, 0.9"
Interpolations = "0.9, 0.10, 0.11, 0.12, 0.13"
OffsetArrays = "0.10, 0.11, 1.0.1"
Expand Down
4 changes: 3 additions & 1 deletion src/ImageTransformations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ using StaticArrays
using Interpolations, AxisAlgorithms
using OffsetArrays
using ColorVectorSpace
using IdentityRanges

import Base: eltype, size, length
using Base: tail, Indices
Expand All @@ -32,7 +31,10 @@ include("interpolations.jl")
include("warp.jl")
include("warpedview.jl")
include("invwarpedview.jl")
include("compat.jl")
include("deprecated.jl")

# TODO: move to warp.jl
@inline _getindex(A, v::StaticVector) = A[Tuple(v)...]
@inline _getindex(A::AbstractInterpolation, v::StaticVector) = A(Tuple(v)...)
@inline _getindex(A, v) = A[v...]
Expand Down
41 changes: 34 additions & 7 deletions src/autorange.jl
Original file line number Diff line number Diff line change
@@ -1,8 +1,38 @@
function autorange(img, tform)
R = CartesianIndices(axes(img))
autorange(R, tform)
end
"""
autorange(A::AbstractArray, tform::Transformation)
For given transformation `tform`, return the "smallest" range indices that
preserves all information from `A` after applying `tform`.
# Examples
For transformation that preserves the array size, `autorange` is equivalent to `axes(A)`.
```jldoctest; setup=:(using ImageTransformations: autorange; using CoordinateTransformations, Rotations, ImageTransformations)
A = rand(5, 5)
tform = IdentityTransformation()
autorange(A, tform) == axes(A)
# output
true
```
The diffrence shows up when `tform` enlarges the input array `A`. In the following example, we need
at least `(0:6, 0:6)` as the range indices to get all data of `A`:
```jldoctest; setup=:(using ImageTransformations: autorange; using CoordinateTransformations, Rotations, ImageTransformations)
A = rand(5, 5)
tform = recenter(RotMatrix(pi/8), center(A))
autorange(A, tform)
# output
(0:6, 0:6)
```
!!! note
This function is not exported; it is mainly for internal usage to infer the default indices.
"""
autorange(A::AbstractArray, tform) = autorange(CartesianIndices(A), tform)
function autorange(R::CartesianIndices, tform)
tform = _round(tform)
mn = mx = tform(SVector(first(R).I))
Expand Down Expand Up @@ -91,9 +121,6 @@ function _round(tform::T; kwargs...) where T<:CoordinateTransformations.Transfor
end
T(rounded_fields...)
end
if isdefined(Base, :ComposedFunction)
_round(tform::ComposedFunction; kwargs...) = _round(tform.outer; kwargs...) _round(tform.inner; kwargs...)
end
_round(tform; kwargs...) = tform

__round(x; kwargs...) = x
Expand Down
25 changes: 25 additions & 0 deletions src/compat.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# This file includes two kinds of codes
# - Codes for backward compatibility
# - Glue codes that might nolonger be necessary in the future

# patch for issue #110
if isdefined(Base, :ComposedFunction) # Julia >= 1.6.0-DEV.85
# https://github.com/JuliaLang/julia/pull/37517
_round(tform::ComposedFunction; kwargs...) = _round(tform.outer; kwargs...) _round(tform.inner; kwargs...)
end

@static if !isdefined(Base, :IdentityUnitRange)
const IdentityUnitRange = Base.Slice
else
using Base: IdentityUnitRange
end

@static if VERSION < v"1.1"
@inline isnothing(x) = x === nothing
end

# FIXME: upstream https://github.com/JuliaGraphics/ColorVectorSpace.jl/issues/75
@inline _nan(::Type{HSV{Float16}}) = HSV{Float16}(NaN16,NaN16,NaN16)
@inline _nan(::Type{HSV{Float32}}) = HSV{Float32}(NaN32,NaN32,NaN32)
@inline _nan(::Type{HSV{Float64}}) = HSV{Float64}(NaN,NaN,NaN)
@inline _nan(::Type{T}) where {T} = nan(T)
41 changes: 41 additions & 0 deletions src/deprecated.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# BEGIN 0.9 deprecations

@deprecate warp(img::AbstractArray, tform::Transformation, method::MethodType, ) warp(img, tform; method=method)
@deprecate warp(img::AbstractArray, tform::Transformation, fillvalue::FillType) warp(img, tform; fillvalue=fillvalue)
@deprecate warp(img::AbstractArray, tform::Transformation, method::MethodType, fillvalue::FillType) warp(img, tform; method=method, fillvalue=fillvalue)
@deprecate warp(img::AbstractArray, tform::Transformation, fillvalue::FillType, method::MethodType) warp(img, tform; method=method, fillvalue=fillvalue)
@deprecate warp(img::AbstractArray, tform::Transformation, inds, method::MethodType, ) warp(img, tform, inds; method=method)
@deprecate warp(img::AbstractArray, tform::Transformation, inds, fillvalue::FillType) warp(img, tform, inds; fillvalue=fillvalue)
@deprecate warp(img::AbstractArray, tform::Transformation, inds, method::MethodType, fillvalue::FillType) warp(img, tform, inds; method=method, fillvalue=fillvalue)
@deprecate warp(img::AbstractArray, tform::Transformation, inds, fillvalue::FillType, method::MethodType) warp(img, tform, inds; method=method, fillvalue=fillvalue)

@deprecate imrotate(img::AbstractArray, θ::Real, method::MethodType ) imrotate(img, θ; method=method)
@deprecate imrotate(img::AbstractArray, θ::Real, fillvalue::FillType) imrotate(img, θ; fillvalue=fillvalue)
@deprecate imrotate(img::AbstractArray, θ::Real, method::MethodType, fillvalue::FillType) imrotate(img, θ; method=method, fillvalue=fillvalue)
@deprecate imrotate(img::AbstractArray, θ::Real, fillvalue::FillType, method::MethodType) imrotate(img, θ; method=method, fillvalue=fillvalue)
@deprecate imrotate(img::AbstractArray, θ::Real, inds, method::MethodType ) imrotate(img, θ, inds; method=method)
@deprecate imrotate(img::AbstractArray, θ::Real, inds, fillvalue::FillType) imrotate(img, θ, inds; fillvalue=fillvalue)
@deprecate imrotate(img::AbstractArray, θ::Real, inds, method::MethodType, fillvalue::FillType) imrotate(img, θ, inds; method=method, fillvalue=fillvalue)
@deprecate imrotate(img::AbstractArray, θ::Real, inds, fillvalue::FillType, method::MethodType) imrotate(img, θ, inds; method=method, fillvalue=fillvalue)

@deprecate WarpedView(img::AbstractArray, tform::Transformation, method::MethodType, ) WarpedView(img, tform; method=method)
@deprecate WarpedView(img::AbstractArray, tform::Transformation, fillvalue::FillType) WarpedView(img, tform; fillvalue=fillvalue)
@deprecate WarpedView(img::AbstractArray, tform::Transformation, method::MethodType, fillvalue::FillType) WarpedView(img, tform; method=method, fillvalue=fillvalue)
@deprecate WarpedView(img::AbstractArray, tform::Transformation, fillvalue::FillType, method::MethodType) WarpedView(img, tform; method=method, fillvalue=fillvalue)
@deprecate WarpedView(img::AbstractArray, tform::Transformation, inds, method::MethodType, ) WarpedView(img, tform, inds; method=method)
@deprecate WarpedView(img::AbstractArray, tform::Transformation, inds, fillvalue::FillType) WarpedView(img, tform, inds; fillvalue=fillvalue)
@deprecate WarpedView(img::AbstractArray, tform::Transformation, inds, method::MethodType, fillvalue::FillType) WarpedView(img, tform, inds; method=method, fillvalue=fillvalue)
@deprecate WarpedView(img::AbstractArray, tform::Transformation, inds, fillvalue::FillType, method::MethodType) WarpedView(img, tform, inds; method=method, fillvalue=fillvalue)

@deprecate warpedview(args...; kwargs...) WarpedView(args...; kwargs...)

@deprecate invwarpedview(img::AbstractArray, tinv::Transformation, method::MethodType, ) invwarpedview(img, tinv; method=method)
@deprecate invwarpedview(img::AbstractArray, tinv::Transformation, fillvalue::FillType) invwarpedview(img, tinv; fillvalue=fillvalue)
@deprecate invwarpedview(img::AbstractArray, tinv::Transformation, method::MethodType, fillvalue::FillType) invwarpedview(img, tinv; method=method, fillvalue=fillvalue)
@deprecate invwarpedview(img::AbstractArray, tinv::Transformation, fillvalue::FillType, method::MethodType) invwarpedview(img, tinv; method=method, fillvalue=fillvalue)
@deprecate invwarpedview(img::AbstractArray, tinv::Transformation, inds, method::MethodType, ) invwarpedview(img, tinv, inds; method=method)
@deprecate invwarpedview(img::AbstractArray, tinv::Transformation, inds, fillvalue::FillType) invwarpedview(img, tinv, inds; fillvalue=fillvalue)
@deprecate invwarpedview(img::AbstractArray, tinv::Transformation, inds, method::MethodType, fillvalue::FillType) invwarpedview(img, tinv, inds; method=method, fillvalue=fillvalue)
@deprecate invwarpedview(img::AbstractArray, tinv::Transformation, inds, fillvalue::FillType, method::MethodType) invwarpedview(img, tinv, inds; method=method, fillvalue=fillvalue)

# END 0.9 deprecations
72 changes: 21 additions & 51 deletions src/interpolations.jl
Original file line number Diff line number Diff line change
@@ -1,72 +1,42 @@
# FIXME: upstream https://github.com/JuliaGraphics/ColorVectorSpace.jl/issues/75
@inline _nan(::Type{HSV{Float16}}) = HSV{Float16}(NaN16,NaN16,NaN16)
@inline _nan(::Type{HSV{Float32}}) = HSV{Float32}(NaN32,NaN32,NaN32)
@inline _nan(::Type{HSV{Float64}}) = HSV{Float64}(NaN,NaN,NaN)
@inline _nan(::Type{T}) where {T} = nan(T)

#wraper to deal with degree or interpolation types
# A helper function to let our `method` keyword correctly understands `Degree` inputs.
@inline wrap_BSpline(itp::Interpolations.InterpolationType) = itp
@inline wrap_BSpline(degree::Interpolations.Degree) = BSpline(degree)

# The default values used by extrapolation for off-domain points
const FillType = Union{Number,Colorant,Flat,Periodic,Reflect}
const FloatLike{T<:AbstractFloat} = Union{T,AbstractGray{T}}
const FloatColorant{T<:AbstractFloat} = Colorant{T}
@inline _default_fill(::Type{T}) where {T<:FloatLike} = convert(T, NaN)
@inline _default_fill(::Type{T}) where {T<:FloatColorant} = _nan(T)
@inline _default_fill(::Type{T}) where {T} = zero(T)
@inline _default_fillvalue(::Type{T}) where {T<:FloatLike} = convert(T, NaN)
@inline _default_fillvalue(::Type{T}) where {T<:FloatColorant} = _nan(T)
@inline _default_fillvalue(::Type{T}) where {T} = zero(T)

@inline _make_compatible(T, fill) = fill
@inline _make_compatible(::Type{T}, fill::Number) where {T} = T(fill)

Interpolations.tweight(A::AbstractArray{C}) where C<:Colorant{T} where T = T

box_extrapolation(etp::AbstractExtrapolation) = etp

function box_extrapolation(itp::AbstractInterpolation{T}, fill::FillType = _default_fill(T); kwargs...) where T
etp = extrapolate(itp, _make_compatible(T, fill))
box_extrapolation(etp)
end

function box_extrapolation(parent::AbstractArray, args...; method::Union{Interpolations.Degree,Interpolations.InterpolationType}=Linear(), kwargs...)
if typeof(method)<:Interpolations.Degree
box_extrapolation(parent, method, args...)
else
itp = interpolate(parent, method)
box_extrapolation(itp, args...)
end
end

function box_extrapolation(parent::AbstractArray{T,N}, degree::Interpolations.Degree, args...; method::Union{Interpolations.Degree,Interpolations.InterpolationType}=Linear(), kwargs...) where {T,N}
itp = interpolate(parent, BSpline(degree))
box_extrapolation(itp, args...)
end

function box_extrapolation(parent::AbstractArray, degree::D, args...; method::Union{Interpolations.Degree,Interpolations.InterpolationType}=Linear(), kwargs...) where D<:Union{Linear,Constant}
axs = axes(parent)
T = typeof(zero(Interpolations.tweight(parent))*zero(eltype(parent)))
itp = Interpolations.BSplineInterpolation{T,ndims(parent),typeof(parent),BSpline{D},typeof(axs)}(parent, axs, BSpline(degree))
box_extrapolation(itp, args...)
end

function box_extrapolation(parent::AbstractArray, fill::FillType; kwargs...)
box_extrapolation(parent, Linear(), fill)
end

function box_extrapolation(itp::AbstractInterpolation, degree::Union{Linear,Constant}, args...; kwargs...)
throw(ArgumentError("Boxing an interpolation in another interpolation is discouraged. Did you specify the parameter \"$degree\" on purpose?"))
end
const MethodType = Union{Interpolations.Degree, Interpolations.InterpolationType}

function box_extrapolation(itp::AbstractInterpolation, degree::Interpolations.Degree, args...; kwargs...)
throw(ArgumentError("Boxing an interpolation in another interpolation is discouraged. Did you specify the parameter \"$degree\" on purpose?"))
function box_extrapolation(
parent::AbstractArray;
fillvalue::FillType = _default_fillvalue(eltype(parent)),
method=Linear(),
kwargs...)
T = typeof(zero(Interpolations.tweight(parent)) * zero(eltype(parent)))
itp = maybe_lazy_interpolate(T, parent, method)
extrapolate(itp, _make_compatible(T, fillvalue))
end
box_extrapolation(etp::AbstractExtrapolation) = etp
box_extrapolation(itp::AbstractInterpolation{T}; fillvalue=_default_fillvalue(T)) where T =
extrapolate(itp, _make_compatible(T, fillvalue))

function box_extrapolation(itp::AbstractExtrapolation, fill::FillType; kwargs...)
throw(ArgumentError("Boxing an extrapolation in another extrapolation is discouraged. Did you specify the parameter \"$fill\" on purpose?"))
@inline function maybe_lazy_interpolate(::Type{T}, A::AbstractArray, degree::D) where {T, D<:Union{Linear, Constant}}
axs = axes(A)
return Interpolations.BSplineInterpolation{T,ndims(A),typeof(A),BSpline{D},typeof(axs)}(A, axs, BSpline(degree))
end

function box_extrapolation(parent::AbstractArray, itp::Interpolations.InterpolationType; kwargs...)
throw(ArgumentError("Argument support for interpolation is not supported. Are you looking for the method keyword to pass an interpolation method?"))
@inline function maybe_lazy_interpolate(::Type{T}, A::AbstractArray, method::MethodType) where T
return interpolate(A, wrap_BSpline(method))
end

# This is type-piracy, but necessary if we want Interpolations to be
Expand Down
100 changes: 26 additions & 74 deletions src/invwarpedview.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@
InvWarpedView(img, tinv, [indices]) -> wv
Create a view of `img` that lazily transforms any given index `I`
passed to `wv[I]` to correspond to `img[inv(tinv)(I)]`. While
technically this approach is known as backward mode warping, note
that `InvWarpedView` is created by supplying the forward
transformation
passed to `wv[I]` so that `wv[I] == img[inv(tinv)(I)]`.
The conceptual difference to [`WarpedView`](@ref) is that
`InvWarpedView` is intended to be used when reasoning about the
Expand All @@ -14,20 +11,18 @@ Furthermore, `InvWarpedView` allows simple nesting of
transformations, in which case the transformations will be
composed into a single one.
The optional parameter `indices` can be used to specify the
domain of the resulting `wv`. By default the indices are computed
in such a way that `wv` contains all the original pixels in
`img`.
See [`invwarpedview`](@ref) for a convenient constructor of `InvWarpedView`.
see [`invwarpedview`](@ref) for more information.
For detailed explaination of warp, associated arguments and parameters,
please refer to [`warp`](@ref).
"""
struct InvWarpedView{T,N,A,F,I,FI<:Transformation,E} <: AbstractArray{T,N}
inner::WarpedView{T,N,A,F,I,E}
inverse::FI
end

function InvWarpedView(inner::WarpedView{T,N,TA,F,I,E}) where {T,N,TA,F,I,E}
tinv = inv(inner.transform)
tinv = _round(inv(inner.transform))
InvWarpedView{T,N,TA,F,I,typeof(tinv),E}(inner, tinv)
end

Expand Down Expand Up @@ -67,78 +62,35 @@ function Base.showarg(io::IO, A::InvWarpedView, toplevel)
end

"""
invwarpedview(img, tinv, [indices], [degree = Linear()], [fill = NaN]) -> wv
invwarpedview(img, tinv, [indices]; kwargs...) -> wv
Create a view of `img` that lazily transforms any given index `I`
passed to `wv[I]` to correspond to `img[inv(tinv)(I)]`. While
technically this approach is known as backward mode warping, note
that `InvWarpedView` is created by supplying the forward
transformation. The given transformation `tinv` must accept a
`SVector` as input and support `inv(tinv)`. A useful package to
create a wide variety of such transformations is
[CoordinateTransformations.jl](https://github.com/FugroRoames/CoordinateTransformations.jl).
When invoking `wv[I]`, values for `img` must be reconstructed at
arbitrary locations `inv(tinv)(I)`. `InvWarpedView` serves as a
wrapper around [`WarpedView`](@ref) which takes care of
interpolation and extrapolation. The parameters `degree` and
`fill` can be used to specify the b-spline degree and the
extrapolation scheme respectively.
The optional parameter `indices` can be used to specify the
domain of the resulting `wv`. By default the indices are computed
in such a way that `wv` contains all the original pixels in
`img`.
"""
@inline invwarpedview(A::AbstractArray, tinv::Transformation, args...) =
InvWarpedView(A, tinv, args...)

function invwarpedview(
A::AbstractArray{T},
tinv::Transformation,
degree::Union{Linear,Constant},
fill::FillType = _default_fill(T)) where T
invwarpedview(box_extrapolation(A, degree, fill), tinv)
end
passed to `wv[I]` so that `wv[I] == img[inv(tinv)(I)]`.
function invwarpedview(
A::AbstractArray{T},
tinv::Transformation,
indices::Tuple,
degree::Union{Linear,Constant},
fill::FillType = _default_fill(T)) where T
invwarpedview(box_extrapolation(A, degree, fill), tinv, indices)
end
Except for the lazy evaluation, the following two lines are equivalent:
function invwarpedview(
A::AbstractArray,
tinv::Transformation,
fill::FillType)
invwarpedview(A, tinv, Linear(), fill)
end
```julia
warp(img, inv(tform), [indices]; kwargs...)
invwarpedview(img, tform, [indices]; kwargs...)
```
function invwarpedview(
A::AbstractArray,
tinv::Transformation,
indices::Tuple,
fill::FillType)
invwarpedview(A, tinv, indices, Linear(), fill)
For detailed explaination of warp, associated arguments and parameters,
please refer to [`warp`](@ref).
"""
function invwarpedview(A::AbstractArray, tinv::Transformation, indices::Tuple=autorange(A, tinv); kwargs...)
InvWarpedView(box_extrapolation(A; kwargs...), tinv, indices)
end

function invwarpedview(
inner_view::SubArray{T,N,W,I},
tinv::Transformation) where {T,N,W<:InvWarpedView,I<:Tuple{Vararg{AbstractUnitRange}}}
inner = parent(inner_view)
new_inner = InvWarpedView(inner, tinv, autorange(inner, tinv))
inds = autorange(CartesianIndices(inner_view.indices), tinv)
view(new_inner, map(x->IdentityRange(first(x),last(x)), inds)...)
# For SubArray:
# 1. We can exceed the boundary of SubArray by using its parent and thus trick Interpolations in
# order to get better extrapolation result around the border. Otherwise it will just fill it.
# 2. For default indices, we use `IdentityUnitRange`, which guarantees `r[i] == i`, to preserve the view indices.
function invwarpedview(A::SubArray, tinv::Transformation; kwargs...)
default_indices = map(IdentityUnitRange, autorange(CartesianIndices(A.indices), tinv))
invwarpedview(A, tinv, default_indices; kwargs...)
end

function invwarpedview(
inner_view::SubArray{T,N,W,I},
tinv::Transformation,
indices::Tuple) where {T,N,W<:InvWarpedView,I<:Tuple{Vararg{AbstractUnitRange}}}
inner = parent(inner_view)
function invwarpedview(A::SubArray, tinv::Transformation, indices::Tuple; kwargs...)
inner = parent(A)
new_inner = InvWarpedView(inner, tinv, autorange(inner, tinv))
view(new_inner, indices...)
end
Loading

0 comments on commit eb94fc2

Please sign in to comment.