-
Notifications
You must be signed in to change notification settings - Fork 27
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
[WIP] fixed point perspective for imresize
, zoom
and imrotate
#142
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,7 +6,23 @@ function restrict(img::Union{WarpedView, InvWarpedView}, dims::Integer) | |
restrict(OffsetArray(collect(img), axes(img)), dims) | ||
end | ||
|
||
# imresize | ||
########### | ||
# imresize/imresize! | ||
# | ||
# `imresize` API: | ||
# - `imresize(original; ratio, [method])` | ||
# - `imresize(original, inds; [method])` | ||
# - `imresize(original, sz; [method])` | ||
# The output type: | ||
# - If `inds` method get triggered, the output will unconditionally be `OffsetArray` type. | ||
# - Otherwise, it should preserve the original input array type. | ||
# | ||
# `imresize!` API: | ||
# - `imresize!(resized, original::AbstractArray; [method])` | ||
# - `imresize!(resized, original::AbstractInterpolation)` | ||
########### | ||
|
||
|
||
imresize(original::AbstractArray, dim1::T, dimN::T...; kwargs...) where T<:Union{Integer,AbstractUnitRange} = imresize(original, (dim1,dimN...); kwargs...) | ||
function imresize(original::AbstractArray; ratio, kwargs...) | ||
all(ratio .> 0) || throw(ArgumentError("ratio $ratio should be positive")) | ||
|
@@ -26,16 +42,18 @@ function imresize(original::AbstractArray, itp::Union{Interpolations.Degree,Inte | |
end | ||
|
||
odims(original, i, short_size::Tuple{Integer,Vararg{Integer}}) = size(original, i) | ||
odims(original, i, short_size::Tuple{}) = axes(original, i) | ||
odims(original, i, short_size::Tuple{}) = size(original, i) | ||
odims(original, i, short_size) = oftype(first(short_size), axes(original, i)) | ||
|
||
""" | ||
imresize(img, sz; [method]) -> imgr | ||
imresize(img, inds; [method]) -> imgr | ||
imresize(img, inds; [method]) -> imgr::OffsetArray | ||
imresize(img; ratio, [method]) -> imgr | ||
|
||
upsample/downsample the image `img` to a given size `sz` or axes `inds` using interpolations. If | ||
`ratio` is provided, the output size is then `ceil(Int, size(img).*ratio)`. | ||
upsample/downsample the image `img` to a given size `sz` or axes `inds` using interpolations. | ||
|
||
If `ratio` is provided, the output size is then `ceil(Int, size(img).*ratio)`. If axes information is | ||
provided by passing `inds`, then the output array is `OffsetArray`. | ||
|
||
!!! tip | ||
This interpolates the values at sub-pixel locations. If you are shrinking the image, you risk | ||
|
@@ -104,19 +122,42 @@ function imresize(original::AbstractArray{T,N}, new_size::Dims{N}; kwargs...) wh | |
if axes(dest) == inds | ||
copyto!(dest, original) | ||
else | ||
# Non 1-based case as a fallback solution | ||
# The OffsetArray case is specially handled to also output OffsetArray | ||
copyto!(dest, CartesianIndices(axes(dest)), original, CartesianIndices(inds)) | ||
end | ||
else | ||
imresize!(similar(original, Tnew, new_size), original; kwargs...) | ||
end | ||
end | ||
|
||
function imresize(original::OffsetArray{T,N}, new_size::Dims{N}; kwargs...) where {T,N} | ||
Tnew = imresize_type(first(original)) | ||
inds = axes(original) | ||
new_inds = map((ax, n)->first(ax):first(ax)+n-1, axes(original), new_size) | ||
if map(length, inds) == new_size | ||
dest = similar(original, Tnew, new_inds) | ||
if axes(dest) == new_inds | ||
# a trivial case of OffsetArray | ||
copyto!(parent(dest), original) | ||
else | ||
copyto!(dest, CartesianIndices(axes(dest)), original, CartesianIndices(inds)) | ||
end | ||
else | ||
dest = imresize(original, new_inds; kwargs...) | ||
end | ||
return dest | ||
end | ||
|
||
function imresize(original::AbstractArray{T,N}, new_inds::Indices{N}; kwargs...) where {T,N} | ||
Tnew = imresize_type(first(original)) | ||
# The pirated `similar` method from OffsetArrays will be triggered | ||
# thus for type stability, we unconditionally output an `OffsetArray`. | ||
origin = OffsetArrays.Origin(map(first, new_inds)) | ||
if axes(original) == new_inds | ||
copyto!(similar(original, Tnew), original) | ||
OffsetArray(copyto!(similar(original, Tnew), original), origin) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather than manually create an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe this won't be type stable due to the With this change: - OffsetArray(copyto!(similar(original, Tnew), original), origin)
+ copyto!(similar(original, Tnew, new_inds), original) julia> img = testimage("cameraman");
julia> @inferred imresize(img, axes(img))
ERROR: return type Matrix{Gray{N0f8}} does not match inferred return type Union{Matrix{Gray{N0f8}}, OffsetMatrix{Gray{N0f8}, Matrix{Gray{N0f8}}}} |
||
else | ||
imresize!(similar(original, Tnew, new_inds), original; kwargs...) | ||
OffsetArray(imresize!(similar(original, Tnew, new_inds), original; kwargs...), origin) | ||
end | ||
end | ||
|
||
|
@@ -140,6 +181,9 @@ function imresize!(resized::AbstractArray{T,N}, original::AbstractArray{S,N}; me | |
imresize!(resized, itp) | ||
end | ||
|
||
# If we use the `warp` API then we need to build the backward coordinate map function | ||
# as a closure, which would unavoidably introduce the overhead. (Although Julia compiler | ||
# might optimize it away) | ||
function imresize!(resized::AbstractArray{T,N}, original::AbstractInterpolation{S,N}) where {T,S,N} | ||
# Define the equivalent of an affine transformation for mapping | ||
# locations in `resized` to the corresponding position in | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
""" | ||
zoom(img; ratio, [fixed_point], kwargs...) | ||
|
||
Zoom in/out. | ||
""" | ||
function zoom(img; ratio, kwargs...) | ||
all(ratio .> 0) || throw(ArgumentError("ratio $ratio should be positive")) | ||
new_size = ceil.(Int, size(img) .* ratio) # use ceil to avoid 0 | ||
_zoom(img, new_size; kwargs...) | ||
end | ||
|
||
# TODO: | ||
# before we make this a part of API, we need to figure out how should we interpret | ||
# the axes information. | ||
function _zoom(img, size_or_axes; fixed_point=OffsetArrays.center(img), kwargs...) | ||
# zoom introduces out-of-domain points, so we need to build extrapolation | ||
|
||
# Because `first(CartesianIndices(R)) == oneunit(first(R))`, we need to | ||
# preserve the axes information if `size_or_axes` is actually an CartesianIndices `R` | ||
Rdst = if size_or_axes isa AbstractArray{<:CartesianIndex} | ||
size_or_axes | ||
else | ||
CartesianIndices(size_or_axes) | ||
end | ||
Rsrc = CartesianIndices(img) | ||
tform = zoom_coordinate_map(Rdst, Rsrc, fixed_point) | ||
@assert tform(SVector(fixed_point)) == SVector(fixed_point) | ||
|
||
warp(img, tform, Rsrc.indices) | ||
end | ||
|
||
function zoom_coordinate_map(Rdst, Rsrc, c) | ||
k = SVector((size(Rsrc) .- 1) ./(size(Rdst) .- 1)) | ||
b = @. (1-k)*c | ||
return x->@. k*x + b | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,28 +25,62 @@ end | |
testtype = (Float32, Float64, N0f8, N0f16) | ||
|
||
@testset "Interface" begin | ||
function test_imresize_interface(img, outsz, args...; kargs...) | ||
img2 = @test_broken @inferred imresize(img, args...; kargs...) # FIXME: @inferred failed | ||
img2 = @test_nowarn imresize(img, args...; kargs...) | ||
@test size(img2) == outsz | ||
@test eltype(img2) == eltype(img) | ||
function test_imresize_interface(src_img, outsz, outinds, args...; kwargs...) | ||
out = @inferred imresize(src_img, args...; kwargs...) | ||
@test size(out) == outsz | ||
@test axes(out) == outinds | ||
@test eltype(out) == eltype(src_img) | ||
return out | ||
end | ||
for C in testcolor, T in testtype | ||
img = rand(C{T},10,10) | ||
|
||
test_imresize_interface(img, (5,5), (5,5)) | ||
test_imresize_interface(img, (5,5), (1:5,1:5)) # FIXME: @inferred failed | ||
test_imresize_interface(img, (5,5), 5,5) | ||
test_imresize_interface(img, (5,5), 1:5,1:5) # FIXME: @inferred failed | ||
test_imresize_interface(img, (5,5), ratio = 0.5) | ||
test_imresize_interface(img, (20,20), ratio = 2) | ||
test_imresize_interface(img, (20,20), ratio = (2, 2)) | ||
test_imresize_interface(img, (20,10), ratio = (2, 1)) | ||
test_imresize_interface(img, (10,10), ()) | ||
test_imresize_interface(img, (5,10), 5) | ||
test_imresize_interface(img, (5,10), (5,)) | ||
test_imresize_interface(img, (5,10), 1:5) # FIXME: @inferred failed | ||
test_imresize_interface(img, (5,10), (1:5,)) # FIXME: @inferred failed | ||
@test test_imresize_interface(img, (10,10), (1:10, 1:10), ()) isa Array | ||
|
||
@test test_imresize_interface(img, (5,5), (1:5, 1:5), (5,5)) isa Array | ||
@test test_imresize_interface(img, (5,5), (1:5, 1:5), 5, 5) isa Array | ||
@test test_imresize_interface(img, (5,10), (1:5, 1:10), 5) isa Array | ||
@test test_imresize_interface(img, (5,10), (1:5, 1:10), (5,)) isa Array | ||
|
||
@test test_imresize_interface(img, (5,5), (1:5, 1:5), ratio = 0.5) isa Array | ||
@test test_imresize_interface(img, (20,20), (1:20, 1:20), ratio = 2) isa Array | ||
@test test_imresize_interface(img, (20,20), (1:20, 1:20), ratio = (2, 2)) isa Array | ||
@test test_imresize_interface(img, (20,10), (1:20, 1:10), ratio = (2, 1)) isa Array | ||
|
||
# indices method always return OffsetArray | ||
@test test_imresize_interface(img, (5,5), (1:5, 1:5), (1:5,1:5)) isa OffsetArray | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also add @test test_imresize_interface(img, (5,5), (1:5, 1:5), (Base.OneTo(5),Base.OneTo(5))) isa Array |
||
@test test_imresize_interface(img, (5,5), (1:5, 1:5), 1:5,1:5) isa OffsetArray | ||
@test test_imresize_interface(img, (5,10), (1:5, 1:10), 1:5) isa OffsetArray | ||
@test test_imresize_interface(img, (5,10), (1:5, 1:10), (1:5,)) isa OffsetArray | ||
|
||
@test_throws MethodError imresize(img,5.0,5.0) | ||
@test_throws MethodError imresize(img,(5.0,5.0)) | ||
@test_throws MethodError imresize(img,(5, 5.0)) | ||
@test_throws MethodError imresize(img,[5,5]) | ||
@test_throws UndefKeywordError imresize(img) | ||
@test_throws DimensionMismatch imresize(img,(5,5,5)) | ||
@test_throws ArgumentError imresize(img, ratio = -0.5) | ||
@test_throws ArgumentError imresize(img, ratio = (-0.5, 1)) | ||
@test_throws DimensionMismatch imresize(img, ratio=(5,5,5)) | ||
@test_throws DimensionMismatch imresize(img, (5,5,1)) | ||
end | ||
|
||
for C in testcolor, T in testtype | ||
img = OffsetArray(rand(C{T},10,10), -5, -4) | ||
|
||
@test test_imresize_interface(img, (5,5), (-4:0, -3:1), (5,5)) isa OffsetArray | ||
@test test_imresize_interface(img, (5,5), (-4:0, -3:1), 5, 5) isa OffsetArray | ||
@test test_imresize_interface(img, (5,5), (1:5, 1:5), (1:5,1:5)) isa OffsetArray | ||
@test test_imresize_interface(img, (5,5), (1:5, 1:5), 1:5,1:5) isa OffsetArray | ||
@test test_imresize_interface(img, (5,5), (-4:0, -3:1), ratio = 0.5) isa OffsetArray | ||
@test test_imresize_interface(img, (20,20), (-4:15, -3:16), ratio = 2) isa OffsetArray | ||
@test test_imresize_interface(img, (20,20), (-4:15, -3:16), ratio = (2, 2)) isa OffsetArray | ||
@test test_imresize_interface(img, (20,10), (-4:15, -3:6), ratio = (2, 1)) isa OffsetArray | ||
@test test_imresize_interface(img, (10,10), (-4:5, -3:6), ()) isa OffsetArray | ||
@test test_imresize_interface(img, (5,10), (-4:0, -3:6), 5) isa OffsetArray | ||
@test test_imresize_interface(img, (5,10), (-4:0, -3:6), (5,)) isa OffsetArray | ||
@test test_imresize_interface(img, (5,10), (1:5, -3:6), 1:5) isa OffsetArray | ||
@test test_imresize_interface(img, (5,10), (1:5, -3:6), (1:5,)) isa OffsetArray | ||
|
||
@test_throws MethodError imresize(img,5.0,5.0) | ||
@test_throws MethodError imresize(img,(5.0,5.0)) | ||
|
@@ -105,7 +139,7 @@ end | |
@test !(R === A) | ||
Ao = OffsetArray(A, -2:2, 0:4) | ||
R = imresize(Ao, (5,5)) | ||
@test axes(R) === axes(A) | ||
@test axes(R) === axes(Ao) | ||
R = imresize(Ao, axes(Ao)) | ||
@test axes(R) === axes(Ao) | ||
@test !(R === A) | ||
|
@@ -134,9 +168,10 @@ end | |
@test_throws ArgumentError imresize(img, Linear()) | ||
|
||
#consisency checks | ||
@test imresize(img, (128, 128), method=Linear()) == imresize(OffsetArray(img, -1, -1), (128, 128), method=Linear()) | ||
@test imresize(img, (128, 128), method=BSpline(Linear())) == imresize(OffsetArray(img, -1, -1), (128, 128), method=BSpline(Linear())) | ||
@test imresize(img, (128, 128), method=Lanczos4OpenCV()) == imresize(OffsetArray(img, -1, -1), (128, 128), method=Lanczos4OpenCV()) | ||
imgo = OffsetArray(img, -1, -1) | ||
@test imresize(img, (128, 128), method=Linear()) == OffsetArrays.no_offset_view(imresize(imgo, (128, 128), method=Linear())) | ||
@test imresize(img, (128, 128), method=BSpline(Linear())) == OffsetArrays.no_offset_view(imresize(imgo, (128, 128), method=BSpline(Linear()))) | ||
@test imresize(img, (128, 128), method=Lanczos4OpenCV()) == OffsetArrays.no_offset_view(imresize(imgo, (128, 128), method=Lanczos4OpenCV())) | ||
|
||
out = imresize(img, (0:127, 0:127), method=Linear()) | ||
@test axes(out) == (0:127, 0:127) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OffsetArray is not necessarily the only array type that doesn't have indexing starting with 1; there are several other examples. What's the reason for the special method? It looks almost identical to the above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only difference here is that the above method returns an
Array
while this new method forOffsetArray
unconditionally returns anOffsetArray
.I also have the same concerns and I'm not very satisfied with this version very much, but it seems there's no other way to work around the type instability if we put them into one method that returns either
OffsetArray
orArray
.