Skip to content

Commit

Permalink
Introduce RCropSize() - Function for generating random crops of fixed…
Browse files Browse the repository at this point in the history
… size (#64)

* introduce RCropSize

* Improve error message

Co-authored-by: Max Freudenberg <[email protected]>
  • Loading branch information
maxfreu and Max Freudenberg authored Aug 25, 2020
1 parent 51172d8 commit 30b0d75
Show file tree
Hide file tree
Showing 3 changed files with 232 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/Augmentor.jl
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export
CropSize,
CropRatio,
RCropRatio,
RCropSize,

Resize,

Expand Down
120 changes: 119 additions & 1 deletion src/operations/crop.jl
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ Arguments
See also
--------------
[`CropRatio`](@ref), [`CropSize`](@ref), [`Crop`](@ref), [`CropNative`](@ref), [`augment`](@ref)
[`RCropSize`](@ref), [`CropRatio`](@ref), [`CropSize`](@ref), [`Crop`](@ref), [`CropNative`](@ref), [`augment`](@ref)
Examples
--------------
Expand Down Expand Up @@ -542,3 +542,121 @@ function Base.show(io::IO, op::RCropRatio)
showconstruction(io, op)
end
end

"""
RCropSize <: Augmentor.ImageOperation
Description
--------------
Crops out an area of predefined size at some random position of
the given image.
For example the operation `RCropSize(128, 64)` denotes a random crop with height
128 and width 64. `RCropSize(64)` denotes a square shaped crop of size 64.
Usage
--------------
RCropSize(height, width)
RCropSize(width)
Arguments
--------------
- **`height::Number`** : Height of cropped region
- **`width::Number`** : Width of cropped region
See also
--------------
[`RCropRatio`](@ref), [`CropRatio`](@ref), [`CropSize`](@ref), [`Crop`](@ref), [`CropNative`](@ref), [`augment`](@ref)
Examples
--------------
```julia
using Augmentor
img = testpattern()
# crop a randomly placed square of size 100
augment(img, RCropSize(100))
```
"""
struct RCropSize <: ImageOperation
height::Int
width::Int

function RCropSize(height::Integer, width::Integer)
(width > 0 && height > 0) || throw(ArgumentError("Width and height must be greater than 0."))
new(Int(height), Int(width))
end
end
# RCropSize(height::Integer, width::Integer) = RCropSize(height, width)
RCropSize(height::Integer) = RCropSize(height, height)

@inline supports_eager(::Type{RCropSize}) = false
@inline supports_affineview(::Type{RCropSize}) = true
@inline supports_view(::Type{RCropSize}) = true
@inline supports_stepview(::Type{RCropSize}) = true

function rcropsize_axes(op::RCropSize, img::AbstractMatrix)
h, w = size(img)
#new size dictated by op
nw = op.width
nh = op.height
# place window at a random position
if nw == w && nh == h
return 1:h, 1:w
elseif nw < w && nh < h
x_max = w - nw + 1
y_max = h - nh + 1
x = safe_rand(1:x_max)
y = safe_rand(1:y_max)
return y:(y+nh-1), x:(x+nw-1)
elseif nw < w && nh == h
x_max = w - nw + 1
x = safe_rand(1:x_max)
return 1:h, x:(x+nw-1)
elseif nh < h && nw == w
y_max = h - nh + 1
y = safe_rand(1:y_max)
return y:(y+nh-1), 1:w
else
# alternative behaviour could be to return the whole image or to pad
error("Trying to crop region of size ($nh, $nw) from image with size $(size(img)).")
end
end

randparam(op::RCropSize, imgs::Tuple) = rcropsize_axes(op, imgs[1])
randparam(op::RCropSize, img::AbstractArray) = rcropsize_axes(op, img)

function applylazy(op::RCropSize, img::AbstractArray, inds)
applyview(op, img, inds)
end

function applyaffineview(op::RCropSize, img::AbstractArray, inds)
applyview(op, prepareaffine(img), inds)
end

function applyview(op::RCropSize, img::AbstractArray, inds)
indirect_view(img, inds)
end

function applystepview(op::RCropSize, img::AbstractArray, inds)
indirect_view(img, map(StepRange, inds))
end

function showconstruction(io::IO, op::RCropSize)
print(io, typeof(op).name.name, '(', op.height, ", ", op.width, ')')
end

function Base.show(io::IO, op::RCropSize)
if get(io, :compact, false)
print(io, "Crop random window with size ($(op.height), $(op.width))")
else
print(io, "Augmentor.")
showconstruction(io, op)
end
end
112 changes: 112 additions & 0 deletions test/operations/tst_crop.jl
Original file line number Diff line number Diff line change
Expand Up @@ -632,3 +632,115 @@ end
@test @inferred(Augmentor.supports_permute(RCropRatio)) === false
end
end

# --------------------------------------------------------------------

@testset "RCropSize" begin
@test (RCropSize <: Augmentor.AffineOperation) == false
@test typeof(@inferred(RCropSize(1))) <: RCropSize <: Augmentor.ImageOperation
@testset "constructor" begin
@test_throws MethodError RCropSize(())
@test_throws MethodError RCropSize(1.,2.)
@test_throws MethodError RCropSize(:a)
@test_throws MethodError RCropSize([:a])
@test_throws ArgumentError RCropSize(-1)
@test_throws ArgumentError RCropSize(0)
op = @inferred(RCropSize(100, 100))
@test op === RCropSize(100, 100)
@test str_show(op) == "Augmentor.RCropSize(100, 100)"
@test str_showconst(op) == "RCropSize(100, 100)"
@test str_showcompact(op) == "Crop random window with size (100, 100)"
end
@testset "randparam" begin
@test @inferred(Augmentor.randparam(RCropSize(3), square)) == (1:3, 1:3)
@test @inferred(Augmentor.randparam(RCropSize(2,3), rect)) == (1:2, 1:3)
@test @inferred(Augmentor.randparam(RCropSize(2,2), rect)) in ((1:2, 1:2), (1:2, 2:3))
end
imgs = [
(rect, (1:2, 1:3)),
(Augmentor.prepareaffine(rect), (1:2, 1:3)),
(OffsetArray(rect, -2, -1), (-1:0, 0:2)),
(view(rect, IdentityRange(1:2), IdentityRange(1:3)), (1:2, 1:3)),
]
@testset "eager" begin
@test_throws MethodError Augmentor.applyeager(RCropSize(10), nothing)
@test Augmentor.supports_eager(RCropSize) === false
@test @inferred(Augmentor.applyeager(RCropSize(3), square)) == square
@test @inferred(Augmentor.applyeager(RCropSize(4), square2)) == square2
out = collect(@inferred(Augmentor.applyeager(RCropSize(2,2), rect)))
@test out == rect[1:2,1:2] || out == rect[1:2,2:3]
@testset "single image" begin
for (img_in, inds) in imgs
res = @inferred(Augmentor.applyeager(RCropSize(2,3), img_in))
@test collect(res) == img_in[inds...]
@test axes(res) == map(n->1:n, size(res)) # 1-base
@test typeof(res) <: Array{eltype(img_in),2}
end
end
@testset "multiple images" begin
for (img_in, _) in imgs
res1, res2 = @inferred(Augmentor.applyeager(RCropSize(2), (img_in, N0f8.(img_in))))
# make sure both images are processed
@test res1 == res2
@test axes(res1) == axes(res2)
@test typeof(res1) <: Array{Gray{N0f8}}
@test typeof(res2) <: Array{N0f8}
end
end
end
@testset "affine" begin
@test Augmentor.supports_affine(RCropSize) === false
end
@testset "affineview" begin
@test Augmentor.supports_affineview(RCropSize) === true
@test_throws MethodError Augmentor.applyaffineview(RCropSize(1), nothing)
@test @inferred(Augmentor.applyaffineview(RCropSize(2,3), rect)) == view(Augmentor.prepareaffine(rect), IdentityRange(1:2), IdentityRange(1:3))
@test @inferred(Augmentor.applyaffineview(RCropSize(3), square)) == view(Augmentor.prepareaffine(square), IdentityRange(1:3), IdentityRange(1:3))
@test @inferred(Augmentor.applyaffineview(RCropSize(4), square2)) == view(Augmentor.prepareaffine(square2), IdentityRange(1:4), IdentityRange(1:4))
# randomly placed
out = @inferred Augmentor.applyaffineview(RCropSize(2), rect)
@test out == view(Augmentor.prepareaffine(rect), IdentityRange(1:2), IdentityRange(1:2)) || out == view(Augmentor.prepareaffine(rect), IdentityRange(1:2), IdentityRange(2:3))
out = @inferred Augmentor.applyaffineview(RCropSize(2), square)
@test (out == view(Augmentor.prepareaffine(square), IdentityRange(1:2), IdentityRange(1:2)) || out == view(Augmentor.prepareaffine(square), IdentityRange(1:2), IdentityRange(2:3))
|| out == view(Augmentor.prepareaffine(square), IdentityRange(2:3), IdentityRange(1:2)) || out == view(Augmentor.prepareaffine(square), IdentityRange(2:3), IdentityRange(2:3)))
end
@testset "lazy" begin
@test Augmentor.supports_lazy(RCropSize) === true
@test @inferred(Augmentor.applylazy(RCropSize(2,3), rect)) === view(rect, IdentityRange(1:2), IdentityRange(1:3))
@test @inferred(Augmentor.applylazy(RCropSize(3), square)) === view(square, IdentityRange(1:3), IdentityRange(1:3))
@test @inferred(Augmentor.applylazy(RCropSize(4), square2)) === view(square2, IdentityRange(1:4), IdentityRange(1:4))
# randomly placed
out = @inferred Augmentor.applylazy(RCropSize(2), rect)
@test out === view(rect, IdentityRange(1:2), IdentityRange(1:2)) || out === view(rect, IdentityRange(1:2), IdentityRange(2:3))
out = @inferred Augmentor.applylazy(RCropSize(2), square)
@test (out === view(square, IdentityRange(1:2), IdentityRange(1:2)) || out === view(square, IdentityRange(1:2), IdentityRange(2:3))
|| out === view(square, IdentityRange(2:3), IdentityRange(1:2)) || out === view(square, IdentityRange(2:3), IdentityRange(2:3)))
end
@testset "view" begin
@test Augmentor.supports_view(RCropSize) === true
@test @inferred(Augmentor.applyview(RCropSize(2,3), rect)) === view(rect, IdentityRange(1:2), IdentityRange(1:3))
@test @inferred(Augmentor.applyview(RCropSize(3), square)) === view(square, IdentityRange(1:3), IdentityRange(1:3))
@test @inferred(Augmentor.applyview(RCropSize(4), square2)) === view(square2, IdentityRange(1:4), IdentityRange(1:4))
# randomly placed
out = @inferred Augmentor.applyview(RCropSize(2), rect)
@test out === view(rect, IdentityRange(1:2), IdentityRange(1:2)) || out === view(rect, IdentityRange(1:2), IdentityRange(2:3))
out = @inferred Augmentor.applyview(RCropSize(2), square)
@test (out === view(square, IdentityRange(1:2), IdentityRange(1:2)) || out === view(square, IdentityRange(1:2), IdentityRange(2:3))
|| out === view(square, IdentityRange(2:3), IdentityRange(1:2)) || out === view(square, IdentityRange(2:3), IdentityRange(2:3)))
end
@testset "stepview" begin
@test Augmentor.supports_stepview(RCropSize) === true
@test @inferred(Augmentor.applystepview(RCropSize(2,3), rect)) === view(rect, 1:1:2, 1:1:3)
@test @inferred(Augmentor.applystepview(RCropSize(3), square)) === view(square, 1:1:3, 1:1:3)
@test @inferred(Augmentor.applystepview(RCropSize(4), square2)) === view(square2, 1:1:4, 1:1:4)
# randomly placed
out = @inferred Augmentor.applystepview(RCropSize(2), rect)
@test out === view(rect, 1:1:2, 1:1:2) || out === view(rect, 1:1:2, 2:1:3)
out = @inferred Augmentor.applystepview(RCropSize(2), square)
@test (out === view(square, 1:1:2, 1:1:2) || out === view(square, 1:1:2, 2:1:3)
|| out === view(square, 2:1:3, 1:1:2) || out === view(square, 2:1:3, 2:1:3))
end
@testset "permute" begin
@test @inferred(Augmentor.supports_permute(RCropSize)) === false
end
end

0 comments on commit 30b0d75

Please sign in to comment.