From e4024c6bd5c27f76caa925c9f15eef869b3980c8 Mon Sep 17 00:00:00 2001 From: Christof Stocker Date: Tue, 4 Jul 2017 18:59:17 +0200 Subject: [PATCH 1/5] outline mutating augmentbatch --- REQUIRE | 2 ++ src/Augmentor.jl | 5 +++++ src/augment.jl | 21 +++++++++++++++--- src/augmentbatch.jl | 46 ++++++++++++++++++++++++++++++++++++++ src/codegen.jl | 49 +++++++++++++++++++++++------------------ src/operations/cache.jl | 4 ---- src/utils.jl | 6 +++++ 7 files changed, 105 insertions(+), 28 deletions(-) create mode 100644 src/augmentbatch.jl diff --git a/REQUIRE b/REQUIRE index 9c212277..7d0858f8 100644 --- a/REQUIRE +++ b/REQUIRE @@ -10,6 +10,8 @@ StaticArrays OffsetArrays IdentityRanges ColorTypes 0.4 +MLDataPattern 0.1.2 +ComputationalResources 0.0.2 ShowItLikeYouBuildIt FileIO Compat 0.17 diff --git a/src/Augmentor.jl b/src/Augmentor.jl index 76c6dd89..ea1a0e61 100644 --- a/src/Augmentor.jl +++ b/src/Augmentor.jl @@ -13,6 +13,8 @@ using Interpolations using StaticArrays using OffsetArrays using IdentityRanges +using MLDataPattern +using ComputationalResources using FileIO using ShowItLikeYouBuildIt using Compat @@ -59,6 +61,8 @@ export ElasticDistortion, augment, + augment!, + augmentbatch!, testpattern @@ -87,5 +91,6 @@ include("operations/distortion.jl") include("pipeline.jl") include("codegen.jl") include("augment.jl") +include("augmentbatch.jl") end # module diff --git a/src/augment.jl b/src/augment.jl index 66ecffc3..ae96a0a7 100644 --- a/src/augment.jl +++ b/src/augment.jl @@ -39,12 +39,27 @@ function augment(op::Union{AbstractPipeline,Operation}) augment(use_testpattern(), op) end -# -------------------------------------------------------------------- - @inline function _augment(img, pipeline::AbstractPipeline) _augment(img, operations(pipeline)...) end @generated function _augment(img, pipeline::Vararg{Operation}) - Expr(:block, Expr(:meta, :inline), augment_impl(:img, pipeline)) + Expr(:block, Expr(:meta, :inline), augment_impl(:img, pipeline, false)) +end + +# -------------------------------------------------------------------- + +function augment!(out, img, pipeline::AbstractPipeline) + out_lazy = _augment_avoid_eager(img, pipeline) + copy!(match_idx(out, indices(out_lazy)), out_lazy) + out end + +@inline function _augment_avoid_eager(img, pipeline::AbstractPipeline) + _augment_avoid_eager(img, operations(pipeline)...) +end + +@generated function _augment_avoid_eager(img, pipeline::Vararg{Operation}) + Expr(:block, Expr(:meta, :inline), augment_impl(:img, pipeline, true)) +end + diff --git a/src/augmentbatch.jl b/src/augmentbatch.jl new file mode 100644 index 00000000..e6c28778 --- /dev/null +++ b/src/augmentbatch.jl @@ -0,0 +1,46 @@ +_berror() = throw(ArgumentError("Number of output images must be equal to the number of input images")) + +imagesvector(imgs::AbstractArray) = obsview(imgs) +@inline imagesvector(imgs::AbstractVector{<:AbstractArray}) = imgs + +# -------------------------------------------------------------------- + +function augmentbatch!( + outs::AbstractArray, + imgs::AbstractArray, + pipeline::AbstractPipeline) + augmentbatch!(CPU1(), outs, imgs, pipeline) +end + +function augmentbatch!( + r::AbstractResource, + outs::AbstractArray, + imgs::AbstractArray, + pipeline::AbstractPipeline) + augmentbatch!(r, imagesvector(outs), imagesvector(imgs), pipeline) + outs +end + +function augmentbatch!( + ::CPU1, + outs::AbstractVector{<:AbstractArray}, + imgs::AbstractVector{<:AbstractArray}, + pipeline::AbstractPipeline) + length(outs) == length(imgs) || _berror() + for i in 1:length(outs) + augment!(outs[i], imgs[i], pipeline) + end + outs +end + +function augmentbatch!( + ::CPUThreads, + outs::AbstractVector{<:AbstractArray}, + imgs::AbstractVector{<:AbstractArray}, + pipeline::AbstractPipeline) + length(outs) == length(imgs) || _berror() + Threads.@threads for i in 1:length(outs) + augment!(outs[i], imgs[i], pipeline) + end + outs +end diff --git a/src/codegen.jl b/src/codegen.jl index 41ebf3a7..558d09bb 100644 --- a/src/codegen.jl +++ b/src/codegen.jl @@ -1,9 +1,11 @@ """ - seek_connected(f, N::Int, head::DataType, tail::Tuple) -> (N, seq) + seek_connected(f, N::Int, head::DataType, tail::Tuple) -> (N, remainder) Recursively scan a tuple of `DataType` (split into its `head` and -`tail`) to compute the uninterrupted sequence `seq` of adjacent -operations (and its length `N`) where the predicate `f` is true. +`tail`) to compute the length `N` of the uninterrupted sequence +of adjacent operations where the predicate `f` is true. +Additionally the `remainder` of the tuple (without that sequence) +is also returned. """ @inline function seek_connected(f, N::Int, head::Type{<:Operation}, tail::Tuple) if f(head) @@ -32,51 +34,56 @@ end # -------------------------------------------------------------------- -function augment_impl(var_offset::Int, op_offset::Int, pipeline::Tuple) - augment_impl(var_offset, op_offset, first(pipeline), Base.tail(pipeline)) +function augment_impl(var_offset::Int, op_offset::Int, pipeline::Tuple, args...) + augment_impl(var_offset, op_offset, first(pipeline), Base.tail(pipeline), args...) end -function augment_impl(var_offset::Int, op_offset::Int, pipeline::Tuple{}) +function augment_impl(var_offset::Int, op_offset::Int, pipeline::Tuple{}, args...) :($(Symbol(:img_, var_offset))) end -function augment_impl(var_offset::Int, op_offset::Int, head, tail::NTuple{N,DataType}) where N +function augment_impl(var_offset::Int, op_offset::Int, head::DataType, tail::NTuple{N,DataType}, avoid_eager = false) where N var_in = Symbol(:img_, var_offset) var_out = Symbol(:img_, var_offset+1) if supports_lazy(head, tail) - num_affine, rest_affine = uses_affinemap(head, tail) ? seek_connected(uses_affinemap, 0, head, tail) : (0, nothing) + # If reached there are at least two adjacent lazy operations + num_affine, after_affine = uses_affinemap(head, tail) ? seek_connected(uses_affinemap, 0, head, tail) : (0, nothing) num_special, _ = seek_connected(x->(supports_permute(x)||supports_view(x)||supports_stepview(x)), 0, head, tail) - num_lazy, rest_lazy = seek_connected(supports_lazy, 0, head, tail) + num_lazy, after_lazy = seek_connected(supports_lazy, 0, head, tail) if num_special >= num_affine quote $var_out = unroll_applylazy($(Expr(:tuple, (:(pipeline[$i]) for i in op_offset:op_offset+num_lazy-1)...)), $var_in) - $(augment_impl(var_offset+1, op_offset+num_lazy, rest_lazy)) + $(augment_impl(var_offset+1, op_offset+num_lazy, after_lazy, avoid_eager)) end else quote $var_out = unroll_applyaffine($(Expr(:tuple, (:(pipeline[$i]) for i in op_offset:op_offset+num_affine-1)...)), $var_in) - $(augment_impl(var_offset+1, op_offset+num_affine, rest_affine)) + $(augment_impl(var_offset+1, op_offset+num_affine, after_affine, avoid_eager)) end end else - if length(tail) == 0 || supports_eager(head) || !supports_lazy(head) + # At most "head" is lazy (i.e. tail[1] is surely not). + # Unless "avoid_eager==true" we prefer using "applyeager" in + # this case because there is no neighbour synergy and we + # assume "applyeager" is more efficient. + if !supports_lazy(head) || (!avoid_eager && (length(tail) == 0 || supports_eager(head))) quote $var_out = applyeager(pipeline[$op_offset], $var_in) - $(augment_impl(var_offset+1, op_offset+1, tail)) + $(augment_impl(var_offset+1, op_offset+1, tail, avoid_eager)) end - else # use lazy because there is no special eager implementation + else quote - $var_out = unroll_applylazy(pipeline[$op_offset], $var_in) - $(augment_impl(var_offset+1, op_offset+1, tail)) + $var_out = applylazy(pipeline[$op_offset], $var_in) + $(augment_impl(var_offset+1, op_offset+1, tail, avoid_eager)) end end end end -function augment_impl(varname, pipeline::NTuple{N,DataType}) where N +function augment_impl(varname::Symbol, pipeline::NTuple{N,DataType}, args...) where N quote img_1 = $varname - $(augment_impl(1, 1, pipeline)) + $(augment_impl(1, 1, pipeline, args...)) end end @@ -102,8 +109,8 @@ end # end # end -function augment_impl(pipeline::AbstractPipeline) - augment_impl(:input_image, map(typeof, operations(pipeline))) +function augment_impl(pipeline::AbstractPipeline; avoid_eager = false) + augment_impl(:input_image, map(typeof, operations(pipeline)), avoid_eager) end -augment_impl(op::Operation) = augment_impl((op,)) +augment_impl(op::Operation; kw...) = augment_impl((op,); kw...) diff --git a/src/operations/cache.jl b/src/operations/cache.jl index 7fde1f3a..941b4f5c 100644 --- a/src/operations/cache.jl +++ b/src/operations/cache.jl @@ -86,10 +86,6 @@ CacheImage(buffer::AbstractArray) = CacheImageInto(buffer) @inline supports_lazy(::Type{<:CacheImageInto}) = true -@inline match_idx(buffer::AbstractArray, inds::Tuple) = buffer -@inline match_idx(buffer::Array, inds::NTuple{N,UnitRange}) where {N} = - OffsetArray(buffer, inds) - applyeager(op::CacheImageInto, img) = applylazy(op, img) function applylazy(op::CacheImageInto, img) diff --git a/src/utils.jl b/src/utils.jl index 0ce13cdc..91392603 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -27,6 +27,12 @@ end # -------------------------------------------------------------------- +@inline match_idx(buffer::AbstractArray, inds::Tuple) = buffer +@inline match_idx(buffer::Union{Array,SubArray}, inds::NTuple{N,UnitRange}) where {N} = + OffsetArray(buffer, inds) + +# -------------------------------------------------------------------- + function indirect_indices(::Tuple{}, ::Tuple{}) throw(MethodError(indirect_indices, ((),()))) end From b77ecf19b69a049c274253b150938e8bb4d17de0 Mon Sep 17 00:00:00 2001 From: Christof Stocker Date: Wed, 5 Jul 2017 21:50:03 +0200 Subject: [PATCH 2/5] add tests for mutating augment --- src/augment.jl | 2 ++ test/runtests.jl | 2 +- test/tst_augment.jl | 40 +++++++++++++++++++++++++--------------- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/augment.jl b/src/augment.jl index ae96a0a7..53763d64 100644 --- a/src/augment.jl +++ b/src/augment.jl @@ -49,6 +49,8 @@ end # -------------------------------------------------------------------- +augment!(out, img, op::Operation) = augment!(out, img, (op,)) + function augment!(out, img, pipeline::AbstractPipeline) out_lazy = _augment_avoid_eager(img, pipeline) copy!(match_idx(out, indices(out_lazy)), out_lazy) diff --git a/test/runtests.jl b/test/runtests.jl index b5974162..53cf7823 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,4 +1,4 @@ -using ImageCore, ImageFiltering, ImageTransformations, CoordinateTransformations, Interpolations, OffsetArrays, StaticArrays, ColorTypes, FixedPointNumbers, TestImages, IdentityRanges, MappedArrays, Base.Test +using ImageCore, ImageFiltering, ImageTransformations, CoordinateTransformations, Interpolations, OffsetArrays, StaticArrays, ColorTypes, FixedPointNumbers, TestImages, IdentityRanges, MappedArrays, ComputationalResources, MLDataPattern, Base.Test using ImageInTerminal # check for ambiguities diff --git a/test/tst_augment.jl b/test/tst_augment.jl index a245dc1c..fa7dd95b 100644 --- a/test/tst_augment.jl +++ b/test/tst_augment.jl @@ -64,21 +64,19 @@ end # -------------------------------------------------------------------- @testset "single op" begin - img = @inferred Augmentor._augment(rect, Augmentor.ImmutablePipeline(Rotate90())) - @test typeof(img) <: Array - @test typeof(img) == typeof(@inferred(augment(rect, (Rotate90(),)))) - @test eltype(img) <: eltype(rect) - @test img == rotl90(rect) - img = @inferred Augmentor._augment(rect, (Rotate90(),)) - @test typeof(img) <: Array - @test typeof(img) == typeof(@inferred(augment(rect, (Rotate90(),)))) - @test eltype(img) <: eltype(rect) - @test img == rotl90(rect) - img = @inferred Augmentor._augment(square, (Rotate(90),)) - @test typeof(img) <: Array - @test typeof(img) == typeof(@inferred(augment(square, (Rotate(90),)))) - @test eltype(img) <: eltype(square) - @test img == rotl90(square) + @test_throws BoundsError augment!(rand(2,2), rect, Rotate90()) + for pl in (Augmentor.ImmutablePipeline(Rotate90()), (Rotate90(),)) + img = @inferred Augmentor._augment(rect, pl) + @test typeof(img) <: Array + @test typeof(img) == typeof(@inferred(augment(rect, Rotate90()))) + @test eltype(img) <: eltype(rect) + @test img == rotl90(rect) + out = similar(img) + @test @inferred(augment!(out, rect, pl)) == img + out = similar(img) + @test @inferred(augment!(out, rect, Rotate90())) == img + @test_throws BoundsError augment!(rand(2,2), rect, pl) + end end ops = Augmentor.ImmutablePipeline(Rotate(90),Rotate(-90)) # forces affine @@ -162,6 +160,10 @@ ops = (Rotate180(),Crop(5:200,200:500),Rotate90(1),Crop(1:250, 1:150)) @test typeof(img) <: Array @test eltype(img) <: eltype(camera) @test_reference "rot_crop_either_crop" img + out = similar(img) + @test @inferred(augment!(out, camera, ops)) === out + @test_reference "rot_crop_either_crop" out + @test @allocated(augment!(out, camera, ops)) < @allocated(augment(camera, ops)) end ops = Augmentor.ImmutablePipeline(Rotate180(),Crop(5:200,200:500),Rotate90(),Crop(50:300, 50:195)) @@ -175,6 +177,10 @@ ops = Augmentor.ImmutablePipeline(Rotate180(),Crop(5:200,200:500),Rotate90(),Cro @test typeof(img) <: Array @test eltype(img) <: eltype(camera) @test_reference "rot_crop_rot_crop" img + out = similar(img) + @test @inferred(augment!(out, camera, ops)) === out + @test_reference "rot_crop_rot_crop" out + @test @allocated(augment!(out, camera, ops)) < @allocated(augment(camera, ops)) end ops = (Rotate180(),Crop(5:200,200:500),Rotate90(),Crop(50:300, 50:195),Resize(25,15)) @@ -191,6 +197,10 @@ ops = (Rotate180(),Crop(5:200,200:500),Rotate90(),Crop(50:300, 50:195),Resize(25 @test typeof(img) <: Array @test eltype(img) <: eltype(camera) @test_reference "rot_crop_rot_crop_resize" img + out = similar(img) + @test @inferred(augment!(out, camera, ops)) === out + @test_reference "rot_crop_rot_crop_resize" out + @test @allocated(augment!(out, camera, ops)) < @allocated(augment(camera, ops)) end ops = (Rotate(45),CropNative(1:512,1:512)) From aff3de02fc5f5c1c4b500da6c61d9903a0e398c8 Mon Sep 17 00:00:00 2001 From: Christof Stocker Date: Thu, 6 Jul 2017 20:15:46 +0200 Subject: [PATCH 3/5] make rand threadsafe --- src/augmentbatch.jl | 8 ++++---- src/distortionfields.jl | 4 ++-- src/operations/crop.jl | 4 ++-- src/operations/either.jl | 4 ++-- src/operations/rotation.jl | 2 +- src/operations/scale.jl | 2 +- src/operations/shear.jl | 4 ++-- src/operations/zoom.jl | 2 +- src/utils.jl | 20 ++++++++++++++++++++ 9 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/augmentbatch.jl b/src/augmentbatch.jl index e6c28778..d4555eec 100644 --- a/src/augmentbatch.jl +++ b/src/augmentbatch.jl @@ -8,7 +8,7 @@ imagesvector(imgs::AbstractArray) = obsview(imgs) function augmentbatch!( outs::AbstractArray, imgs::AbstractArray, - pipeline::AbstractPipeline) + pipeline) augmentbatch!(CPU1(), outs, imgs, pipeline) end @@ -16,7 +16,7 @@ function augmentbatch!( r::AbstractResource, outs::AbstractArray, imgs::AbstractArray, - pipeline::AbstractPipeline) + pipeline) augmentbatch!(r, imagesvector(outs), imagesvector(imgs), pipeline) outs end @@ -25,7 +25,7 @@ function augmentbatch!( ::CPU1, outs::AbstractVector{<:AbstractArray}, imgs::AbstractVector{<:AbstractArray}, - pipeline::AbstractPipeline) + pipeline) length(outs) == length(imgs) || _berror() for i in 1:length(outs) augment!(outs[i], imgs[i], pipeline) @@ -37,7 +37,7 @@ function augmentbatch!( ::CPUThreads, outs::AbstractVector{<:AbstractArray}, imgs::AbstractVector{<:AbstractArray}, - pipeline::AbstractPipeline) + pipeline) length(outs) == length(imgs) || _berror() Threads.@threads for i in 1:length(outs) augment!(outs[i], imgs[i], pipeline) diff --git a/src/distortionfields.jl b/src/distortionfields.jl index 109935ce..f6fbe240 100644 --- a/src/distortionfields.jl +++ b/src/distortionfields.jl @@ -18,10 +18,10 @@ end function uniform_field(gridheight::Int, gridwidth::Int, scale, border, normalize) A = if !border @assert gridwidth > 2 && gridheight > 2 - _2dborder!(rand(2, gridheight, gridwidth), .5) + _2dborder!(safe_rand(2, gridheight, gridwidth), .5) else @assert gridwidth > 0 && gridheight > 0 - rand(2, gridheight, gridwidth) + safe_rand(2, gridheight, gridwidth) end::Array{Float64,3} broadcast!(*, A, A, 2.) broadcast!(-, A, A, 1.) diff --git a/src/operations/crop.jl b/src/operations/crop.jl index 70cc55e8..6b55331e 100644 --- a/src/operations/crop.jl +++ b/src/operations/crop.jl @@ -480,12 +480,12 @@ function rcropratio_indices(op::RCropRatio, img::AbstractMatrix) elseif nw < w x_max = w - nw + 1 @assert x_max > 0 - x = rand(1:x_max) + x = safe_rand(1:x_max) 1:h, x:(x+nw-1) elseif nh < h y_max = h - nh + 1 @assert y_max > 0 - y = rand(1:y_max) + y = safe_rand(1:y_max) y:(y+nh-1), 1:w else error("unreachable code reached") diff --git a/src/operations/either.jl b/src/operations/either.jl index 7d2d3d36..e97a06b6 100644 --- a/src/operations/either.jl +++ b/src/operations/either.jl @@ -158,7 +158,7 @@ end function toaffinemap(op::Either, img) supports_affine(typeof(op)) || throw(MethodError(toaffinemap, (op, img))) - p = rand() + p = safe_rand() for (i, p_i) in enumerate(op.cum_chances) if p <= p_i return toaffinemap_common(op.operations[i], img) @@ -179,7 +179,7 @@ for KIND in (:eager, :permute, :view, :stepview, :affine, :affineview) APP = startswith(String(KIND),"affine") ? Symbol(FUN, :_common) : FUN @eval function ($FUN)(op::Either, img) ($SUP)(typeof(op)) || throw(MethodError($FUN, (op, img))) - p = rand() + p = safe_rand() for (i, p_i) in enumerate(op.cum_chances) if p <= p_i return ($APP)(op.operations[i], img) diff --git a/src/operations/rotation.jl b/src/operations/rotation.jl index a1528662..a950c276 100644 --- a/src/operations/rotation.jl +++ b/src/operations/rotation.jl @@ -321,7 +321,7 @@ Rotate(degree::Real) = Rotate(degree:degree) @inline supports_eager(::Type{<:Rotate}) = false function toaffinemap(op::Rotate, img::AbstractMatrix) - recenter(RotMatrix(deg2rad(Float64(rand(op.degree)))), center(img)) + recenter(RotMatrix(deg2rad(Float64(safe_rand(op.degree)))), center(img)) end function Base.show(io::IO, op::Rotate) diff --git a/src/operations/scale.jl b/src/operations/scale.jl index 54ea0867..66bdc103 100644 --- a/src/operations/scale.jl +++ b/src/operations/scale.jl @@ -86,7 +86,7 @@ end @inline supports_eager(::Type{<:Scale}) = false function toaffinemap(op::Scale{2}, img::AbstractMatrix) - idx = rand(1:length(op.factors[1])) + idx = safe_rand(1:length(op.factors[1])) @inbounds tfm = recenter(@SMatrix([Float64(op.factors[1][idx]) 0.; 0. Float64(op.factors[2][idx])]), center(img)) tfm end diff --git a/src/operations/shear.jl b/src/operations/shear.jl index 0dd52516..7080017a 100644 --- a/src/operations/shear.jl +++ b/src/operations/shear.jl @@ -63,7 +63,7 @@ ShearX(degree::Real) = ShearX(degree:degree) @inline supports_eager(::Type{<:ShearX}) = false function toaffinemap(op::ShearX, img::AbstractMatrix) - angle = deg2rad(Float64(rand(op.degree))) + angle = deg2rad(Float64(safe_rand(op.degree))) recenter(@SMatrix([1. 0.; tan(-angle) 1.]), center(img)) end @@ -147,7 +147,7 @@ ShearY(degree::Real) = ShearY(degree:degree) @inline supports_eager(::Type{<:ShearY}) = false function toaffinemap(op::ShearY, img::AbstractMatrix) - angle = deg2rad(Float64(rand(op.degree))) + angle = deg2rad(Float64(safe_rand(op.degree))) recenter(@SMatrix([1. tan(-angle); 0. 1.]), center(img)) end diff --git a/src/operations/zoom.jl b/src/operations/zoom.jl index f20d44b8..f4977334 100644 --- a/src/operations/zoom.jl +++ b/src/operations/zoom.jl @@ -91,7 +91,7 @@ end @inline supports_eager(::Type{<:Zoom}) = false function toaffinemap(op::Zoom{2}, img::AbstractMatrix) - idx = rand(1:length(op.factors[1])) + idx = safe_rand(1:length(op.factors[1])) @inbounds tfm = recenter(@SMatrix([Float64(op.factors[1][idx]) 0.; 0. Float64(op.factors[2][idx])]), center(img)) tfm end diff --git a/src/utils.jl b/src/utils.jl index 91392603..8ff74aca 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -15,6 +15,26 @@ function use_testpattern() testpattern() end +# -------------------------------------------------------------------- +# rand() is not threadsafe (https://discourse.julialang.org/t/4683) + +# Because we only require random numbers to sample parameters +# and not the actual expensive computation, this seems like a better +# approach than using separate RNG per thread. +const rand_mutex = Ref{Threads.Mutex}() + +function __init__() + rand_mutex[] = Threads.Mutex() +end + +# constant overhead of about 80 ns compared to unsafe rand +function safe_rand(args...) + lock(rand_mutex[]) + result = rand(args...) + unlock(rand_mutex[]) + result +end + # -------------------------------------------------------------------- @inline _plain_array(A::OffsetArray) = parent(A) From be3d2fee7ba98d7985d1d020daa10e5e7c6c0148 Mon Sep 17 00:00:00 2001 From: Christof Stocker Date: Sat, 8 Jul 2017 01:00:56 +0200 Subject: [PATCH 4/5] improve tests and clarity --- src/Augmentor.jl | 4 + src/operations/rotation.jl | 8 +- src/operations/scale.jl | 4 +- src/operations/zoom.jl | 6 +- src/utils.jl | 18 ++-- test/operations/tst_either.jl | 154 +++++++++++++++++++++------------- test/runtests.jl | 11 +++ test/tst_augment.jl | 1 + test/tst_augmentbatch.jl | 66 +++++++++++++++ test/tst_operations.jl | 14 ++++ test/tst_utils.jl | 130 +++++++++++++++++++++++----- 11 files changed, 316 insertions(+), 100 deletions(-) create mode 100644 test/tst_augmentbatch.jl diff --git a/src/Augmentor.jl b/src/Augmentor.jl index ea1a0e61..c4d6bcb3 100644 --- a/src/Augmentor.jl +++ b/src/Augmentor.jl @@ -93,4 +93,8 @@ include("codegen.jl") include("augment.jl") include("augmentbatch.jl") +function __init__() + rand_mutex[] = Threads.Mutex() +end + end # module diff --git a/src/operations/rotation.jl b/src/operations/rotation.jl index a950c276..c7e4144b 100644 --- a/src/operations/rotation.jl +++ b/src/operations/rotation.jl @@ -69,13 +69,13 @@ function applypermute(::Rotate90, img::AbstractMatrix{T}) where T view(perm_img, reverse(idx[2]), idx[1]) end -function applypermute(::Rotate90, sub::SubArray{T,2,IT}) where {T,IT<:PermutedDimsArray{T,2,(2,1)}} +function applypermute(::Rotate90, sub::SubArray{T,2,IT,<:NTuple{2,Range}}) where {T,IT<:PermutedDimsArray{T,2,(2,1)}} idx = map(StepRange, sub.indexes) img = parent(parent(sub)) view(img, reverse(idx[2]), idx[1]) end -function applypermute(::Rotate90, sub::SubArray{T,2}) where T +function applypermute(::Rotate90, sub::SubArray{T,2,IT,<:NTuple{2,Range}}) where {T,IT} idx = map(StepRange, sub.indexes) img = parent(sub) perm_img = PermutedDimsArray{T,2,(2,1),(2,1),typeof(img)}(img) @@ -223,13 +223,13 @@ function applypermute(::Rotate270, img::AbstractMatrix{T}) where T view(perm_img, idx[2], reverse(idx[1])) end -function applypermute(::Rotate270, sub::SubArray{T,2,IT}) where {T,IT<:PermutedDimsArray{T,2,(2,1)}} +function applypermute(::Rotate270, sub::SubArray{T,2,IT,<:NTuple{2,Range}}) where {T,IT<:PermutedDimsArray{T,2,(2,1)}} idx = map(StepRange, sub.indexes) img = parent(parent(sub)) view(img, idx[2], reverse(idx[1])) end -function applypermute(::Rotate270, sub::SubArray{T,2}) where T +function applypermute(::Rotate270, sub::SubArray{T,2,IT,<:NTuple{2,Range}}) where {T,IT} idx = map(StepRange, sub.indexes) img = parent(sub) perm_img = PermutedDimsArray{T,2,(2,1),(2,1),typeof(img)}(img) diff --git a/src/operations/scale.jl b/src/operations/scale.jl index 66bdc103..c855e09d 100644 --- a/src/operations/scale.jl +++ b/src/operations/scale.jl @@ -74,13 +74,13 @@ Scale() = throw(MethodError(Scale, ())) Scale(::Tuple{}) = throw(MethodError(Scale, ((),))) Scale(factors...) = Scale(factors) Scale(factor::Union{AbstractVector,Real}) = Scale((factor, factor)) -Scale(factors::NTuple{N,Any}) where {N} = Scale(map(_vectorize, factors)) +Scale(factors::NTuple{N,Any}) where {N} = Scale(map(vectorize, factors)) Scale(factors::NTuple{N,Range}) where {N} = Scale{N}(promote(factors...)) function Scale(factors::NTuple{N,AbstractVector}) where N Scale{N}(map(Vector{Float64}, factors)) end function (::Type{Scale{N}})(factors::NTuple{N,Any}) where N - Scale(map(_vectorize, factors)) + Scale(map(vectorize, factors)) end @inline supports_eager(::Type{<:Scale}) = false diff --git a/src/operations/zoom.jl b/src/operations/zoom.jl index f4977334..7fd7f422 100644 --- a/src/operations/zoom.jl +++ b/src/operations/zoom.jl @@ -78,13 +78,13 @@ Zoom() = throw(MethodError(Zoom, ())) Zoom(::Tuple{}) = throw(MethodError(Zoom, ((),))) Zoom(factors...) = Zoom(factors) Zoom(factor::Union{AbstractVector,Real}) = Zoom((factor, factor)) -Zoom(factors::NTuple{N,Any}) where {N} = Zoom(map(_vectorize, factors)) +Zoom(factors::NTuple{N,Any}) where {N} = Zoom(map(vectorize, factors)) Zoom(factors::NTuple{N,Range}) where {N} = Zoom{N}(promote(factors...)) function Zoom(factors::NTuple{N,AbstractVector}) where N Zoom{N}(map(Vector{Float64}, factors)) end function (::Type{Zoom{N}})(factors::NTuple{N,Any}) where N - Zoom(map(_vectorize, factors)) + Zoom(map(vectorize, factors)) end @inline supports_affineview(::Type{<:Zoom}) = true @@ -120,7 +120,7 @@ end function Base.show(io::IO, op::Zoom{N}) where N if get(io, :compact, false) - str = join(map(t->join(_round(t,2),"×"), collect(zip(op.factors...))), ", ") + str = join(map(t->join(round_if_float(t,2),"×"), collect(zip(op.factors...))), ", ") if length(op.factors[1]) == 1 print(io, "Zoom by $(str)") else diff --git a/src/utils.jl b/src/utils.jl index 8ff74aca..36342165 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -23,10 +23,6 @@ end # approach than using separate RNG per thread. const rand_mutex = Ref{Threads.Mutex}() -function __init__() - rand_mutex[] = Threads.Mutex() -end - # constant overhead of about 80 ns compared to unsafe rand function safe_rand(args...) lock(rand_mutex[]) @@ -83,7 +79,7 @@ function indirect_view(A::AbstractArray, I::Tuple) view(A, indirect_indices(indices(A), I)...) end -function indirect_view(A::SubArray, I::Tuple) +function indirect_view(A::SubArray{T,N,TA,<:NTuple{N,Range}}, I::Tuple) where {T,N,TA} view(parent(A), indirect_indices(A.indexes, I)...) end @@ -111,18 +107,18 @@ function direct_view(A::AbstractArray{T,N}, I::NTuple{N,Range}) where {T,N} view(A, direct_indices(indices(A), I)...) end -function direct_view(A::SubArray{T,N}, I::NTuple{N,Range}) where {T,N} +function direct_view(A::SubArray{T,N,TA,<:NTuple{N,Range}}, I::NTuple{N,Range}) where {T,N,TA} view(A, direct_indices(A.indexes, I)...) end # -------------------------------------------------------------------- -@inline _vectorize(A::AbstractVector) = A -@inline _vectorize(A::Real) = A:A +@inline vectorize(A::AbstractVector) = A +@inline vectorize(A::Real) = A:A -@inline _round(num::Integer, d) = num -_round(num::AbstractFloat, d) = round(num,d) -_round(nums::Tuple, d) = map(num->_round(num,d), nums) +@inline round_if_float(num::Integer, d) = num +round_if_float(num::AbstractFloat, d) = round(num,d) +round_if_float(nums::Tuple, d) = map(num->round_if_float(num,d), nums) function unionrange(i1::AbstractUnitRange, i2::AbstractUnitRange) map(min, first(i1), first(i2)):map(max, last(i1), last(i2)) diff --git a/test/operations/tst_either.jl b/test/operations/tst_either.jl index 8ffc7beb..4869aef7 100644 --- a/test/operations/tst_either.jl +++ b/test/operations/tst_either.jl @@ -1,3 +1,7 @@ +# Things to test +# [x] Construction limited to ImageOperation +# [x] Construction also works for ops like ElasticDistortion + @test (Either <: Augmentor.AffineOperation) == false @test Either <: Augmentor.ImageOperation @@ -20,38 +24,48 @@ @test op.chances === @SVector([0.5,0.5]) @test op.cum_chances === @SVector([0.5,1.0]) end - let op = @inferred Either(1 => Rotate90(), 2 => Rotate180(), 1 => Rotate270()) - @test op.operations === (Rotate90(),Rotate180(),Rotate270()) + let op = @inferred Either(1 => Rotate90(), 2 => Rotate180(), 1 => Crop(1:10,1:10)) + @test op.operations === (Rotate90(),Rotate180(),Crop(1:10,1:10)) + @test op.chances === @SVector([0.25,0.5,0.25]) + @test op.cum_chances === @SVector([0.25,0.75,1.]) + end + let op = @inferred Either(1 => Rotate90(), 2 => Rotate180(), 1 => Crop(1:10,1:10)) + @test op.operations === (Rotate90(),Rotate180(),Crop(1:10,1:10)) @test op.chances === @SVector([0.25,0.5,0.25]) @test op.cum_chances === @SVector([0.25,0.75,1.]) end - let op = @inferred Either((Rotate90(),Rotate180(),Rotate270()), (0.1,0.7,0.2)) - @test op.operations === (Rotate90(),Rotate180(),Rotate270()) + let op = @inferred Either((Rotate90(),Rotate180(),Crop(1:10,1:10)), (0.1,0.7,0.2)) + @test op.operations === (Rotate90(),Rotate180(),Crop(1:10,1:10)) @test op.chances === @SVector([0.1,0.7,0.2]) @test op.cum_chances ≈ @SVector([0.1,0.8,1.]) end - let op = @inferred Either((Rotate90(),Rotate180(),Rotate270())) - @test op.operations === (Rotate90(),Rotate180(),Rotate270()) + let op = @inferred Either((Rotate90(),Rotate180(),Crop(1:10,1:10))) + @test op.operations === (Rotate90(),Rotate180(),Crop(1:10,1:10)) @test op.chances ≈ @SVector([1/3,1/3,1/3]) @test op.cum_chances ≈ @SVector([1/3,2/3,3/3]) end - let op = Either(Rotate90(), Rotate180(), Rotate270(), chances = [1,7,2]) - @test op.operations === (Rotate90(),Rotate180(),Rotate270()) + let op = Either(Rotate90(), Rotate180(), Crop(1:10,1:10), chances = [1,7,2]) + @test op.operations === (Rotate90(),Rotate180(),Crop(1:10,1:10)) @test op.chances === @SVector([0.1,0.7,0.2]) @test op.cum_chances ≈ @SVector([0.1,0.8,1.]) end - let op = @inferred Either(Rotate90(),Rotate180(),Rotate270()) - @test op.operations === (Rotate90(),Rotate180(),Rotate270()) + let op = @inferred Either(Rotate90(),Rotate180(),Crop(1:10,1:10)) + @test op.operations === (Rotate90(),Rotate180(),Crop(1:10,1:10)) + @test op.chances ≈ @SVector([1/3,1/3,1/3]) + @test op.cum_chances ≈ @SVector([1/3,2/3,3/3]) + end + let op = @inferred(Rotate90()*Rotate180()*Crop(1:10,1:10)) + @test op.operations === (Rotate90(),Rotate180(),Crop(1:10,1:10)) @test op.chances ≈ @SVector([1/3,1/3,1/3]) @test op.cum_chances ≈ @SVector([1/3,2/3,3/3]) end - let op = @inferred(Rotate90()*Rotate180()*Rotate270()) - @test op.operations === (Rotate90(),Rotate180(),Rotate270()) + let op = @inferred(Rotate90()*Rotate180()*ElasticDistortion(5)) + @test op.operations === (Rotate90(),Rotate180(),ElasticDistortion(5)) @test op.chances ≈ @SVector([1/3,1/3,1/3]) @test op.cum_chances ≈ @SVector([1/3,2/3,3/3]) end - let op = @inferred((1=>Rotate90())*(2=>Rotate180())*(1=>Rotate270())) - @test op.operations === (Rotate90(),Rotate180(),Rotate270()) + let op = @inferred((1=>Rotate90())*(2=>Rotate180())*(1=>Crop(1:10,1:10))) + @test op.operations === (Rotate90(),Rotate180(),Crop(1:10,1:10)) @test op.chances ≈ @SVector([1/4,2/4,1/4]) @test op.cum_chances ≈ @SVector([1/4,3/4,4/4]) end @@ -87,53 +101,75 @@ end @testset "eager" begin @test_throws MethodError Augmentor.applyeager(Either(Rotate90(),NoOp()), nothing) for img in (rect, OffsetArray(rect, -2, -1), view(rect, IdentityRange(1:2), IdentityRange(1:3))) - op = @inferred Either((Rotate90(),Rotate270()), (1,0)) - @test Augmentor.supports_eager(op) === true - @test @inferred(Augmentor.applyeager(op, img)) == rotl90(rect) - @test typeof(Augmentor.applyeager(op, img)) <: Array - op = @inferred Either((Rotate90(),Rotate270(),Crop(1:2,2:3)), (0,1,0)) - @test Augmentor.supports_eager(op) === true - @test @inferred(Augmentor.applyeager(op, img)) == rotr90(rect) - @test typeof(Augmentor.applyeager(op, img)) <: Array - op = @inferred Either((Rotate90(),Rotate270(),NoOp()), (0,0,1)) - @test Augmentor.supports_eager(op) === true - if typeof(img) <: SubArray - @test @inferred(Augmentor.applyeager(op, img)) == rect + let op = @inferred Either((Rotate90(),ElasticDistortion(5)), (1,0)) + @test_throws MethodError Augmentor.applylazy(op, img) + @test Augmentor.supports_eager(op) === true + @test Augmentor.supports_affine(op) === false + @test Augmentor.supports_lazy(op) === false + @test Augmentor.supports_affineview(op) === false + @test Augmentor.supports_view(op) === false + @test Augmentor.supports_stepview(op) === false + @test Augmentor.supports_permute(op) === false + @test @inferred(Augmentor.applyeager(op, img)) == rotl90(rect) + @test typeof(Augmentor.applyeager(op, img)) <: Array + end + let op = @inferred Either((Rotate90(),Rotate270()), (1,0)) + @test Augmentor.supports_eager(op) === true + @test @inferred(Augmentor.applyeager(op, img)) == rotl90(rect) + @test typeof(Augmentor.applyeager(op, img)) <: Array + end + let op = @inferred Either((Rotate90(),Rotate270(),Crop(1:2,2:3)), (0,1,0)) + @test Augmentor.supports_eager(op) === true + @test @inferred(Augmentor.applyeager(op, img)) == rotr90(rect) + @test typeof(Augmentor.applyeager(op, img)) <: Array + end + let op = @inferred Either((Rotate90(),Rotate270(),NoOp()), (0,0,1)) + @test Augmentor.supports_eager(op) === true + if typeof(img) <: SubArray + @test @inferred(Augmentor.applyeager(op, img)) == rect + @test typeof(Augmentor.applyeager(op, img)) <: Array + else + @test @inferred(Augmentor.applyeager(op, img)) === rect + end + end + let op = @inferred Either((Rotate90(),Rotate270(),Crop(1:2,2:3)), (0,0,1)) + @test Augmentor.supports_eager(op) === true + @test @inferred(Augmentor.applyeager(op, img)) == rect[1:2,2:3] + @test_throws MethodError Augmentor.applyaffine(op, rect) + @test_throws MethodError Augmentor.applyview(op, rect) + @test_throws MethodError Augmentor.applystepview(op, rect) + @test_throws MethodError Augmentor.applypermute(op, rect) + end + let op = @inferred Either((Rotate90(),Zoom(.8)), (1,0)) + @test Augmentor.supports_eager(op) === true + @test @inferred(Augmentor.applyeager(op, img)) == rotl90(rect) + @test typeof(Augmentor.applyeager(op, img)) <: Array + end + let op = @inferred Either((Rotate90(),FlipX()), (1,0)) + @test Augmentor.supports_eager(op) === true + @test @inferred(Augmentor.applyeager(op, img)) == rotl90(rect) + @test typeof(Augmentor.applyeager(op, img)) <: Array + end + let op = @inferred Either((Rotate90(),FlipX()), (0,1)) + @test Augmentor.supports_eager(op) === true + @test @inferred(Augmentor.applyeager(op, img)) == flipdim(rect,2) + @test typeof(Augmentor.applyeager(op, img)) <: Array + end + let op = @inferred Either((Rotate90(),FlipY()), (0,1)) + @test Augmentor.supports_eager(op) === true + @test @inferred(Augmentor.applyeager(op, img)) == flipdim(rect,1) + @test typeof(Augmentor.applyeager(op, img)) <: Array + end + let op = @inferred Either((Rotate90(),Resize(5,5)), (0,1)) + @test Augmentor.supports_eager(op) === true + @test @inferred(Augmentor.applyeager(op, img)) == imresize(rect,5,5) + @test typeof(Augmentor.applyeager(op, img)) <: Array + end + let op = @inferred Either((Crop(1:2,1:2),Resize(5,5)), (0,1)) + @test Augmentor.supports_eager(op) === true + @test @inferred(Augmentor.applyeager(op, img)) == imresize(rect,5,5) @test typeof(Augmentor.applyeager(op, img)) <: Array - else - @test @inferred(Augmentor.applyeager(op, img)) === rect end - op = @inferred Either((Rotate90(),Rotate270(),Crop(1:2,2:3)), (0,0,1)) - @test Augmentor.supports_eager(op) === true - @test @inferred(Augmentor.applyeager(op, img)) == rect[1:2,2:3] - @test_throws MethodError Augmentor.applyaffine(op, rect) - @test_throws MethodError Augmentor.applyview(op, rect) - @test_throws MethodError Augmentor.applystepview(op, rect) - @test_throws MethodError Augmentor.applypermute(op, rect) - op = @inferred Either((Rotate90(),Zoom(.8)), (1,0)) - @test Augmentor.supports_eager(op) === true - @test @inferred(Augmentor.applyeager(op, img)) == rotl90(rect) - @test typeof(Augmentor.applyeager(op, img)) <: Array - op = @inferred Either((Rotate90(),FlipX()), (1,0)) - @test Augmentor.supports_eager(op) === true - @test @inferred(Augmentor.applyeager(op, img)) == rotl90(rect) - @test typeof(Augmentor.applyeager(op, img)) <: Array - op = @inferred Either((Rotate90(),FlipX()), (0,1)) - @test Augmentor.supports_eager(op) === true - @test @inferred(Augmentor.applyeager(op, img)) == flipdim(rect,2) - @test typeof(Augmentor.applyeager(op, img)) <: Array - op = @inferred Either((Rotate90(),FlipY()), (0,1)) - @test Augmentor.supports_eager(op) === true - @test @inferred(Augmentor.applyeager(op, img)) == flipdim(rect,1) - @test typeof(Augmentor.applyeager(op, img)) <: Array - op = @inferred Either((Rotate90(),Resize(5,5)), (0,1)) - @test Augmentor.supports_eager(op) === true - @test @inferred(Augmentor.applyeager(op, img)) == imresize(rect,5,5) - @test typeof(Augmentor.applyeager(op, img)) <: Array - op = @inferred Either((Crop(1:2,1:2),Resize(5,5)), (0,1)) - @test Augmentor.supports_eager(op) === true - @test @inferred(Augmentor.applyeager(op, img)) == imresize(rect,5,5) - @test typeof(Augmentor.applyeager(op, img)) <: Array end end diff --git a/test/runtests.jl b/test/runtests.jl index 53cf7823..15713fba 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,3 +1,10 @@ +# Things that needs to be tested +# [x] Utility functions work properly and type stable +# [x] Individual operations do what they should +# [x] Individual operations are always type stable +# [x] Lazy Either works correctly and type stable +# [x] Operations accept AbstractArray as input (esp. Array and SubArray) + using ImageCore, ImageFiltering, ImageTransformations, CoordinateTransformations, Interpolations, OffsetArrays, StaticArrays, ColorTypes, FixedPointNumbers, TestImages, IdentityRanges, MappedArrays, ComputationalResources, MLDataPattern, Base.Test using ImageInTerminal @@ -26,6 +33,9 @@ function str_showconst(obj) end camera = testimage("cameraman") +cameras = similar(camera, size(camera)..., 2) +copy!(view(cameras,:,:,1), camera) +copy!(view(cameras,:,:,2), camera) square = Gray{N0f8}[0.1 0.2 0.3; 0.4 0.5 0.6; 0.7 0.6 0.9] square2 = rand(Gray{N0f8}, 4, 4) rect = Gray{N0f8}[0.1 0.2 0.3; 0.4 0.5 0.6] @@ -50,6 +60,7 @@ tests = [ "tst_operations.jl", "tst_pipeline.jl", "tst_augment.jl", + "tst_augmentbatch.jl", "tst_distortedview.jl", ] diff --git a/test/tst_augment.jl b/test/tst_augment.jl index fa7dd95b..a5c66d38 100644 --- a/test/tst_augment.jl +++ b/test/tst_augment.jl @@ -69,6 +69,7 @@ end img = @inferred Augmentor._augment(rect, pl) @test typeof(img) <: Array @test typeof(img) == typeof(@inferred(augment(rect, Rotate90()))) + @test typeof(img) == typeof(@inferred(augment(rect, (Rotate90(),)))) @test eltype(img) <: eltype(rect) @test img == rotl90(rect) out = similar(img) diff --git a/test/tst_augmentbatch.jl b/test/tst_augmentbatch.jl new file mode 100644 index 00000000..964094aa --- /dev/null +++ b/test/tst_augmentbatch.jl @@ -0,0 +1,66 @@ + +ops = (Rotate180(),Crop(5:200,200:500),Rotate90(1),Crop(1:250, 1:150)) +@testset "$(str_showcompact(ops))" begin + @test_throws ArgumentError augmentbatch!(similar(camera, 250, 150, 3), cameras, ops) + @test_throws DimensionMismatch augmentbatch!(similar(camera, 250, 140, 2), cameras, ops) + out = similar(camera, 250, 150, 2) + @test @inferred(augmentbatch!(out, cameras, ops)) === out + @test typeof(out) <: Array + @test eltype(out) <: eltype(camera) + @test_reference "rot_crop_either_crop" out[:,:,1] + @test_reference "rot_crop_either_crop" out[:,:,2] + out = similar(camera, 250, 150, 2) + @test @inferred(augmentbatch!(out, cameras, Augmentor.ImmutablePipeline(ops))) === out + @test typeof(out) <: Array + @test eltype(out) <: eltype(camera) + @test_reference "rot_crop_either_crop" out[:,:,1] + @test_reference "rot_crop_either_crop" out[:,:,2] + out = similar(camera, 250, 150, 2) + @test @inferred(augmentbatch!(out, collect.(obsview(cameras)), ops)) === out + @test typeof(out) <: Array + @test eltype(out) <: eltype(camera) + @test_reference "rot_crop_either_crop" out[:,:,1] + @test_reference "rot_crop_either_crop" out[:,:,2] + outs = [similar(camera, 250, 150), similar(camera, 250, 150)] + @test @inferred(augmentbatch!(outs, cameras, ops)) === outs + @test typeof(outs) <: Vector + @test eltype(outs) <: Array{eltype(camera)} + @test_reference "rot_crop_either_crop" outs[1] + @test_reference "rot_crop_either_crop" outs[2] +end + +@testset "Multithreaded: $(str_showcompact(ops))" begin + @test_throws ArgumentError augmentbatch!(CPUThreads(), similar(camera, 250, 150, 3), cameras, ops) + # doesn't work because exception is thrown in thread + # @test_throws DimensionMismatch augmentbatch!(CPUThreads(), similar(camera, 250, 140, 2), cameras, ops) + out = similar(camera, 250, 150, 2) + @test @inferred(augmentbatch!(CPUThreads(), out, cameras, ops)) === out + @test typeof(out) <: Array + @test eltype(out) <: eltype(camera) + @test_reference "rot_crop_either_crop" out[:,:,1] + @test_reference "rot_crop_either_crop" out[:,:,2] + out = similar(camera, 250, 150, 2) + @test @inferred(augmentbatch!(CPUThreads(), out, cameras, Augmentor.ImmutablePipeline(ops))) === out + @test typeof(out) <: Array + @test eltype(out) <: eltype(camera) + @test_reference "rot_crop_either_crop" out[:,:,1] + @test_reference "rot_crop_either_crop" out[:,:,2] + out = similar(camera, 250, 150, 2) + @test @inferred(augmentbatch!(CPUThreads(), out, collect.(obsview(cameras)), ops)) === out + @test typeof(out) <: Array + @test eltype(out) <: eltype(camera) + @test_reference "rot_crop_either_crop" out[:,:,1] + @test_reference "rot_crop_either_crop" out[:,:,2] + outs = [similar(camera, 250, 150), similar(camera, 250, 150)] + @test @inferred(augmentbatch!(CPUThreads(), outs, cameras, ops)) === outs + @test typeof(outs) <: Vector + @test eltype(outs) <: Array{eltype(camera)} + @test_reference "rot_crop_either_crop" outs[1] + @test_reference "rot_crop_either_crop" outs[2] +end + +ops = Rotate90() +@testset "$(str_showcompact(ops))" begin + out = similar(camera, 512, 512, 2) + @test @inferred(augmentbatch!(out, cameras, ops)) === out +end diff --git a/test/tst_operations.jl b/test/tst_operations.jl index 97599579..13212d66 100644 --- a/test/tst_operations.jl +++ b/test/tst_operations.jl @@ -36,6 +36,9 @@ ops = (FlipX(), FlipY()) v = @inferred Augmentor.unroll_applylazy(ops, rect) @test v === view(rect, 2:-1:1, 3:-1:1) @test v == rot180(rect) + v = @inferred Augmentor.unroll_applylazy(ops, view(cameras,:,:,1)) + @test v === view(cameras, 512:-1:1, 512:-1:1, 1) + @test v == rot180(camera) end ops = (Rotate90(), Rotate270()) @@ -48,6 +51,9 @@ ops = (Rotate90(), Rotate270()) @test wv == rect img = @inferred Augmentor.applyeager(Resize(4,4), wv) @test img == imresize(rect, (4,4)) + v = @inferred Augmentor.unroll_applylazy(ops, view(cameras,:,:,1)) + @test v === view(cameras, 1:1:512, 1:1:512, 1) + @test v == camera v = @inferred Augmentor.unroll_applylazy(ops, rect) @test v === view(rect, 1:1:2, 1:1:3) @test v == rect @@ -161,6 +167,14 @@ ops = (Rotate180(), CropNative(1:2,2:3)) @test v == view(rot180(square), 1:2, 2:3) end +ops = (Rotate180(), ElasticDistortion(5)) +@testset "$(str_showcompact(ops))" begin + @test_throws MethodError Augmentor.unroll_applyaffine(ops, square) + v = @inferred Augmentor.unroll_applylazy(ops, square) + @test v isa Augmentor.DistortedView + @test parent(v) === view(square, 3:-1:1, 3:-1:1) +end + ops = (Rotate180(), CropSize(2,2)) @testset "$(str_showcompact(ops))" begin wv = @inferred Augmentor.unroll_applyaffine(ops, square) diff --git a/test/tst_utils.jl b/test/tst_utils.jl index 7cc61ae6..c7d23a33 100644 --- a/test/tst_utils.jl +++ b/test/tst_utils.jl @@ -1,3 +1,14 @@ +# Things that need to be tested +# [x] testpattern and use_testpattern +# [x] safe_rand and the mutex +# [x] plain_array +# [x] match_idx +# [x] indirect_view +# [x] direct_view +# [x] vectorize +# [x] round_if_float +# [x] unionrange + @testset "testpattern" begin tp = testpattern() @test typeof(tp) <: Matrix @@ -7,31 +18,82 @@ @test tp == tp2 end +@testset "rand_mutex" begin + mutex = Augmentor.rand_mutex[] + typeof(mutex) <: Threads.Mutex + # check that its not a null pointer + @test reinterpret(Int, mutex.handle) > 0 +end + +@testset "safe_rand" begin + num = @inferred Augmentor.safe_rand() + @test 0 <= num <= 1 + @test typeof(num) <: Float64 + num = @inferred Augmentor.safe_rand(2) + @test all(0 .<= num .<= 1) + @test typeof(num) <: Vector{Float64} +end + @testset "plain_array" begin A = [1 2 3; 4 5 6; 7 8 9] - As = sparse(A) - Ar = reshape(As, 3, 3, 1) - Ast = @SMatrix [1 2 3; 4 5 6; 7 8 9] - @test @inferred(Augmentor.plain_array(As)) == A - @test typeof(Augmentor.plain_array(As)) == typeof(A) - @test @inferred(Augmentor.plain_array(Ar)) == reshape(A,3,3,1) - @test typeof(Augmentor.plain_array(Ar)) <: Array - @test @inferred(Augmentor.plain_array(Ast)) == A - @test typeof(Augmentor.plain_array(Ast)) == typeof(A) @test @inferred(Augmentor.plain_array(A)) === A @test @inferred(Augmentor.plain_array(OffsetArray(A, (-2,-1)))) === A - v = view(A, 2:3, 1:2) - @test typeof(Augmentor.plain_array(v)) <: Array - @test @inferred(Augmentor.plain_array(v)) == A[2:3, 1:2] - v = view(A, IdentityRange(2:3), IdentityRange(1:2)) - @test typeof(Augmentor.plain_array(v)) <: Array - @test @inferred(Augmentor.plain_array(v)) == A[2:3, 1:2] - p = permuteddimsview(A, (2,1)) - @test typeof(Augmentor.plain_array(p)) <: Array - @test @inferred(Augmentor.plain_array(p)) == A' - p = view(permuteddimsview(A, (2,1)), IdentityRange(2:3), IdentityRange(1:2)) - @test typeof(Augmentor.plain_array(p)) <: Array - @test @inferred(Augmentor.plain_array(p)) == A'[2:3, 1:2] + let As = sparse(A) + @test @inferred(Augmentor.plain_array(As)) == A + @test typeof(Augmentor.plain_array(As)) == typeof(A) + Ar = reshape(As, 3, 3, 1) + @test typeof(Ar) <: Base.ReshapedArray + @test @inferred(Augmentor.plain_array(Ar)) == reshape(A,3,3,1) + @test typeof(Augmentor.plain_array(Ar)) <: Array + end + let Ast = @SMatrix [1 2 3; 4 5 6; 7 8 9] + @test @inferred(Augmentor.plain_array(Ast)) == A + @test typeof(Augmentor.plain_array(Ast)) == typeof(A) + end + let v = view(A, 2:3, 1:2) + @test typeof(Augmentor.plain_array(v)) <: Array + @test @inferred(Augmentor.plain_array(v)) == A[2:3, 1:2] + end + let v = view(OffsetArray(A, (-2,-1)), 0:1, 0:1) + @test typeof(Augmentor.plain_array(v)) <: Array + @test @inferred(Augmentor.plain_array(v)) == A[2:3, 1:2] + end + let v = view(A, IdentityRange(2:3), IdentityRange(1:2)) + @test typeof(Augmentor.plain_array(v)) <: Array + @test @inferred(Augmentor.plain_array(v)) == A[2:3, 1:2] + end + let p = permuteddimsview(A, (2,1)) + @test typeof(Augmentor.plain_array(p)) <: Array + @test @inferred(Augmentor.plain_array(p)) == A' + end + let p = view(permuteddimsview(A, (2,1)), IdentityRange(2:3), IdentityRange(1:2)) + @test typeof(Augmentor.plain_array(p)) <: Array + @test @inferred(Augmentor.plain_array(p)) == A'[2:3, 1:2] + end +end + +@testset "match_idx" begin + A = [1 2 3; 4 5 6; 7 8 9] + @test @inferred(Augmentor.match_idx(A, indices(A))) === A + let img = @inferred Augmentor.match_idx(A, (2:4, 2:4)) + @test indices(img) === (2:4, 2:4) + @test typeof(img) <: OffsetArray + end + let B = view(A,1:3,1:3) + @test @inferred(Augmentor.match_idx(B, indices(B))) === B + end + let B = view(A,1:3,1:3) + img = @inferred(Augmentor.match_idx(B, B.indexes)) + @test indices(img) === (1:3, 1:3) + @test typeof(img) <: OffsetArray + end + let img = @inferred Augmentor.match_idx(view(A,1:3,1:3), (2:4,2:4)) + @test indices(img) === (2:4, 2:4) + @test typeof(img) <: OffsetArray + end + let C = Augmentor.prepareaffine(A) + @test @inferred(Augmentor.match_idx(C, (2:4, 2:4))) === C + end end @testset "direct_view" begin @@ -55,6 +117,32 @@ end @test @inferred(Augmentor.indirect_view(Av, (2:-1:1,2:-1:1))) === view(A,3:-1:2,2:-1:1) end +@testset "vectorize" begin + @test @inferred(Augmentor.vectorize(2)) === 2:2 + @test @inferred(Augmentor.vectorize(2.3)) === 2.3:2.3 + @test @inferred(Augmentor.vectorize(3:4)) === 3:4 + @test @inferred(Augmentor.vectorize(3:1:4)) === 3:1:4 + @test @inferred(Augmentor.vectorize(3.0:4)) === 3.0:4 + @test @inferred(Augmentor.vectorize(3.0:1:4)) === 3.0:1:4 + @test @inferred(Augmentor.vectorize(Base.OneTo(4))) === Base.OneTo(4) +end + +@testset "round_if_float" begin + @test @inferred(Augmentor.round_if_float(3,2)) === 3 + @test @inferred(Augmentor.round_if_float(3.1111,2)) === 3.11 + @test @inferred(Augmentor.round_if_float((3,3.1111),2)) === (3,3.11) +end + +@testset "unionrange" begin + @test_throws MethodError Augmentor.unionrange(1:1:2, 4:5) + @test @inferred(Augmentor.unionrange(1:5, 2:6)) === 1:6 + @test @inferred(Augmentor.unionrange(2:6, 1:5)) === 1:6 + @test @inferred(Augmentor.unionrange(1:2, 4:5)) === 1:5 + @test @inferred(Augmentor.unionrange(Base.OneTo(2), 4:5)) === 1:5 + @test @inferred(Augmentor.unionrange(1:6, 2:3)) === 1:6 + @test @inferred(Augmentor.unionrange(2:3, 1:6)) === 1:6 +end + @testset "_2dborder!" begin A = rand(2, 5, 6) @test @inferred(Augmentor._2dborder!(A, 0.)) === A From b174795a3d26780b671ef3a710eeffda78a6c5e3 Mon Sep 17 00:00:00 2001 From: Christof Stocker Date: Sat, 8 Jul 2017 18:05:38 +0200 Subject: [PATCH 5/5] update NEWS.md and add docstrings --- NEWS.md | 7 +++++++ src/augment.jl | 19 ++++++++++++++++++- src/augmentbatch.jl | 22 ++++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index ff3e9e06..3f090db3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,12 @@ # v0.2.0 +New functionality: + +- `augment!`: Use preallocated storage for the output + +- `augmentbatch!`: Augment a whole batch of images. Optionally + using multiple threads. + New operations: - `ConvertEltype`: Convert the array elements to the given type diff --git a/src/augment.jl b/src/augment.jl index 53763d64..a8fb7b92 100644 --- a/src/augment.jl +++ b/src/augment.jl @@ -49,6 +49,24 @@ end # -------------------------------------------------------------------- +""" + augment!(out, img, pipeline) -> out + +Apply the operations of the given `pipeline` to the image `img` +and write the resulting image into `out`. + +The parameter `pipeline` can be a subtype of +`Augmentor.Pipeline`, a tuple of `Augmentor.Operation`, or a +single `Augmentor.Operation` + +```julia +img = testpattern() +out = similar(img) +augment!(out, img, FlipX() |> FlipY()) +augment!(out, img, (FlipX(), FlipY())) +augment!(out, img, FlipX()) +``` +""" augment!(out, img, op::Operation) = augment!(out, img, (op,)) function augment!(out, img, pipeline::AbstractPipeline) @@ -64,4 +82,3 @@ end @generated function _augment_avoid_eager(img, pipeline::Vararg{Operation}) Expr(:block, Expr(:meta, :inline), augment_impl(:img, pipeline, true)) end - diff --git a/src/augmentbatch.jl b/src/augmentbatch.jl index d4555eec..4cdddbe4 100644 --- a/src/augmentbatch.jl +++ b/src/augmentbatch.jl @@ -5,6 +5,28 @@ imagesvector(imgs::AbstractArray) = obsview(imgs) # -------------------------------------------------------------------- +""" + augmentbatch!([resource], outs, imgs, pipeline) -> outs + +Apply the operations of the given `pipeline` to the images in +`imgs` and write the resulting images into `outs`. + +Both `outs` and `imgs` have to contain the same number of images. +Each of the two variables can either be in the form of a higher +dimensional array for which the last dimension enumerates the +individual images, or alternatively in the form of a vector of +arrays, for which each vector element denotes an image. + +The parameter `pipeline` can be a subtype of +`Augmentor.Pipeline`, a tuple of `Augmentor.Operation`, or a +single `Augmentor.Operation`. + +The optional first parameter `resource` can either be `CPU1()` +(default) or `CPUThreads()`. In the case of the later the images +will be augmented in parallel. For this to make sense make sure +that the environment variable `JULIA_NUM_THREADS` is set to a +reasonable number so that `Threads.nthreads()` is greater than 1. +""" function augmentbatch!( outs::AbstractArray, imgs::AbstractArray,