From 0551a0dc0cab7f1e2a50dd0790604deeb6b0c332 Mon Sep 17 00:00:00 2001 From: Christof Stocker Date: Thu, 4 May 2017 23:39:31 +0200 Subject: [PATCH] streamline API for warp and WarpedView and add InvWarpedView (#24) * refactor warp to be consistent with WarpedView and perform backward mode * clean up WarpedView API and add warpedview * add InvWarpedView as wrapper around WarpedView * add ImageInTerminal style visual tests * enable color on travis and appveyor * improve and add tests for warp helper functions * implement a deprecation strategy for old warp * add warp support for OneTo inds * add docstring for warpedview and invwarpedview --- .travis.yml | 2 +- appveyor.yml | 2 +- src/ImageTransformations.jl | 9 +- src/interpolations.jl | 58 ++++ src/invwarpedview.jl | 119 +++++++ src/resizing.jl | 1 + src/warp.jl | 147 +++++---- src/warpedview.jl | 150 ++++++--- test/REQUIRE | 1 + test/deprecations.jl | 7 + test/interpolations.jl | 105 ++++++ test/reference/invwarpedview_cameraman.txt | 17 + .../invwarpedview_cameraman_rotate_r22deg.txt | 11 + test/reference/warp_cameraman.txt | 17 + .../warp_cameraman_rotate_r22deg.txt | 11 + .../warp_cameraman_rotate_r22deg_crop.txt | 17 + ...arp_cameraman_rotate_r22deg_crop_white.txt | 17 + .../warp_cameraman_rotate_r22deg_flat.txt | 11 + .../warp_cameraman_rotate_r22deg_periodic.txt | 11 + .../warp_cameraman_rotate_r22deg_white.txt | 11 + test/runtests.jl | 61 +++- test/warp.jl | 309 ++++++++++++++++-- 22 files changed, 954 insertions(+), 140 deletions(-) create mode 100644 src/interpolations.jl create mode 100644 src/invwarpedview.jl create mode 100644 test/deprecations.jl create mode 100644 test/interpolations.jl create mode 100644 test/reference/invwarpedview_cameraman.txt create mode 100644 test/reference/invwarpedview_cameraman_rotate_r22deg.txt create mode 100644 test/reference/warp_cameraman.txt create mode 100644 test/reference/warp_cameraman_rotate_r22deg.txt create mode 100644 test/reference/warp_cameraman_rotate_r22deg_crop.txt create mode 100644 test/reference/warp_cameraman_rotate_r22deg_crop_white.txt create mode 100644 test/reference/warp_cameraman_rotate_r22deg_flat.txt create mode 100644 test/reference/warp_cameraman_rotate_r22deg_periodic.txt create mode 100644 test/reference/warp_cameraman_rotate_r22deg_white.txt diff --git a/.travis.yml b/.travis.yml index 1697ed6..fdaf071 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ notifications: # uncomment the following lines to override the default test script script: - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi - - julia -e 'Pkg.clone(pwd()); Pkg.build("ImageTransformations"); Pkg.test("ImageTransformations"; coverage=VERSION >= v"0.6.0-alpha")' + - julia --color=yes -e 'Pkg.clone(pwd()); Pkg.build("ImageTransformations"); Pkg.test("ImageTransformations"; coverage=VERSION >= v"0.6.0-alpha")' after_success: # push coverage results to Codecov - julia -e 'if VERSION >= v"0.6.0-alpha" cd(Pkg.dir("ImageTransformations")); Pkg.add("Coverage"); using Coverage; Codecov.submit(Codecov.process_folder()); end' diff --git a/appveyor.yml b/appveyor.yml index 9306951..aeae9ef 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -31,4 +31,4 @@ build_script: Pkg.clone(pwd(), \"ImageTransformations\"); Pkg.build(\"ImageTransformations\")" test_script: - - C:\projects\julia\bin\julia -e "Pkg.test(\"ImageTransformations\")" + - C:\projects\julia\bin\julia --color=yes -e "Pkg.test(\"ImageTransformations\")" diff --git a/src/ImageTransformations.jl b/src/ImageTransformations.jl index 4f254de..c53fdc3 100644 --- a/src/ImageTransformations.jl +++ b/src/ImageTransformations.jl @@ -21,12 +21,19 @@ export imresize, center, warp, - WarpedView + WarpedView, + warpedview, + InvWarpedView, + invwarpedview include("autorange.jl") include("resizing.jl") +include("interpolations.jl") include("warp.jl") include("warpedview.jl") +include("invwarpedview.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 diff --git a/src/interpolations.jl b/src/interpolations.jl new file mode 100644 index 0000000..47aa1eb --- /dev/null +++ b/src/interpolations.jl @@ -0,0 +1,58 @@ +# 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{T}(::Type{T}) = nan(T) + +# The default values used by extrapolation for off-domain points +@compat const FillType = Union{Number,Colorant,Flat,Periodic,Reflect} +@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) + +box_extrapolation(etp::AbstractExtrapolation) = etp + +function box_extrapolation{T}(itp::AbstractInterpolation{T}, fill::FillType = _default_fill(T)) + etp = extrapolate(itp, fill) + box_extrapolation(etp) +end + +function box_extrapolation{T,N,D<:Union{Linear,Constant}}(parent::AbstractArray{T,N}, degree::D = Linear(), args...) + itp = Interpolations.BSplineInterpolation{T,N,typeof(parent),BSpline{D},OnGrid,0}(parent) + box_extrapolation(itp, args...) +end + +function box_extrapolation{T,N}(parent::AbstractArray{T,N}, degree::Interpolations.Degree, args...) + itp = interpolate(parent, BSpline(degree), OnGrid()) + box_extrapolation(itp, args...) +end + +function box_extrapolation(parent::AbstractArray, fill::FillType) + box_extrapolation(parent, Linear(), fill) +end + +function box_extrapolation(itp::AbstractInterpolation, degree::Union{Linear,Constant}, args...) + throw(ArgumentError("Boxing an interpolation in another interpolation is discouraged. Did you specify the parameter \"$degree\" on purpose?")) +end + +function box_extrapolation(itp::AbstractInterpolation, degree::Interpolations.Degree, args...) + throw(ArgumentError("Boxing an interpolation in another interpolation is discouraged. Did you specify the parameter \"$degree\" on purpose?")) +end + +function box_extrapolation(itp::AbstractExtrapolation, fill::FillType) + throw(ArgumentError("Boxing an extrapolation in another extrapolation is discouraged. Did you specify the parameter \"$fill\" on purpose?")) +end + +# This is type-piracy, but necessary if we want Interpolations to be +# independent of OffsetArrays. +function AxisAlgorithms.A_ldiv_B_md!(dest::OffsetArray, F, src::OffsetArray, dim::Integer, b::AbstractVector) + indsdim = indices(parent(src), dim) + indsF = indices(F)[2] + if indsF == indsdim + AxisAlgorithms.A_ldiv_B_md!(parent(dest), F, parent(src), dim, b) + return dest + end + throw(DimensionMismatch("indices $(indices(parent(src))) do not match $(indices(F))")) +end diff --git a/src/invwarpedview.jl b/src/invwarpedview.jl new file mode 100644 index 0000000..2cefcb5 --- /dev/null +++ b/src/invwarpedview.jl @@ -0,0 +1,119 @@ +""" + 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 + +The conceptual difference to [`WarpedView`](@ref) is that +`InvWarpedView` is intended to be used when reasoning about the +image is more convenient that reasoning about the indices. +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 more information. +""" +immutable 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{T,N,TA,F,I,E}(inner::WarpedView{T,N,TA,F,I,E}) + tinv = inv(inner.transform) + InvWarpedView{T,N,TA,F,I,typeof(tinv),E}(inner, tinv) +end + +function InvWarpedView(A::AbstractArray, tinv::Transformation, inds::Tuple = autorange(A, tinv)) + InvWarpedView(WarpedView(A, inv(tinv), inds), tinv) +end + +function InvWarpedView(inner::InvWarpedView, outer_tinv::Transformation) + tinv = compose(outer_tinv, inner.inverse) + InvWarpedView(parent(inner), tinv) +end + +Base.parent(A::InvWarpedView) = parent(A.inner) +@inline Base.indices(A::InvWarpedView) = indices(A.inner) + +@compat Compat.IndexStyle{T<:InvWarpedView}(::Type{T}) = IndexCartesian() +@inline Base.getindex{T,N}(A::InvWarpedView{T,N}, I::Vararg{Int,N}) = A.inner[I...] + +Base.size(A::InvWarpedView) = size(A.inner) +Base.size(A::InvWarpedView, d) = size(A.inner, d) + +function ShowItLikeYouBuildIt.showarg(io::IO, A::InvWarpedView) + print(io, "InvWarpedView(") + showarg(io, parent(A)) + print(io, ", ") + print(io, A.inverse) + print(io, ')') +end + +Base.summary(A::InvWarpedView) = summary_build(A) + +""" + invwarpedview(img, tinv, [indices], [degree = Linear()], [fill = NaN]) -> 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{T}( + A::AbstractArray{T}, + tinv::Transformation, + degree::Union{Linear,Constant}, + fill::FillType = _default_fill(T)) + invwarpedview(box_extrapolation(A, degree, fill), tinv) +end + +function invwarpedview{T}( + A::AbstractArray{T}, + tinv::Transformation, + indices::Tuple, + degree::Union{Linear,Constant}, + fill::FillType = _default_fill(T)) + invwarpedview(box_extrapolation(A, degree, fill), tinv, indices) +end + +function invwarpedview( + A::AbstractArray, + tinv::Transformation, + fill::FillType) + invwarpedview(A, tinv, Linear(), fill) +end + +function invwarpedview( + A::AbstractArray, + tinv::Transformation, + indices::Tuple, + fill::FillType) + invwarpedview(A, tinv, indices, Linear(), fill) +end diff --git a/src/resizing.jl b/src/resizing.jl index 67ba6d8..73b4d48 100644 --- a/src/resizing.jl +++ b/src/resizing.jl @@ -222,6 +222,7 @@ imresize_type(c::FixedPoint) = typeof(c) imresize_type(c) = typeof((c*1)/1) function imresize!{T,S,N}(resized::AbstractArray{T,N}, original::AbstractArray{S,N}) + # FIXME: avoid allocation for interpolation itp = interpolate(original, BSpline(Linear()), OnGrid()) imresize!(resized, itp) end diff --git a/src/warp.jl b/src/warp.jl index 819e654..af295fa 100644 --- a/src/warp.jl +++ b/src/warp.jl @@ -1,59 +1,51 @@ -# 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 - -Transform the coordinates of `img`, returning a new `imgw` satisfying -`imgw[x] = img[tform(x)]`. `tform` should be defined using -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. + warp(img, tform, [indices], [degree = Linear()], [fill = NaN]) -> imgw + +Transform the coordinates of `img`, returning a new `imgw` +satisfying `imgw[I] = img[tform(I)]`. This approach is known as +backward mode warping. The transformation `tform` must accept a +`SVector` as input. A useful package to create a wide variety of +such transformations is +[CoordinateTransformations.jl](https://github.com/FugroRoames/CoordinateTransformations.jl). + +# Reconstruction scheme + +During warping, values for `img` must be reconstructed at +arbitrary locations `tform(I)` which do not lie on to the lattice +of pixels. How this reconstruction is done depends on the type of +`img` and the optional parameter `degree`. + +When `img` is a plain array, then on-grid b-spline interpolation +will be used. It is possible to configure what degree of b-spline +to use with the parameter `degree`. For example one can use +`degree = Linear()` for linear interpolation, `degree = +Constant()` for nearest neighbor interpolation, or `degree = +Quadratic(Flat())` for quadratic interpolation. + +In the case `tform(I)` maps to indices outside the original +`img`, those locations are set to a value `fill` (which defaults +to `NaN` if the element type supports it, and `0` otherwise). The +parameter `fill` also accepts extrapolation schemes, such as +`Flat()`, `Periodic()` or `Reflect()`. + +For more control over the reconstruction scheme --- and how +beyond-the-edge points are handled --- pass `img` as an +`AbstractInterpolation` or `AbstractExtrapolation` from +[Interpolations.jl](https://github.com/JuliaMath/Interpolations.jl). # The meaning of the coordinates -The output array `imgw` has indices that would result from applying -`tform` to the indices of `img`. This can be very handy for keeping -track of how pixels in `imgw` line up with pixels in `img`. +The output array `imgw` has indices that would result from +applying `inv(tform)` to the indices of `img`. This can be very +handy for keeping track of how pixels in `imgw` line up with +pixels in `img`. -If you just want a plain array, you can "strip" the custom indices -with `parent(imgw)`. +If you just want a plain array, you can "strip" the custom +indices with `parent(imgw)`. # Examples: a 2d rotation (see JuliaImages documentation for pictures) -```jldoctest +``` julia> using Images, CoordinateTransformations, TestImages, OffsetArrays julia> img = testimage("lighthouse"); @@ -62,8 +54,8 @@ julia> indices(img) (Base.OneTo(512),Base.OneTo(768)) # Rotate around the center of `img` -julia> tfm = recenter(RotMatrix(pi/4), center(img)) -AffineMap([0.707107 -0.707107; 0.707107 0.707107], [347.01,-68.7554]) +julia> tfm = recenter(RotMatrix(-pi/4), center(img)) +AffineMap([0.707107 0.707107; -0.707107 0.707107], [-196.755,293.99]) julia> imgw = warp(img, tfm); @@ -73,7 +65,7 @@ julia> indices(imgw) # Alternatively, specify the origin in the image itself julia> img0 = OffsetArray(img, -30:481, -384:383); # origin near top of image -julia> rot = LinearMap(RotMatrix(pi/4)) +julia> rot = LinearMap(RotMatrix(-pi/4)) LinearMap([0.707107 -0.707107; 0.707107 0.707107]) julia> imgw = warp(img0, rot); @@ -87,28 +79,49 @@ julia> indices(imgr) (Base.OneTo(906),Base.OneTo(905)) ``` """ -function warp(img::AbstractExtrapolation, tform) - inds = autorange(img, tform) - out = OffsetArray(Array{eltype(img)}(map(length, inds)), inds) +function warp_new{T}(img::AbstractExtrapolation{T}, tform, inds::Tuple = autorange(img, inv(tform))) + out = similar(Array{T}, inds) warp!(out, img, tform) end +# this function was never exported, so no need to deprecate function warp!(out, img::AbstractExtrapolation, tform) - tinv = inv(tform) - for I in CartesianRange(indices(out)) - out[I] = _getindex(img, tinv(SVector(I.I))) + @inbounds for I in CartesianRange(indices(out)) + out[I] = _getindex(img, tform(SVector(I.I))) end out end -# This is type-piracy, but necessary if we want Interpolations to be -# independent of OffsetArrays. -function AxisAlgorithms.A_ldiv_B_md!(dest::OffsetArray, F, src::OffsetArray, dim::Integer, b::AbstractVector) - indsdim = indices(parent(src), dim) - indsF = indices(F)[2] - if indsF == indsdim - AxisAlgorithms.A_ldiv_B_md!(parent(dest), F, parent(src), dim, b) - return dest - end - throw(DimensionMismatch("indices $(indices(parent(src))) do not match $(indices(F))")) +function warp_new(img::AbstractArray, tform, inds::Tuple, args...) + etp = box_extrapolation(img, args...) + warp_new(etp, tform, inds) +end + +function warp_new(img::AbstractArray, tform, args...) + etp = box_extrapolation(img, args...) + warp_new(etp, tform) +end + +# # after deprecation period: +# @deprecate warp_new(img::AbstractArray, tform, args...) warp(img, tform, args...) +# @deprecate warp_old(img::AbstractArray, tform, args...) warp(img, tform, args...) + +""" + warp(img, tform, [indices], [degree = Linear()], [fill = NaN]) + +`warp` is transitioning to a different interpretation of the +transformation, and you are using the old version. + +More specifically, this method with the signature `warp(img, +tform, args...)` is deprecated in favour of the new +interpretation, which is equivalent to calling `warp(img, +inv(tform), args...)` right now. + +To change to the new behaviour, set `const warp = +ImageTransformations.warp_new` right after package import. +""" +function warp_old(img::AbstractArray, tform, args...) + Base.depwarn("'warp(img, tform)' is deprecated in favour of the new interpretation 'warp(img, inv(tform))'. Set 'const warp = ImageTransformations.warp_new' right after package import to change to the new behaviour right away. See https://github.com/JuliaImages/ImageTransformations.jl/issues/25 for more background information", :warp_old) + warp_new(img, inv(tform), args...) end +const warp = warp_old diff --git a/src/warpedview.jl b/src/warpedview.jl index 1496fc7..04f1a1c 100644 --- a/src/warpedview.jl +++ b/src/warpedview.jl @@ -1,37 +1,40 @@ -immutable WarpedView{T,N,A<:AbstractArray,F1<:Transformation,F2<:Transformation,I} <: AbstractArray{T,N} +""" + WarpedView(img, tform, [indices]) -> wv + +Create a view of `img` that lazily transforms any given index `I` +passed to `wv[I]` to correspond to `img[tform(I)]`. This approach +is known as backward mode warping. + +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`. To do this `inv(tform)` has to be computed. If the given +transformation `tform` does not support `inv`, then the parameter +`indices` has to be specified manually. + +see [`warpedview`](@ref) for more information. +""" +immutable WarpedView{T,N,A<:AbstractArray,F<:Transformation,I<:Tuple,E<:AbstractExtrapolation} <: AbstractArray{T,N} parent::A - transform::F1 - transform_inv::F2 + transform::F indices::I + extrapolation::E - function (::Type{WarpedView{T,N,A,F1,F2,I}}){T,N,A<:AbstractArray,F1<:Transformation,F2<:Transformation,I}( - parent::A, tform::F1, tinv::F2, indices::I) + function (::Type{WarpedView{T,N,TA,F,I}}){T,N,TA<:AbstractArray,F<:Transformation,I<:Tuple}( + parent::TA, + tform::F, + indices::I) @assert eltype(parent) == T - new{T,N,A,F1,F2,I}(parent, tform, tinv, indices) + etp = box_extrapolation(parent) + new{T,N,TA,F,I,typeof(etp)}(parent, tform, indices, etp) end end -function WarpedView{T,N,F<:Transformation}(inner::WarpedView{T,N}, outer_tform::F) - tform = compose(outer_tform, inner.transform) - tinv = inv(tform) - etp = parent(inner) - inds = autorange(etp, tform) - WarpedView{T,N,typeof(etp),typeof(tform),typeof(tinv),typeof(inds)}(etp, tform, tinv, inds) -end - -function WarpedView{T,N}(parent::AbstractArray{T,N}, args...) - itp = Interpolations.BSplineInterpolation{T,N,typeof(parent),Interpolations.BSpline{Interpolations.Linear},OnGrid,0}(parent) - WarpedView(itp, args...) -end - -function WarpedView{T,F<:Transformation}(itp::AbstractInterpolation{T}, tform::F, fill=_default_fill(T)) - WarpedView(extrapolate(itp, fill), tform) -end - -function WarpedView{T,N,F<:Transformation}(etp::AbstractExtrapolation{T,N}, tform::F) - inds = autorange(etp, tform) - tinv = inv(tform) - WarpedView{T,N,typeof(etp),F,typeof(tinv),typeof(inds)}(etp, tform, tinv, inds) +function WarpedView{T,N,F<:Transformation,I<:Tuple}( + A::AbstractArray{T,N}, + tform::F, + inds::I = autorange(A, inv(tform))) + WarpedView{T,N,typeof(A),F,I}(A, tform, inds) end Base.parent(A::WarpedView) = A.parent @@ -39,24 +42,97 @@ Base.parent(A::WarpedView) = A.parent @compat Compat.IndexStyle{T<:WarpedView}(::Type{T}) = IndexCartesian() @inline Base.getindex{T,N}(A::WarpedView{T,N}, I::Vararg{Int,N}) = - _getindex(A.parent, A.transform_inv(SVector(I))) + _getindex(A.extrapolation, A.transform(SVector(I))) -Base.size(A::WarpedView) = OffsetArrays.errmsg(A) -Base.size(A::WarpedView, d) = OffsetArrays.errmsg(A) +Base.size{T,N,TA,F}(A::WarpedView{T,N,TA,F}) = OffsetArrays.errmsg(A) +Base.size{T,N,TA,F}(A::WarpedView{T,N,TA,F}, d) = OffsetArrays.errmsg(A) -# This will return the next non-standard parent -# This way only those extrapolations/interpolations are displayed -# that are different to the default settings -_next_custom(A) = A -_next_custom(A::Interpolations.FilledExtrapolation) = _next_custom(A.itp) -_next_custom{T,N,TI,IT<:BSpline{Linear},GT<:OnGrid}(A::Interpolations.BSplineInterpolation{T,N,TI,IT,GT}) = _next_custom(A.coefs) +Base.size{T,N,TA,F}(A::WarpedView{T,N,TA,F,NTuple{N,Base.OneTo{Int}}}) = map(length, A.indices) +Base.size{T,N,TA,F}(A::WarpedView{T,N,TA,F,NTuple{N,Base.OneTo{Int}}}, d) = d <= N ? length(A.indices[d]) : 1 function ShowItLikeYouBuildIt.showarg(io::IO, A::WarpedView) print(io, "WarpedView(") - showarg(io, _next_custom(parent(A))) + showarg(io, parent(A)) print(io, ", ") print(io, A.transform) print(io, ')') end Base.summary(A::WarpedView) = summary_build(A) + +""" + warpedview(img, tform, [indices], [degree = Linear()], [fill = NaN]) -> wv + +Create a view of `img` that lazily transforms any given index `I` +passed to `wv[I]` to correspond to `img[tform(I)]`. This approach +is known as backward mode warping. The given transformation +`tform` must accept a `SVector` as input. 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 `tform(I)` which do not lie on to the lattice +of pixels. How this reconstruction is done depends on the type of +`img` and the optional parameter `degree`. When `img` is a plain +array, then on-grid b-spline interpolation will be used, where +the pixel of `img` will serve as the coeficients. It is possible +to configure what degree of b-spline to use with the parameter +`degree`. The two possible values are `degree = Linear()` for +linear interpolation, or `degree = Constant()` for nearest +neighbor interpolation. + +In the case `tform(I)` maps to indices outside the domain of +`img`, those locations are set to a value `fill` (which defaults +to `NaN` if the element type supports it, and `0` otherwise). +Additionally, the parameter `fill` also accepts extrapolation +schemes, such as `Flat()`, `Periodic()` or `Reflect()`. + +The optional parameter `indices` can be used to specify the +domain of the resulting `WarpedView`. By default the indices are +computed in such a way that the resulting `WarpedView` contains +all the original pixels in `img`. To do this `inv(tform)` has to +be computed. If the given transformation `tform` does not support +`inv`, then the parameter `indices` has to be specified manually. + +`warpedview` is essentially a non-coping, lazy version of +[`warp`](@ref). As such, the two functions share the same +interface, with one important difference. `warpedview` will +insist that the resulting `WarpedView` will be a view of `img` +(i.e. `parent(warpedview(img, ...)) === img`). Consequently, +`warpedview` restricts the parameter `degree` to be either +`Linear()` or `Constant()`. +""" +@inline warpedview(A::AbstractArray, tform::Transformation, args...) = + WarpedView(A, tform, args...) + +function warpedview{T}( + A::AbstractArray{T}, + tform::Transformation, + degree::Union{Linear,Constant}, + fill::FillType = _default_fill(T)) + warpedview(box_extrapolation(A, degree, fill), tform) +end + +function warpedview{T}( + A::AbstractArray{T}, + tform::Transformation, + indices::Tuple, + degree::Union{Linear,Constant}, + fill::FillType = _default_fill(T)) + warpedview(box_extrapolation(A, degree, fill), tform, indices) +end + +function warpedview( + A::AbstractArray, + tform::Transformation, + fill::FillType) + warpedview(A, tform, Linear(), fill) +end + +function warpedview( + A::AbstractArray, + tform::Transformation, + indices::Tuple, + fill::FillType) + warpedview(A, tform, indices, Linear(), fill) +end diff --git a/test/REQUIRE b/test/REQUIRE index d27d873..29fce5c 100644 --- a/test/REQUIRE +++ b/test/REQUIRE @@ -1,5 +1,6 @@ TestImages FixedPointNumbers 0.3.0 +ImageInTerminal @windows ImageMagick @linux ImageMagick @osx QuartzImageIO diff --git a/test/deprecations.jl b/test/deprecations.jl new file mode 100644 index 0000000..8f2624c --- /dev/null +++ b/test/deprecations.jl @@ -0,0 +1,7 @@ +info("Start of deprecation warnings") + +img_camera = testimage("camera") +tfm = recenter(RotMatrix(-pi/8), center(img_camera)) +imgr = @inferred(ImageTransformations.warp_old(img_camera, tfm)) +@test eltype(imgr) == eltype(img_camera) +@test_reference "warp_cameraman_rotate_r22deg" imgr diff --git a/test/interpolations.jl b/test/interpolations.jl new file mode 100644 index 0000000..7073300 --- /dev/null +++ b/test/interpolations.jl @@ -0,0 +1,105 @@ +@testset "_default_fill" begin + @test_throws UndefVarError _default_fill + @test typeof(ImageTransformations._default_fill) <: Function + + @test @inferred(ImageTransformations._default_fill(N0f8)) === N0f8(0) + @test @inferred(ImageTransformations._default_fill(Int)) === 0 + @test @inferred(ImageTransformations._default_fill(Float16)) === NaN16 + @test @inferred(ImageTransformations._default_fill(Float32)) === NaN32 + @test @inferred(ImageTransformations._default_fill(Float64)) === NaN + + @test @inferred(ImageTransformations._default_fill(Gray{N0f8})) === Gray{N0f8}(0) + @test @inferred(ImageTransformations._default_fill(Gray{Float16})) === Gray{Float16}(NaN16) + @test @inferred(ImageTransformations._default_fill(Gray{Float32})) === Gray{Float32}(NaN32) + @test @inferred(ImageTransformations._default_fill(Gray{Float64})) === Gray{Float64}(NaN) + + @test @inferred(ImageTransformations._default_fill(GrayA{N0f8})) === GrayA{N0f8}(0,0) + @test @inferred(ImageTransformations._default_fill(GrayA{Float16})) === GrayA{Float16}(NaN16,NaN16) + @test @inferred(ImageTransformations._default_fill(GrayA{Float32})) === GrayA{Float32}(NaN32,NaN32) + @test @inferred(ImageTransformations._default_fill(GrayA{Float64})) === GrayA{Float64}(NaN,NaN) + + @test @inferred(ImageTransformations._default_fill(RGB{N0f8})) === RGB{N0f8}(0,0,0) + @test @inferred(ImageTransformations._default_fill(RGB{Float16})) === RGB{Float16}(NaN16,NaN16,NaN16) + @test @inferred(ImageTransformations._default_fill(RGB{Float32})) === RGB{Float32}(NaN32,NaN32,NaN32) + @test @inferred(ImageTransformations._default_fill(RGB{Float64})) === RGB{Float64}(NaN,NaN,NaN) + + @test @inferred(ImageTransformations._default_fill(RGBA{N0f8})) === RGBA{N0f8}(0,0,0,0) + @test @inferred(ImageTransformations._default_fill(RGBA{Float16})) === RGBA{Float16}(NaN16,NaN16,NaN16,NaN16) + @test @inferred(ImageTransformations._default_fill(RGBA{Float32})) === RGBA{Float32}(NaN32,NaN32,NaN32,NaN32) + @test @inferred(ImageTransformations._default_fill(RGBA{Float64})) === RGBA{Float64}(NaN,NaN,NaN,NaN) + + @test @inferred(ImageTransformations._default_fill(HSV{Float16})) === HSV{Float16}(NaN16,NaN16,NaN16) + @test @inferred(ImageTransformations._default_fill(HSV{Float32})) === HSV{Float32}(NaN32,NaN32,NaN32) + @test @inferred(ImageTransformations._default_fill(HSV{Float64})) === HSV{Float64}(NaN,NaN,NaN) +end + +@testset "box_extrapolation" begin + @test_throws UndefVarError box_extrapolation + @test typeof(ImageTransformations.box_extrapolation) <: Function + + img = rand(Gray{N0f8}, 2, 2) + + etp = @inferred ImageTransformations.box_extrapolation(img) + @test @inferred(ImageTransformations.box_extrapolation(etp)) === etp + @test summary(etp) == "2×2 extrapolate(interpolate(::Array{Gray{N0f8},2}, BSpline(Linear()), OnGrid()), Gray{N0f8}(0.0)) with element type ColorTypes.Gray{FixedPointNumbers.Normed{UInt8,8}}" + @test typeof(etp) <: Interpolations.FilledExtrapolation + @test etp.fillvalue === Gray{N0f8}(0.0) + @test etp.itp.coefs === img + + @test_throws ArgumentError ImageTransformations.box_extrapolation(etp, 0) + @test_throws ArgumentError ImageTransformations.box_extrapolation(etp, Flat()) + @test_throws ArgumentError ImageTransformations.box_extrapolation(etp, Quadratic(Flat())) + @test_throws ArgumentError ImageTransformations.box_extrapolation(etp, Quadratic(Flat()), Flat()) + @test_throws ArgumentError ImageTransformations.box_extrapolation(etp, Constant()) + @test_throws ArgumentError ImageTransformations.box_extrapolation(etp, Constant(), Flat()) + @test_throws ArgumentError ImageTransformations.box_extrapolation(etp.itp, Constant()) + @test_throws ArgumentError ImageTransformations.box_extrapolation(etp.itp, Constant(), Flat()) + + etp2 = @inferred ImageTransformations.box_extrapolation(etp.itp) + @test summary(etp2) == "2×2 extrapolate(interpolate(::Array{Gray{N0f8},2}, BSpline(Linear()), OnGrid()), Gray{N0f8}(0.0)) with element type ColorTypes.Gray{FixedPointNumbers.Normed{UInt8,8}}" + @test typeof(etp2) <: Interpolations.FilledExtrapolation + @test etp2.fillvalue === Gray{N0f8}(0.0) + @test etp2 !== etp + @test etp2.itp === etp.itp + + etp2 = @inferred ImageTransformations.box_extrapolation(etp.itp, Flat()) + @test summary(etp2) == "2×2 extrapolate(interpolate(::Array{Gray{N0f8},2}, BSpline(Linear()), OnGrid()), Flat()) with element type ColorTypes.Gray{FixedPointNumbers.Normed{UInt8,8}}" + @test typeof(etp2) <: Interpolations.Extrapolation + @test etp2 !== etp + @test etp2.itp === etp.itp + + etp = @inferred ImageTransformations.box_extrapolation(img, 1) + @test summary(etp) == "2×2 extrapolate(interpolate(::Array{Gray{N0f8},2}, BSpline(Linear()), OnGrid()), Gray{N0f8}(1.0)) with element type ColorTypes.Gray{FixedPointNumbers.Normed{UInt8,8}}" + @test typeof(etp) <: Interpolations.FilledExtrapolation + @test etp.fillvalue === Gray{N0f8}(1.0) + @test etp.itp.coefs === img + + etp = @inferred ImageTransformations.box_extrapolation(img, Flat()) + @test @inferred(ImageTransformations.box_extrapolation(etp)) === etp + @test summary(etp) == "2×2 extrapolate(interpolate(::Array{Gray{N0f8},2}, BSpline(Linear()), OnGrid()), Flat()) with element type ColorTypes.Gray{FixedPointNumbers.Normed{UInt8,8}}" + @test typeof(etp) <: Interpolations.Extrapolation + @test etp.itp.coefs === img + + etp = @inferred ImageTransformations.box_extrapolation(img, Constant()) + @test summary(etp) == "2×2 extrapolate(interpolate(::Array{Gray{N0f8},2}, BSpline(Constant()), OnGrid()), Gray{N0f8}(0.0)) with element type ColorTypes.Gray{FixedPointNumbers.Normed{UInt8,8}}" + @test typeof(etp) <: Interpolations.FilledExtrapolation + @test etp.itp.coefs === img + + etp = @inferred ImageTransformations.box_extrapolation(img, Constant(), Flat()) + @test summary(etp) == "2×2 extrapolate(interpolate(::Array{Gray{N0f8},2}, BSpline(Constant()), OnGrid()), Flat()) with element type ColorTypes.Gray{FixedPointNumbers.Normed{UInt8,8}}" + @test typeof(etp) <: Interpolations.Extrapolation + @test etp.itp.coefs === img + + imgfloat = Float64.(img) + etp = @inferred ImageTransformations.box_extrapolation(imgfloat, Quadratic(Flat())) + @test typeof(etp) <: Interpolations.FilledExtrapolation + @test summary(etp) == "2×2 extrapolate(interpolate(::Array{Float64,2}, BSpline(Quadratic(Flat())), OnGrid()), NaN) with element type Float64" + + etp = @inferred ImageTransformations.box_extrapolation(imgfloat, Cubic(Flat()), Flat()) + @test typeof(etp) <: Interpolations.Extrapolation + @test summary(etp) == "2×2 extrapolate(interpolate(::Array{Float64,2}, BSpline(Cubic(Flat())), OnGrid()), Flat()) with element type Float64" +end + +@testset "AxisAlgorithms.A_ldiv_B_md" begin + # TODO +end diff --git a/test/reference/invwarpedview_cameraman.txt b/test/reference/invwarpedview_cameraman.txt new file mode 100644 index 0000000..ed6ef35 --- /dev/null +++ b/test/reference/invwarpedview_cameraman.txt @@ -0,0 +1,17 @@ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ \ No newline at end of file diff --git a/test/reference/invwarpedview_cameraman_rotate_r22deg.txt b/test/reference/invwarpedview_cameraman_rotate_r22deg.txt new file mode 100644 index 0000000..aebdc97 --- /dev/null +++ b/test/reference/invwarpedview_cameraman_rotate_r22deg.txt @@ -0,0 +1,11 @@ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ \ No newline at end of file diff --git a/test/reference/warp_cameraman.txt b/test/reference/warp_cameraman.txt new file mode 100644 index 0000000..cd72d92 --- /dev/null +++ b/test/reference/warp_cameraman.txt @@ -0,0 +1,17 @@ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ \ No newline at end of file diff --git a/test/reference/warp_cameraman_rotate_r22deg.txt b/test/reference/warp_cameraman_rotate_r22deg.txt new file mode 100644 index 0000000..aebdc97 --- /dev/null +++ b/test/reference/warp_cameraman_rotate_r22deg.txt @@ -0,0 +1,11 @@ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ \ No newline at end of file diff --git a/test/reference/warp_cameraman_rotate_r22deg_crop.txt b/test/reference/warp_cameraman_rotate_r22deg_crop.txt new file mode 100644 index 0000000..e735cb9 --- /dev/null +++ b/test/reference/warp_cameraman_rotate_r22deg_crop.txt @@ -0,0 +1,17 @@ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ \ No newline at end of file diff --git a/test/reference/warp_cameraman_rotate_r22deg_crop_white.txt b/test/reference/warp_cameraman_rotate_r22deg_crop_white.txt new file mode 100644 index 0000000..1626b84 --- /dev/null +++ b/test/reference/warp_cameraman_rotate_r22deg_crop_white.txt @@ -0,0 +1,17 @@ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ \ No newline at end of file diff --git a/test/reference/warp_cameraman_rotate_r22deg_flat.txt b/test/reference/warp_cameraman_rotate_r22deg_flat.txt new file mode 100644 index 0000000..79bca11 --- /dev/null +++ b/test/reference/warp_cameraman_rotate_r22deg_flat.txt @@ -0,0 +1,11 @@ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ \ No newline at end of file diff --git a/test/reference/warp_cameraman_rotate_r22deg_periodic.txt b/test/reference/warp_cameraman_rotate_r22deg_periodic.txt new file mode 100644 index 0000000..a526f52 --- /dev/null +++ b/test/reference/warp_cameraman_rotate_r22deg_periodic.txt @@ -0,0 +1,11 @@ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ \ No newline at end of file diff --git a/test/reference/warp_cameraman_rotate_r22deg_white.txt b/test/reference/warp_cameraman_rotate_r22deg_white.txt new file mode 100644 index 0000000..8861851 --- /dev/null +++ b/test/reference/warp_cameraman_rotate_r22deg_white.txt @@ -0,0 +1,11 @@ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 5887ab4..8b54712 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,15 +1,74 @@ using CoordinateTransformations, TestImages, ImageCore, Colors, FixedPointNumbers, OffsetArrays, Interpolations -using Base.Test +using Base.Test, ImageInTerminal refambs = detect_ambiguities(CoordinateTransformations, Base, Core) using ImageTransformations ambs = detect_ambiguities(ImageTransformations, CoordinateTransformations, Base, Core) @test isempty(setdiff(ambs, refambs)) +reference_path(filename) = joinpath(dirname(@__FILE__), "reference", "$(filename).txt") + +function test_reference_impl{T<:Colorant}(filename, img::AbstractArray{T}) + res = ImageInTerminal.encodeimg(ImageInTerminal.SmallBlocks(), ImageInTerminal.TermColor256(), img, 20, 40)[1] + test_reference_impl(filename, res) +end + +function test_reference_impl{T<:String}(filename, actual::AbstractArray{T}) + try + reference = replace.(readlines(reference_path(filename)), ["\n"], [""]) + try + @assert reference == actual # to throw error + @test true # to increase test counter if reached + catch # test failed + println("Test for \"$filename\" failed.") + println("- REFERENCE -------------------") + println.(reference) + println("-------------------------------") + println("- ACTUAL ----------------------") + println.(actual) + println("-------------------------------") + if isinteractive() + print("Replace reference with actual result? [y/n] ") + answer = first(readline()) + if answer == 'y' + write(reference_path(filename), join(actual, "\n")) + end + else + error("You need to run the tests interactively with 'include(\"test/runtests.jl\")' to update reference images") + end + end + catch ex + if isa(ex, SystemError) # File doesn't exist + println("Reference file for \"$filename\" does not exist.") + println("- NEW CONTENT -----------------") + println.(actual) + println("-------------------------------") + if isinteractive() + print("Create reference file with above content? [y/n] ") + answer = first(readline()) + if answer == 'y' + write(reference_path(filename), join(actual, "\n")) + end + else + error("You need to run the tests interactively with 'include(\"test/runtests.jl\")' to create new reference images") + end + else + throw(ex) + end + end +end + +# using a macro looks more consistent +macro test_reference(filename, expr) + esc(:(test_reference_impl($filename, $expr))) +end + tests = [ "autorange.jl", "resizing.jl", + "interpolations.jl", "warp.jl", + "deprecations.jl", ] for t in tests diff --git a/test/warp.jl b/test/warp.jl index 75a3949..2791fa1 100644 --- a/test/warp.jl +++ b/test/warp.jl @@ -1,54 +1,280 @@ +const warp = ImageTransformations.warp_new + # helper function to compare NaN nearlysame(x, y) = x ≈ y || (isnan(x) & isnan(y)) nearlysame(A::AbstractArray, B::AbstractArray) = all(map(nearlysame, A, B)) #img_square = Gray{N0f8}.(reshape(linspace(0,1,9), (3,3))) -SPACE = if VERSION < v"0.6.0-dev.2505" # julia PR #20288 - "" -else - " " -end +SPACE = VERSION < v"0.6.0-dev.2505" ? "" : " " # julia PR #20288 img_camera = testimage("camera") -@testset "Constructor" begin - tfm = recenter(RotMatrix(-pi/8), center(img_camera)) +@testset "Interface tests" begin + tfm = recenter(RotMatrix(pi/8), center(img_camera)) ref_inds = (-78:591, -78:591) - @testset "warp" begin + @testset "warp_new" begin imgr = @inferred(warp(img_camera, tfm)) + @test typeof(imgr) <: OffsetArray @test indices(imgr) == ref_inds + @test eltype(imgr) == eltype(img_camera) + @test_reference "warp_cameraman_rotate_r22deg" imgr - 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 + imgr2 = imgr[indices(img_camera)...] + @test_reference "warp_cameraman_rotate_r22deg_crop" imgr2 + + imgr = @inferred(warp(img_camera, tfm, indices(img_camera))) + @test typeof(imgr) <: Array + @test eltype(imgr) == eltype(img_camera) + @test_reference "warp_cameraman_rotate_r22deg_crop" imgr + + imgr = @inferred(warp(img_camera, tfm, indices(img_camera), 1)) + @test typeof(imgr) <: Array + @test eltype(imgr) == eltype(img_camera) + @test_reference "warp_cameraman_rotate_r22deg_crop_white" imgr + + imgr = @inferred(warp(img_camera, tfm, indices(img_camera), Linear(), 1)) + @test typeof(imgr) <: Array + @test eltype(imgr) == eltype(img_camera) + @test_reference "warp_cameraman_rotate_r22deg_crop_white" imgr + + imgr = @inferred(warp(img_camera, tfm, 1)) + @test typeof(imgr) <: OffsetArray + @test eltype(imgr) == eltype(img_camera) + @test_reference "warp_cameraman_rotate_r22deg_white" imgr + imgr2 = @inferred warp(imgr, inv(tfm)) + @test eltype(imgr2) == eltype(img_camera) + @test_reference "warp_cameraman" imgr2[indices(img_camera)...] + # look the same but are not similar enough to pass test + # @test imgr2[indices(img_camera)...] ≈ img_camera + + imgr = @inferred(warp(img_camera, tfm, Flat())) + @test typeof(imgr) <: OffsetArray + @test eltype(imgr) == eltype(img_camera) + @test_reference "warp_cameraman_rotate_r22deg_flat" imgr + imgr = @inferred(warp(img_camera, tfm, ref_inds, Flat())) + @test eltype(imgr) == eltype(img_camera) + @test_reference "warp_cameraman_rotate_r22deg_flat" imgr + + imgr = @inferred(warp(img_camera, tfm, Constant(), Periodic())) + @test typeof(imgr) <: OffsetArray + @test eltype(imgr) == eltype(img_camera) + @test_reference "warp_cameraman_rotate_r22deg_periodic" imgr + imgr = @inferred(warp(img_camera, tfm, ref_inds, Constant(), Periodic())) + @test eltype(imgr) == eltype(img_camera) + @test_reference "warp_cameraman_rotate_r22deg_periodic" imgr + end + + @testset "warpedview" begin + imgr = @inferred(warpedview(img_camera, tfm)) + @test imgr == @inferred(WarpedView(img_camera, tfm)) + @test summary(imgr) == "-78:591×-78:591 WarpedView(::Array{Gray{N0f8},2}, AffineMap([0.92388 -0.382683; 0.382683 0.92388], [117.683,$(SPACE)-78.6334])) with element type ColorTypes.Gray{FixedPointNumbers.Normed{UInt8,8}}" + @test_throws ErrorException size(imgr) + @test_throws ErrorException size(imgr, 1) + @test_throws ErrorException size(imgr, 5) + @test parent(imgr) === img_camera + @test typeof(imgr) <: WarpedView + @test indices(imgr) == ref_inds + @test eltype(imgr) == eltype(img_camera) + @test_reference "warp_cameraman_rotate_r22deg" imgr + + imgr2 = imgr[indices(img_camera)...] + @test_reference "warp_cameraman_rotate_r22deg_crop" imgr2 + + imgr = @inferred(warpedview(img_camera, tfm, indices(img_camera))) + @test imgr == @inferred(WarpedView(img_camera, tfm, indices(img_camera))) + @test summary(imgr) == "512×512 WarpedView(::Array{Gray{N0f8},2}, AffineMap([0.92388 -0.382683; 0.382683 0.92388], [117.683,$(SPACE)-78.6334])) with element type ColorTypes.Gray{FixedPointNumbers.Normed{UInt8,8}}" + @test @inferred(size(imgr)) == size(img_camera) + @test @inferred(size(imgr,3)) == 1 + @test parent(imgr) === img_camera + @test indices(imgr) === indices(img_camera) + @test typeof(imgr) <: WarpedView + @test eltype(imgr) == eltype(img_camera) + @test_reference "warp_cameraman_rotate_r22deg_crop" imgr + + imgr = @inferred(warpedview(img_camera, tfm, indices(img_camera), 1)) + @test summary(imgr) == "512×512 WarpedView(extrapolate(interpolate(::Array{Gray{N0f8},2}, BSpline(Linear()), OnGrid()), Gray{N0f8}(1.0)), AffineMap([0.92388 -0.382683; 0.382683 0.92388], [117.683,$(SPACE)-78.6334])) with element type ColorTypes.Gray{FixedPointNumbers.Normed{UInt8,8}}" + @test @inferred(size(imgr)) == size(img_camera) + @test @inferred(size(imgr,3)) == 1 + @test typeof(parent(imgr)) <: Interpolations.FilledExtrapolation + @test parent(imgr).itp.coefs === img_camera + @test indices(imgr) === indices(img_camera) + @test typeof(imgr) <: WarpedView + @test eltype(imgr) == eltype(img_camera) + @test_reference "warp_cameraman_rotate_r22deg_crop_white" imgr - imgr = @inferred(warp(Gray, img_camera, tfm)) - imgr2 = warp(Gray, imgr, inv(tfm)) + imgr = @inferred(warpedview(img_camera, tfm, indices(img_camera), Linear(), 1)) + @test summary(imgr) == "512×512 WarpedView(extrapolate(interpolate(::Array{Gray{N0f8},2}, BSpline(Linear()), OnGrid()), Gray{N0f8}(1.0)), AffineMap([0.92388 -0.382683; 0.382683 0.92388], [117.683,$(SPACE)-78.6334])) with element type ColorTypes.Gray{FixedPointNumbers.Normed{UInt8,8}}" + @test @inferred(size(imgr)) == size(img_camera) + @test @inferred(size(imgr,3)) == 1 + @test typeof(parent(imgr)) <: Interpolations.FilledExtrapolation + @test parent(imgr).itp.coefs === img_camera + @test indices(imgr) === indices(img_camera) + @test typeof(imgr) <: WarpedView + @test eltype(imgr) == eltype(img_camera) + @test_reference "warp_cameraman_rotate_r22deg_crop_white" imgr + + imgr = @inferred(warpedview(img_camera, tfm, 1)) + @test summary(imgr) == "-78:591×-78:591 WarpedView(extrapolate(interpolate(::Array{Gray{N0f8},2}, BSpline(Linear()), OnGrid()), Gray{N0f8}(1.0)), AffineMap([0.92388 -0.382683; 0.382683 0.92388], [117.683,$(SPACE)-78.6334])) with element type ColorTypes.Gray{FixedPointNumbers.Normed{UInt8,8}}" + @test_throws ErrorException size(imgr) + @test_throws ErrorException size(imgr, 1) + @test_throws ErrorException size(imgr, 5) + @test typeof(parent(imgr)) <: Interpolations.FilledExtrapolation + @test parent(imgr).itp.coefs === img_camera + @test typeof(imgr) <: WarpedView + @test eltype(imgr) == eltype(img_camera) + @test_reference "warp_cameraman_rotate_r22deg_white" imgr + imgr2 = @inferred warpedview(imgr, inv(tfm)) + @test eltype(imgr2) == eltype(img_camera) + @test_reference "warp_cameraman" imgr2[indices(img_camera)...] # look the same but are not similar enough to pass test # @test imgr2[indices(img_camera)...] ≈ img_camera + + imgr = @inferred(warpedview(img_camera, tfm, Flat())) + @test summary(imgr) == "-78:591×-78:591 WarpedView(extrapolate(interpolate(::Array{Gray{N0f8},2}, BSpline(Linear()), OnGrid()), Flat()), AffineMap([0.92388 -0.382683; 0.382683 0.92388], [117.683,$(SPACE)-78.6334])) with element type ColorTypes.Gray{FixedPointNumbers.Normed{UInt8,8}}" + @test_throws ErrorException size(imgr) + @test_throws ErrorException size(imgr, 1) + @test_throws ErrorException size(imgr, 5) + @test typeof(parent(imgr)) <: Interpolations.Extrapolation + @test parent(imgr).itp.coefs === img_camera + @test typeof(imgr) <: WarpedView + @test eltype(imgr) == eltype(img_camera) + @test_reference "warp_cameraman_rotate_r22deg_flat" imgr + imgr = @inferred(warpedview(img_camera, tfm, ref_inds, Flat())) + @test eltype(imgr) == eltype(img_camera) + @test indices(imgr) === ref_inds + @test_reference "warp_cameraman_rotate_r22deg_flat" imgr + + imgr = @inferred(warpedview(img_camera, tfm, Constant(), Periodic())) + @test summary(imgr) == "-78:591×-78:591 WarpedView(extrapolate(interpolate(::Array{Gray{N0f8},2}, BSpline(Constant()), OnGrid()), Periodic()), AffineMap([0.92388 -0.382683; 0.382683 0.92388], [117.683,$(SPACE)-78.6334])) with element type ColorTypes.Gray{FixedPointNumbers.Normed{UInt8,8}}" + @test_throws ErrorException size(imgr) + @test_throws ErrorException size(imgr, 1) + @test_throws ErrorException size(imgr, 5) + @test typeof(parent(imgr)) <: Interpolations.Extrapolation + @test parent(imgr).itp.coefs === img_camera + @test typeof(imgr) <: WarpedView + @test eltype(imgr) == eltype(img_camera) + @test_reference "warp_cameraman_rotate_r22deg_periodic" imgr + + imgr = @inferred(warpedview(img_camera, tfm, ref_inds, Constant(), Periodic())) + @test summary(imgr) == "-78:591×-78:591 WarpedView(extrapolate(interpolate(::Array{Gray{N0f8},2}, BSpline(Constant()), OnGrid()), Periodic()), AffineMap([0.92388 -0.382683; 0.382683 0.92388], [117.683,$(SPACE)-78.6334])) with element type ColorTypes.Gray{FixedPointNumbers.Normed{UInt8,8}}" + @test_throws ErrorException size(imgr) + @test_throws ErrorException size(imgr, 1) + @test_throws ErrorException size(imgr, 5) + @test eltype(imgr) == eltype(img_camera) + @test_reference "warp_cameraman_rotate_r22deg_periodic" imgr end - @testset "WarpedView" begin - wv = @inferred(WarpedView(img_camera, tfm)) - @test summary(wv) == "-78:591×-78:591 WarpedView(::Array{Gray{N0f8},2}, AffineMap([0.92388 0.382683; -0.382683 0.92388], [-78.6334,$(SPACE)117.683])) with element type ColorTypes.Gray{FixedPointNumbers.Normed{UInt8,8}}" + tfm = recenter(RotMatrix(-pi/8), center(img_camera)) + @testset "invwarpedview" begin + wv = @inferred(InvWarpedView(img_camera, tfm)) + @test wv ≈ @inferred(InvWarpedView(WarpedView(img_camera, inv(tfm)))) + @test_reference "invwarpedview_cameraman_rotate_r22deg" wv @test_throws ErrorException size(wv) @test_throws ErrorException size(wv, 1) @test indices(wv) == ref_inds @test eltype(wv) === eltype(img_camera) - @test typeof(parent(wv)) <: Interpolations.AbstractExtrapolation - @test typeof(parent(wv).itp) <: Interpolations.AbstractInterpolation - @test parent(wv).itp.coefs === img_camera + @test parent(wv) === img_camera # check nested transformation using the inverse - wv2 = @inferred(WarpedView(wv, inv(tfm))) + wv2 = @inferred(InvWarpedView(wv, inv(tfm))) + @test_reference "invwarpedview_cameraman" wv2 @test indices(wv2) == indices(img_camera) @test eltype(wv2) === eltype(img_camera) - @test typeof(parent(wv2)) <: Interpolations.AbstractExtrapolation - @test typeof(parent(wv2).itp) <: Interpolations.AbstractInterpolation - @test parent(wv2).itp.coefs === img_camera + @test parent(wv2) === img_camera @test wv2 ≈ img_camera + + imgr = @inferred(invwarpedview(img_camera, tfm)) + @test imgr == @inferred(InvWarpedView(img_camera, tfm)) + @test summary(imgr) == "-78:591×-78:591 InvWarpedView(::Array{Gray{N0f8},2}, AffineMap([0.92388 0.382683; -0.382683 0.92388], [-78.6334,$(SPACE)117.683])) with element type ColorTypes.Gray{FixedPointNumbers.Normed{UInt8,8}}" + @test_throws ErrorException size(imgr) + @test_throws ErrorException size(imgr, 1) + @test_throws ErrorException size(imgr, 5) + @test parent(imgr) === img_camera + @test typeof(imgr) <: InvWarpedView + @test indices(imgr) == ref_inds + @test eltype(imgr) == eltype(img_camera) + @test_reference "warp_cameraman_rotate_r22deg" imgr + + imgr2 = imgr[indices(img_camera)...] + @test_reference "warp_cameraman_rotate_r22deg_crop" imgr2 + + imgr = @inferred(invwarpedview(img_camera, tfm, indices(img_camera))) + @test imgr == @inferred(InvWarpedView(img_camera, tfm, indices(img_camera))) + @test summary(imgr) == "512×512 InvWarpedView(::Array{Gray{N0f8},2}, AffineMap([0.92388 0.382683; -0.382683 0.92388], [-78.6334,$(SPACE)117.683])) with element type ColorTypes.Gray{FixedPointNumbers.Normed{UInt8,8}}" + @test @inferred(size(imgr)) == size(img_camera) + @test @inferred(size(imgr,3)) == 1 + @test parent(imgr) === img_camera + @test indices(imgr) === indices(img_camera) + @test typeof(imgr) <: InvWarpedView + @test eltype(imgr) == eltype(img_camera) + @test_reference "warp_cameraman_rotate_r22deg_crop" imgr + + imgr = @inferred(invwarpedview(img_camera, tfm, indices(img_camera), 1)) + @test summary(imgr) == "512×512 InvWarpedView(extrapolate(interpolate(::Array{Gray{N0f8},2}, BSpline(Linear()), OnGrid()), Gray{N0f8}(1.0)), AffineMap([0.92388 0.382683; -0.382683 0.92388], [-78.6334,$(SPACE)117.683])) with element type ColorTypes.Gray{FixedPointNumbers.Normed{UInt8,8}}" + @test @inferred(size(imgr)) == size(img_camera) + @test @inferred(size(imgr,3)) == 1 + @test typeof(parent(imgr)) <: Interpolations.FilledExtrapolation + @test parent(imgr).itp.coefs === img_camera + @test indices(imgr) === indices(img_camera) + @test typeof(imgr) <: InvWarpedView + @test eltype(imgr) == eltype(img_camera) + @test_reference "warp_cameraman_rotate_r22deg_crop_white" imgr + + imgr = @inferred(invwarpedview(img_camera, tfm, indices(img_camera), Linear(), 1)) + @test summary(imgr) == "512×512 InvWarpedView(extrapolate(interpolate(::Array{Gray{N0f8},2}, BSpline(Linear()), OnGrid()), Gray{N0f8}(1.0)), AffineMap([0.92388 0.382683; -0.382683 0.92388], [-78.6334,$(SPACE)117.683])) with element type ColorTypes.Gray{FixedPointNumbers.Normed{UInt8,8}}" + @test @inferred(size(imgr)) == size(img_camera) + @test @inferred(size(imgr,3)) == 1 + @test typeof(parent(imgr)) <: Interpolations.FilledExtrapolation + @test parent(imgr).itp.coefs === img_camera + @test indices(imgr) === indices(img_camera) + @test typeof(imgr) <: InvWarpedView + @test eltype(imgr) == eltype(img_camera) + @test_reference "warp_cameraman_rotate_r22deg_crop_white" imgr + + imgr = @inferred(invwarpedview(img_camera, tfm, 1)) + @test summary(imgr) == "-78:591×-78:591 InvWarpedView(extrapolate(interpolate(::Array{Gray{N0f8},2}, BSpline(Linear()), OnGrid()), Gray{N0f8}(1.0)), AffineMap([0.92388 0.382683; -0.382683 0.92388], [-78.6334,$(SPACE)117.683])) with element type ColorTypes.Gray{FixedPointNumbers.Normed{UInt8,8}}" + @test_throws ErrorException size(imgr) + @test_throws ErrorException size(imgr, 1) + @test_throws ErrorException size(imgr, 5) + @test typeof(parent(imgr)) <: Interpolations.FilledExtrapolation + @test parent(imgr).itp.coefs === img_camera + @test typeof(imgr) <: InvWarpedView + @test eltype(imgr) == eltype(img_camera) + @test_reference "warp_cameraman_rotate_r22deg_white" imgr + + imgr = @inferred(invwarpedview(img_camera, tfm, Flat())) + @test summary(imgr) == "-78:591×-78:591 InvWarpedView(extrapolate(interpolate(::Array{Gray{N0f8},2}, BSpline(Linear()), OnGrid()), Flat()), AffineMap([0.92388 0.382683; -0.382683 0.92388], [-78.6334,$(SPACE)117.683])) with element type ColorTypes.Gray{FixedPointNumbers.Normed{UInt8,8}}" + @test_throws ErrorException size(imgr) + @test_throws ErrorException size(imgr, 1) + @test_throws ErrorException size(imgr, 5) + @test typeof(parent(imgr)) <: Interpolations.Extrapolation + @test parent(imgr).itp.coefs === img_camera + @test typeof(imgr) <: InvWarpedView + @test eltype(imgr) == eltype(img_camera) + @test_reference "warp_cameraman_rotate_r22deg_flat" imgr + imgr = @inferred(invwarpedview(img_camera, tfm, ref_inds, Flat())) + @test eltype(imgr) == eltype(img_camera) + @test indices(imgr) === ref_inds + @test_reference "warp_cameraman_rotate_r22deg_flat" imgr + + imgr = @inferred(invwarpedview(img_camera, tfm, Constant(), Periodic())) + @test summary(imgr) == "-78:591×-78:591 InvWarpedView(extrapolate(interpolate(::Array{Gray{N0f8},2}, BSpline(Constant()), OnGrid()), Periodic()), AffineMap([0.92388 0.382683; -0.382683 0.92388], [-78.6334,$(SPACE)117.683])) with element type ColorTypes.Gray{FixedPointNumbers.Normed{UInt8,8}}" + @test_throws ErrorException size(imgr) + @test_throws ErrorException size(imgr, 1) + @test_throws ErrorException size(imgr, 5) + @test typeof(parent(imgr)) <: Interpolations.Extrapolation + @test parent(imgr).itp.coefs === img_camera + @test typeof(imgr) <: InvWarpedView + @test eltype(imgr) == eltype(img_camera) + @test_reference "warp_cameraman_rotate_r22deg_periodic" imgr + + imgr = @inferred(invwarpedview(img_camera, tfm, ref_inds, Constant(), Periodic())) + @test summary(imgr) == "-78:591×-78:591 InvWarpedView(extrapolate(interpolate(::Array{Gray{N0f8},2}, BSpline(Constant()), OnGrid()), Periodic()), AffineMap([0.92388 0.382683; -0.382683 0.92388], [-78.6334,$(SPACE)117.683])) with element type ColorTypes.Gray{FixedPointNumbers.Normed{UInt8,8}}" + @test_throws ErrorException size(imgr) + @test_throws ErrorException size(imgr, 1) + @test_throws ErrorException size(imgr, 5) + @test eltype(imgr) == eltype(img_camera) + @test_reference "warp_cameraman_rotate_r22deg_periodic" imgr end end @@ -78,15 +304,25 @@ ref_img_pyramid_quad = Float64[ NaN NaN -0.038 0.205 -0.038 NaN NaN; NaN NaN NaN 0.003 NaN NaN NaN; ] +ref_img_pyramid_grid = Float64[ + NaN NaN NaN NaN NaN NaN NaN; + NaN NaN NaN 0.157977 NaN NaN NaN; + NaN NaN 0.223858 0.654962 0.223858 NaN NaN; + NaN 0.157977 0.654962 1.0 0.654962 0.157977 NaN; + NaN NaN 0.223858 0.654962 0.223858 NaN NaN; + NaN NaN NaN 0.157977 NaN NaN NaN; + NaN NaN NaN NaN NaN NaN NaN; +] @testset "Result against reference" begin - tfm1 = recenter(RotMatrix(-pi/4), center(img_pyramid)) - tfm2 = LinearMap(RotMatrix(-pi/4)) + tfm1 = recenter(RotMatrix(pi/4), center(img_pyramid)) + tfm2 = LinearMap(RotMatrix(pi/4)) @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)) @@ -94,6 +330,10 @@ ref_img_pyramid_quad = Float64[ imgr_cntr = warp(img_pyramid_cntr, tfm2) @test indices(imgr_cntr) == (-3:3, -3:3) @test nearlysame(parent(imgr_cntr), parent(imgr)) + + imgr_cntr = warp(img_pyramid_cntr, tfm2, (-1:1,-1:1)) + @test indices(imgr_cntr) == (-1:1, -1:1) + @test nearlysame(parent(imgr_cntr), imgr[2:4,2:4]) end @testset "Quadratic Interpolation" begin @@ -101,25 +341,30 @@ ref_img_pyramid_quad = Float64[ imgrq_cntr = warp(itp, tfm2) @test indices(imgrq_cntr) == (-3:3, -3:3) @test nearlysame(round.(Float64.(parent(imgrq_cntr)),3), round.(ref_img_pyramid_quad,3)) + + imgrq_cntr = warp(img_pyramid_cntr, tfm2, Quadratic(Flat())) + @test indices(imgrq_cntr) == (-3:3, -3:3) + @test nearlysame(round.(Float64.(parent(imgrq_cntr)),3), round.(ref_img_pyramid_grid,3)) end end - @testset "WarpedView" begin - imgr = WarpedView(img_pyramid, tfm1) + @testset "InvWarpedView" begin + imgr = InvWarpedView(img_pyramid, inv(tfm1)) @test indices(imgr) == (0:6, 0:6) # Use map and === because of the NaNs @test nearlysame(round.(Float64.(imgr[0:6, 0:6]),3), round.(ref_img_pyramid,3)) @testset "OffsetArray" begin - imgr_cntr = WarpedView(img_pyramid_cntr, tfm2) + imgr_cntr = InvWarpedView(img_pyramid_cntr, inv(tfm2)) @test indices(imgr_cntr) == (-3:3, -3:3) @test nearlysame(imgr_cntr[indices(imgr_cntr)...], imgr[indices(imgr)...]) end @testset "Quadratic Interpolation" begin itp = interpolate(img_pyramid_cntr, BSpline(Quadratic(Flat())), OnCell()) - imgrq_cntr = WarpedView(itp, tfm2) - @test summary(imgrq_cntr) == "-3:3×-3:3 WarpedView(interpolate(::OffsetArray{Gray{Float64},2}, BSpline(Quadratic(Flat())), OnCell()), LinearMap([0.707107 0.707107; -0.707107 0.707107])) with element type ColorTypes.Gray{Float64}" + imgrq_cntr = InvWarpedView(itp, inv(tfm2)) + @test parent(imgrq_cntr) === itp + @test summary(imgrq_cntr) == "-3:3×-3:3 InvWarpedView(interpolate(::OffsetArray{Gray{Float64},2}, BSpline(Quadratic(Flat())), OnCell()), LinearMap([0.707107 0.707107; -0.707107 0.707107])) with element type ColorTypes.Gray{Float64}" @test indices(imgrq_cntr) == (-3:3, -3:3) @test nearlysame(round.(Float64.(imgrq_cntr[indices(imgrq_cntr)...]),3), round.(ref_img_pyramid_quad,3)) end