Skip to content

Commit eeee0bf

Browse files
authored
Restrict rounding to the computed corners (#143)
Rounding the transformation makes assumptions about the nature of these transformations. However, in principle any kind of function mapping points to new points should be supported. It seems safer to perform rounding on the *result* of the transformation.
1 parent 226d504 commit eeee0bf

File tree

7 files changed

+23
-31
lines changed

7 files changed

+23
-31
lines changed

Project.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,19 @@ ColorVectorSpace = "0.6, 0.7, 0.8, 0.9"
1919
CoordinateTransformations = "0.5, 0.6"
2020
ImageBase = "0.1.1"
2121
ImageCore = "0.8.1, 0.9"
22-
Interpolations = "0.9, 0.10, 0.11, 0.12, 0.13"
22+
Interpolations = "0.13.4"
2323
OffsetArrays = "0.10, 0.11, 1.0.1"
2424
Rotations = "0.12, 0.13, 1.0"
2525
StaticArrays = "0.10, 0.11, 0.12, 1.0"
2626
julia = "1"
2727

2828
[extras]
29+
EndpointRanges = "340492b5-2a47-5f55-813d-aca7ddf97656"
2930
ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1"
3031
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
3132
ReferenceTests = "324d217c-45ce-50fc-942e-d289b448e8cf"
3233
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
3334
TestImages = "5e47fb64-e119-507b-a336-dd2b206d9990"
3435

3536
[targets]
36-
test = ["ImageMagick", "LinearAlgebra", "ReferenceTests", "Test", "TestImages"]
37+
test = ["EndpointRanges", "ImageMagick", "LinearAlgebra", "ReferenceTests", "Test", "TestImages"]

src/autorange.jl

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ autorange(A, tform)
3434
"""
3535
autorange(A::AbstractArray, tform) = autorange(CartesianIndices(A), tform)
3636
function autorange(R::CartesianIndices, tform)
37-
tform = _round(tform)
3837
mn = mx = tform(SVector(first(R).I))
3938
for I in CornerIterator(R)
4039
x = tform(SVector(I.I))
@@ -46,7 +45,7 @@ function autorange(R::CartesianIndices, tform)
4645
_autorange(Tuple(mn), Tuple(mx))
4746
end
4847

49-
@noinline _autorange(mn,mx) = map((a,b)->floor(Int,a):ceil(Int,b), mn, mx)
48+
@noinline _autorange(mn,mx) = map((a,b)->floor(Int,_round(a)):ceil(Int,_round(b)), mn, mx)
5049

5150
## Iterate over the corner-indices of a rectangular region
5251
struct CornerIterator{I<:CartesianIndex}
@@ -106,23 +105,13 @@ try_static(tfm::Translation{<:SVector}, img::AbstractArray{T,N}) where {T,N} = t
106105
try_static(tfm::Translation{<:AbstractVector}, img::AbstractArray{T,N}) where {T,N} =
107106
Translation(SVector{N}(tfm.translation))
108107

109-
# Slightly round/discretize the transformation so that the warpped image size isn't affected by
108+
# Slightly round/discretize the output coordinates so that the warped image size isn't affected by
110109
# numerical stability
111110
# https://github.com/JuliaImages/ImageTransformations.jl/issues/104
112111
_default_digits(::Type{T}) where T<:Number = _default_digits(floattype(T))
113112
# these constants come from eps() digits
114-
_default_digits(::Type{<:AbstractFloat}) = 15
115-
_default_digits(::Type{Float64}) = 15
116-
_default_digits(::Type{Float32}) = 7
113+
_default_digits(::Type{T}) where T<:AbstractFloat = ceil(Int, -log10(sqrt(eps(T))))
114+
_default_digits(::Type{Float64}) = 8
115+
_default_digits(::Type{Float32}) = 4
117116

118-
function _round(tform::T; kwargs...) where T<:CoordinateTransformations.Transformation
119-
rounded_fields = map(Base.OneTo(fieldcount(T))) do i
120-
__round(getfield(tform, i); kwargs...)
121-
end
122-
T(rounded_fields...)
123-
end
124-
_round(tform; kwargs...) = tform
125-
126-
__round(x; kwargs...) = x
127-
__round(x::AbstractArray; digits=_default_digits(eltype(x)), kwargs...) = round.(x; digits=digits, kwargs...)
128-
__round(x::T; digits=_default_digits(T), kwargs...) where T<:Number = round(x; digits=digits, kwargs...)
117+
_round(x::T; digits=_default_digits(T), kwargs...) where T<:Number = round(x; digits=digits, kwargs...)

src/compat.jl

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
# This file includes two kinds of codes
22
# - Codes for backward compatibility
3-
# - Glue codes that might nolonger be necessary in the future
4-
5-
# patch for issue #110
6-
if isdefined(Base, :ComposedFunction) # Julia >= 1.6.0-DEV.85
7-
# https://github.com/JuliaLang/julia/pull/37517
8-
_round(tform::ComposedFunction; kwargs...) = _round(tform.outer; kwargs...) _round(tform.inner; kwargs...)
9-
end
3+
# - Glue codes that might no longer be necessary in the future
104

115
@static if !isdefined(Base, :IdentityUnitRange)
126
const IdentityUnitRange = Base.Slice

src/invwarpedview.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ struct InvWarpedView{T,N,A,F,I,FI<:Transformation,E} <: AbstractArray{T,N}
2222
end
2323

2424
function InvWarpedView(inner::WarpedView{T,N,TA,F,I,E}) where {T,N,TA,F,I,E}
25-
tinv = _round(inv(inner.transform))
25+
tinv = inv(inner.transform)
2626
InvWarpedView{T,N,TA,F,I,typeof(tinv),E}(inner, tinv)
2727
end
2828

src/warp.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ This approach is known as backward mode warping. It is called "backward" because
5454
the internal coordinate transformation is actually an inverse map from `axes(imgr)` to `axes(img)`.
5555
5656
You can manually specify interpolation behavior by constructing `AbstractExtrapolation` object
57-
and passing it to `warp` as `img`. However, this is usually cumbersome. For this reason, there
57+
and passing it to `warp` as `img`. However, this is usually cumbersome. For this reason, there
5858
are two keywords `method` and `fillvalue` to conveniently construct an `AbstractExtrapolation`
5959
object during `warp`.
6060
@@ -163,7 +163,6 @@ function warp(img::AbstractExtrapolation{T}, tform, inds::Tuple = autorange(img,
163163
end
164164

165165
function warp!(out, img::AbstractExtrapolation, tform)
166-
tform = _round(tform)
167166
@inbounds for I in CartesianIndices(axes(out))
168167
# Backward mode:
169168
# 1. get the target index `I` of `out`

src/warpedview.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ function WarpedView(
1919
tform::Transformation,
2020
inds=autorange(A, inv(tform)); kwargs...) where {T,N,}
2121
etp = box_extrapolation(A; kwargs...)
22-
tform = _round(tform)
2322
WarpedView{T,N,typeof(A),typeof(tform),typeof(inds),typeof(etp)}(A, tform, inds, etp)
2423
end
2524

test/warp.jl

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using CoordinateTransformations, Rotations, TestImages, ImageCore, StaticArrays, OffsetArrays, Interpolations, LinearAlgebra
2+
using EndpointRanges
23
using Test, ReferenceTests
34

45
include("twoints.jl")
@@ -181,7 +182,8 @@ img_camera = testimage("camera")
181182
@test axes(wv2) == axes(img_camera)
182183
@test eltype(wv2) === eltype(img_camera)
183184
@test parent(wv2) === img_camera
184-
@test wv2 img_camera
185+
@test_skip wv2 img_camera # see discussion in #143
186+
@test wv2[ibegin+1:iend-1,ibegin+1:iend-1] img_camera[ibegin+1:iend-1,ibegin+1:iend-1] # TODO: change to begin/end, drop EndpointRanges
185187

186188
imgr = @inferred(invwarpedview(img_camera, tfm))
187189
@test imgr == @inferred(InvWarpedView(img_camera, tfm))
@@ -312,6 +314,14 @@ img_camera = testimage("camera")
312314
@test any(isnan, v)
313315
@test parent(v) isa InvWarpedView
314316
end
317+
318+
@testset "3d warps" begin
319+
img = testimage("mri")
320+
θ = π/8
321+
tfm = AffineMap(RotZ(θ), (I - RotZ(θ))*center(img))
322+
imgr = warpedview(img, tfm, axes(img))
323+
@test axes(imgr) == axes(img)
324+
end
315325
end
316326

317327
img_pyramid = Gray{Float64}[

0 commit comments

Comments
 (0)