diff --git a/.gitignore b/.gitignore index cd8524be..c12b81bd 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ _build/ _static/ _templates +/Manifest.toml diff --git a/.travis.yml b/.travis.yml index 28f118e7..65b0aed5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,9 +3,12 @@ language: julia os: - linux - osx + - windows julia: - - 0.7 - 1.0 + - 1.1 + - 1.2 + - 1.3 - nightly notifications: email: false @@ -18,14 +21,6 @@ matrix: allow_failures: - julia: nightly -## uncomment and modify the following lines to manually install system packages -#addons: -# apt: # apt-get for linux -# packages: -# - gfortran -#before_script: # homebrew for mac -# - if [ $TRAVIS_OS_NAME = osx ]; then brew install gcc; fi - #jobs: # include: # - stage: deploy @@ -36,10 +31,5 @@ matrix: # - julia -e 'import Pkg; Pkg.add("Documenter")' # - julia -e 'import Augmentor; ENV["DOCUMENTER_DEBUG"] = "true"; include(joinpath("docs","make.jl"))' -## uncomment the following lines to override the default test script -script: - - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi - - julia --check-bounds=yes --color=yes -e 'import Pkg; Pkg.clone(pwd()); Pkg.build("Augmentor"); Pkg.test("Augmentor"; coverage=true)'; - after_success: - julia -e 'using Pkg; Pkg.add("Coverage"); using Coverage; Coveralls.submit(process_folder())' diff --git a/Project.toml b/Project.toml new file mode 100644 index 00000000..e847c162 --- /dev/null +++ b/Project.toml @@ -0,0 +1,49 @@ +name = "Augmentor" +uuid = "02898b10-1f73-11ea-317c-6393d7073e15" +authors = ["Christof Stocker "] +version = "0.6.0-pre" + +[deps] +ColorTypes = "3da002f7-5984-5a60-b8a6-cbb66c0b333f" +ComputationalResources = "ed09eef8-17a6-5b46-8889-db040fac31e3" +CoordinateTransformations = "150eb455-5306-5404-9cee-2592286d6298" +FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" +IdentityRanges = "bbac6d45-d8f3-5730-bfe4-7a449cd117ca" +ImageCore = "a09fc81d-aa75-5fe9-8630-4744c3626534" +ImageFiltering = "6a3955dd-da59-5b1f-98d4-e7296123deb5" +ImageTransformations = "02fcd773-0e25-5acc-982a-7f6622650795" +Interpolations = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +MLDataPattern = "9920b226-0b2a-5f5f-9153-9aa70a013f8b" +MappedArrays = "dbb5928d-eab1-5f90-85c2-b9b0edb7c900" +OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" +Rotations = "6038ab10-8711-5258-84ad-4b1120ba62dc" +StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" + +[compat] +ColorTypes = "0.7, 0.8" +ComputationalResources = "0.3" +CoordinateTransformations = "0.5" +FileIO = "1" +IdentityRanges = "0.3" +ImageCore = "0.8.1" +ImageFiltering = "0.4, 0.5, 0.6" +ImageTransformations = "0.5, 0.6, 0.7, 0.8" +Interpolations = "0.8, 0.9, 0.10, 0.11, 0.12" +MLDataPattern = "0.4, 0.5" +MappedArrays = "0.1, 0.2" +OffsetArrays = "0.8, 0.9, 0.10, 0.11" +Rotations = "0.7, 0.8, 0.9, 0.10, 0.11, 0.12" +StaticArrays = "0.8, 0.9, 0.10, 0.11, 0.12" +julia = "1" + +[extras] +ImageInTerminal = "d8c32880-2388-543b-8c61-d9f865259254" +ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1" +ReferenceTests = "324d217c-45ce-50fc-942e-d289b448e8cf" +Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +TestImages = "5e47fb64-e119-507b-a336-dd2b206d9990" + +[targets] +test = ["ImageInTerminal", "ImageMagick", "ReferenceTests", "Statistics", "TestImages", "Test"] diff --git a/REQUIRE b/REQUIRE deleted file mode 100644 index a2a2d306..00000000 --- a/REQUIRE +++ /dev/null @@ -1,15 +0,0 @@ -julia 0.7 -MappedArrays 0.0.3 -ImageCore 0.5.0 -ImageTransformations 0.3.0 -ImageFiltering 0.1.4 -CoordinateTransformations 0.4.0 -Interpolations 0.6 -Rotations 0.6.1 -StaticArrays 0.6.5 -OffsetArrays -IdentityRanges -ColorTypes 0.6.6 -MLDataPattern 0.1.2 -ComputationalResources 0.0.2 -FileIO diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index a43da2ff..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,37 +0,0 @@ -environment: - matrix: - - julia_version: 0.7 - - julia_version: 1.0 - - julia_version: latest - -platform: - - x86 # 32-bit - - x64 # 64-bit - -## uncomment the following lines to allow failures on nightly julia -## (tests will run but not make your overall status red) -matrix: - allow_failures: - - julia_version: latest - -branches: - only: - - master - - /release-.*/ - -notifications: - - provider: Email - on_build_success: false - on_build_failure: false - on_build_status_changed: false - -install: - - ps: iex ((new-object net.webclient).DownloadString("https://raw.githubusercontent.com/JuliaCI/Appveyor.jl/version-1/bin/install.ps1")) - -build_script: - - echo "%JL_BUILD_SCRIPT%" - - C:\julia\bin\julia -e "%JL_BUILD_SCRIPT%" - -test_script: - - echo "%JL_TEST_SCRIPT%" - - C:\julia\bin\julia -e "%JL_TEST_SCRIPT%" diff --git a/src/Augmentor.jl b/src/Augmentor.jl index 995f9fa5..bfe4c8d5 100644 --- a/src/Augmentor.jl +++ b/src/Augmentor.jl @@ -9,12 +9,18 @@ using CoordinateTransformations using Rotations using Interpolations using StaticArrays -using OffsetArrays using IdentityRanges using MLDataPattern using ComputationalResources using FileIO using Base.PermutedDimsArrays: PermutedDimsArray +using LinearAlgebra +using OffsetArrays + +# axes(::OffsetArray) changes from Base.Slice to Base.IdentityUnitRange in julia 1.1 +# https://github.com/JuliaArrays/OffsetArrays.jl/pull/62 +# TODO: switch to Base.IdentityUnitRange when we decide to drop 1.0 compatibility +using OffsetArrays: IdentityUnitRange export @@ -67,6 +73,7 @@ export testpattern +include("compat.jl") include("utils.jl") include("types.jl") include("operation.jl") @@ -97,7 +104,10 @@ include("augment.jl") include("augmentbatch.jl") function __init__() - rand_mutex[] = Threads.Mutex() + if VERSION < v"1.3" + # see compat.jl + rand_mutex[] = Threads.Mutex() + end end end # module diff --git a/src/augment.jl b/src/augment.jl index 8af8051b..670d02d5 100644 --- a/src/augment.jl +++ b/src/augment.jl @@ -110,14 +110,14 @@ augment!(out, img, op::Operation) = augment!(out, img, (op,)) function augment!(out::AbstractArray, img::AbstractArray, pipeline::AbstractPipeline) out_lazy = _augment_avoid_eager(img, pipeline) - copy!(match_idx(out, axes(out_lazy)), out_lazy) + copyto!(match_idx(out, axes(out_lazy)), out_lazy) out end function augment!(outs::NTuple{N,AbstractArray}, imgs::NTuple{N,AbstractArray}, pipeline::AbstractPipeline) where N outs_lazy = _augment_avoid_eager(imgs, pipeline) map(outs, outs_lazy) do out, out_lazy - copy!(match_idx(out, axes(out_lazy)), out_lazy) + copyto!(match_idx(out, axes(out_lazy)), out_lazy) end outs end diff --git a/src/compat.jl b/src/compat.jl new file mode 100644 index 00000000..dc04dba5 --- /dev/null +++ b/src/compat.jl @@ -0,0 +1,22 @@ +if VERSION >= v"1.3" + const safe_rand = rand +else + # rand is thread safe after julia 1.3 + # TODO: delete this when we decide to drop 1.0 compatibility + + # -------------------------------------------------------------------- + # 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}() + + # 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 +end \ No newline at end of file diff --git a/src/distortedview.jl b/src/distortedview.jl index ac5b8253..4de4e0d3 100644 --- a/src/distortedview.jl +++ b/src/distortedview.jl @@ -6,6 +6,8 @@ struct DistortedView{T,P<:AbstractMatrix,E<:AbstractExtrapolation,G,D} <: Abstra function DistortedView(parent::AbstractMatrix{T}, grid::AbstractArray{Float64,3}) where T @assert size(grid,1) == 2 + # to compare two DistortedViews, their `axes` should be the same + parent = plain_axes(parent) etp = ImageTransformations.box_extrapolation(parent, Flat()) field = ImageTransformations.box_extrapolation(grid, 0.0) new{T,typeof(parent),typeof(etp),typeof(grid),typeof(field)}(parent, etp, grid, field) @@ -13,7 +15,8 @@ struct DistortedView{T,P<:AbstractMatrix,E<:AbstractExtrapolation,G,D} <: Abstra end Base.parent(A::DistortedView) = A.parent -Base.size(A::DistortedView) = map(length, axes(A.parent)) +Base.size(A::DistortedView) = map(length, axes(A)) +Base.axes(A::DistortedView) = axes(A.parent) function Base.showarg(io::IO, A::DistortedView, toplevel) print(io, typeof(A).name, '(') @@ -22,7 +25,7 @@ function Base.showarg(io::IO, A::DistortedView, toplevel) Base.showarg(io, A.grid, false) print(io, " as ", size(A.field,2), '×', size(A.field,3), " vector field") print(io, ')') - toplevel && print(io, " with eltype ", eltype(v)) + toplevel && print(io, " with eltype ", eltype(A)) end # inline speeds up ~30% @@ -39,8 +42,8 @@ end # map array indices to grid indices gi, gj = (i-1)/(leny-1)*(gh-1)+1, (j-1)/(lenx-1)*(gw-1)+1 # compute parent indices offset - @inbounds dy = field[1, gi, gj] * leny - @inbounds dx = field[2, gi, gj] * lenx + @inbounds dy = field(1, gi, gj) * leny + @inbounds dx = field(2, gi, gj) * lenx # compute parent indices and return value # Note: we subtract instead of add, because the vector field is # specified in forward mode (i.e. in reference to the source @@ -50,6 +53,6 @@ end checkbounds(indsx, j) @inbounds y = indsy[i] - dy @inbounds x = indsx[j] - dx - @inbounds res = etp[y, x] + @inbounds res = etp(y, x) res end diff --git a/src/operation.jl b/src/operation.jl index d9b6c478..477ab276 100644 --- a/src/operation.jl +++ b/src/operation.jl @@ -63,7 +63,9 @@ for FUN in (:applyeager, :applylazy, :applypermute, end function applyeager(op::Operation, img::AbstractArray, param) - maybe_copy(applylazy(op, img, param)) + # TODO: we don't need wrappers for eager mode, so we might want to + # add plain_array to it as well for the sake of simplicity + contiguous(applylazy(op, img, param)) end function applyaffineview(op::Operation, img::AbstractArray, param) @@ -116,7 +118,7 @@ end @generated function toaffinemap_common(op::AffineOperation, img::AbstractArray{T,N}, param) where {T,N} quote tfm = toaffinemap(op, img, param) - AffineMap(SMatrix(tfm.m), SVector(tfm.v))::AffineMap{SArray{Tuple{$N,$N},Float64,$N,$(N*N)},SVector{$N,Float64}} + AffineMap(SMatrix(tfm.linear), SVector(tfm.translation))::AffineMap{SArray{Tuple{$N,$N},Float64,$N,$(N*N)},SVector{$N,Float64}} end end diff --git a/src/operations/cache.jl b/src/operations/cache.jl index f229a268..3bec779f 100644 --- a/src/operations/cache.jl +++ b/src/operations/cache.jl @@ -53,7 +53,7 @@ pl = ElasticDistortion(3,3) |> zeros(20,20) |> Rotate(-10:10) """ struct CacheImage <: ImageOperation end -applyeager(op::CacheImage, img::AbstractArray, param) = maybe_copy(img) +applyeager(op::CacheImage, img::AbstractArray, param) = contiguous(img) function showconstruction(io::IO, op::CacheImage) print(io, typeof(op).name.name, "()") @@ -92,12 +92,12 @@ function applylazy(op::CacheImageInto, img::Tuple) end function applylazy(op::CacheImageInto{<:AbstractArray}, img::AbstractArray, param) - copy!(match_idx(op.buffer, axes(img)), img) + copyto!(match_idx(op.buffer, axes(img)), img) end function applylazy(op::CacheImageInto{<:Tuple}, imgs::Tuple) map(op.buffer, imgs) do buffer, img - copy!(match_idx(buffer, axes(img)), img) + copyto!(match_idx(buffer, axes(img)), img) end end @@ -130,7 +130,7 @@ function Base.show(io::IO, op::CacheImageInto{<:AbstractArray}) print(io, summary(op.buffer)) else print(io, typeof(op).name, "(") - showarg(io, op.buffer) + Base.showarg(io, op.buffer, false) print(io, ")") end end @@ -142,7 +142,7 @@ function Base.show(io::IO, op::CacheImageInto{<:Tuple}) else print(io, typeof(op).name, "((") for (i, buffer) in enumerate(op.buffer) - showarg(io, buffer) + Base.showarg(io, buffer, false) i < length(op.buffer) && print(io, ", ") end print(io, "))") diff --git a/src/operations/convert.jl b/src/operations/convert.jl index b4ad6643..bc57b94f 100644 --- a/src/operations/convert.jl +++ b/src/operations/convert.jl @@ -52,7 +52,7 @@ end @inline supports_lazy(::Type{<:ConvertEltype}) = true function applyeager(op::ConvertEltype{T}, img::AbstractArray, param) where T - maybe_copy(convert(AbstractArray{T}, img)) + contiguous(convert(AbstractArray{T}, img)) end function applylazy(op::ConvertEltype{T}, img::AbstractArray, param) where T diff --git a/src/operations/crop.jl b/src/operations/crop.jl index 41a3f68f..e418bbfd 100644 --- a/src/operations/crop.jl +++ b/src/operations/crop.jl @@ -88,6 +88,7 @@ function Base.show(io::IO, op::Crop{N}) where N print(io, "Crop region $(op.indices)") end else + print(io, "Augmentor.") print(io, typeof(op).name, "{$N}($(op.indices))") end end @@ -193,6 +194,7 @@ function Base.show(io::IO, op::CropNative{N}) where N print(io, "Crop native region $(op.indices)") end else + print(io, "Augmentor.") print(io, typeof(op).name, "{$N}($(op.indices))") end end @@ -259,8 +261,8 @@ CropSize(; width=64, height=64) = CropSize((height,width)) @inline supports_view(::Type{<:CropSize}) = true @inline supports_stepview(::Type{<:CropSize}) = true -function cropsize_axes(op::CropSize, img::AbstractArray) - cntr = convert(Tuple, center(img)) +function cropsize_axes(op::CropSize, img::AbstractArray)::Tuple + cntr = Tuple(center(img)) sze = op.size corner = map((ci,si)->floor(Int,ci)-floor(Int,si/2)+!isinteger(ci), cntr, sze) map((b,s)->b:(b+s-1), corner, sze) @@ -292,6 +294,7 @@ function Base.show(io::IO, op::CropSize{N}) where N print(io, "Crop a $(join(op.size,"×")) window around the center") end else + print(io, "Augmentor.") print(io, typeof(op), "($(op.size))") end end @@ -358,7 +361,7 @@ CropRatio(; ratio = 1.) = CropRatio(ratio) @inline supports_view(::Type{CropRatio}) = true @inline supports_stepview(::Type{CropRatio}) = true -function cropratio_axes(op::CropRatio, img::AbstractMatrix) +function cropratio_axes(op::CropRatio, img::AbstractMatrix)::Tuple h, w = map(length, axes(img)) ratio = op.ratio # compute new size based on ratio @@ -368,7 +371,7 @@ function cropratio_axes(op::CropRatio, img::AbstractMatrix) nh = nh > 1 ? nh : 1 sze = nh < h ? nh : h, nw < w ? nw : w # compute axes around center for given size - cntr = convert(Tuple, center(img)) + cntr = Tuple(center(img)) corner = map((ci,si)->floor(Int,ci)-floor(Int,si/2)+!isinteger(ci), cntr, sze) map((b,s)->b:(b+s-1), corner, sze) end @@ -397,14 +400,14 @@ function ratio2str(ratio) found = false for i = 1:20 high = i * high0 - if round(high) == round(high,2) + if round(high) == round(high; digits=2) low = i found = true break end end if !found - string(round(ratio,2)) + string(round(ratio; digits=2)) elseif ratio >= 1 string(round(Int,high), ':', low) else diff --git a/src/operations/either.jl b/src/operations/either.jl index 80cf860d..4c1050ab 100644 --- a/src/operations/either.jl +++ b/src/operations/either.jl @@ -91,6 +91,7 @@ struct Either{N,T<:Tuple} <: ImageOperation end Either() = throw(ArgumentError("Must provide at least one operation in the constructor of \"Either\"")) +Either(::Tuple{}) = Either() function Either(operations::NTuple{N,ImageOperation}, chances::NTuple{N,Real} = map(op -> 1/length(operations), operations)) where N Either(operations, SVector{N}(chances)) @@ -180,6 +181,14 @@ function applyeager(op::Either, img::AbstractArray, idx) _offsetarray(applyeager(op.operations[idx], img)) end +# specialize for Either to preserve type stability +@inline function applyeager(op::Either, img::AbstractArray) + applyeager(op, img, randparam(op, img)) +end +@inline function applyview(op::Either, img::AbstractArray) + applyview(op, img, randparam(op, img)) +end + # Sample a random operation and pass the function call along. # Note: "applyaffine" needs to map to "applyaffine_common" for # type stability, because otherwise the concrete type of the @@ -197,7 +206,7 @@ for KIND in (:permute, :view, :stepview, :affine, :affineview) end function showconstruction(io::IO, op::Either) - chances_float = map(c->round(c, 3), op.chances) + chances_float = map(c->round(c; digits=3), op.chances) if all(x->x≈chances_float[1], chances_float) for (i, op_i) in enumerate(op.operations) showconstruction(io, op_i) @@ -218,13 +227,13 @@ function Base.show(io::IO, op::Either) print(io, "Either:") for (op_i, p_i) in zip(op.operations, op.chances) print(io, " (", round(Int, p_i*100), "%) ") - Base.showcompact(io, op_i) + Base.show(IOContext(io, :compact => true), op_i) print(io, '.') end else print(io, typeof(op).name, " (1 out of ", length(op.operations), " operation(s)):") percent_int = map(c->round(Int, c*100), op.chances) - percent_float = map(c->round(c*100, 1), op.chances) + percent_float = map(c->round(c*100; digits=1), op.chances) percent = if any(i != f for (i,f) in zip(percent_int,percent_float)) percent_float else @@ -234,7 +243,7 @@ function Base.show(io::IO, op::Either) for (op_i, p_i) in zip(op.operations, percent) println(io) print(io, " - ", lpad(string(p_i), k, " "), "% chance to: ") - Base.showcompact(io, op_i) + Base.show(IOContext(io, :compact => true), op_i) end end end diff --git a/src/operations/flip.jl b/src/operations/flip.jl index 82a7f6e7..84c09662 100644 --- a/src/operations/flip.jl +++ b/src/operations/flip.jl @@ -57,8 +57,7 @@ FlipX(p::Number) = Either(FlipX(), p) @inline supports_stepview(::Type{FlipX}) = true toaffinemap(::FlipX, img::AbstractMatrix) = recenter(@SMatrix([1. 0; 0 -1.]), center(img)) -# Base.flipdim not type-stable for AbstractArray's -applyeager(::FlipX, img::Array, param) = plain_array(flipdim(img,2)) +applyeager(::FlipX, img::Array, param) = plain_array(reverse(img; dims=2)) applyeager(op::FlipX, img::AbstractArray, param) = plain_array(applystepview(op, img, param)) applylazy_fallback(op::FlipX, img::AbstractMatrix, param) = applystepview(op, img, param) @@ -139,8 +138,7 @@ FlipY(p::Number) = Either(FlipY(), p) @inline supports_stepview(::Type{FlipY}) = true toaffinemap(::FlipY, img::AbstractMatrix) = recenter(@SMatrix([-1. 0; 0 1.]), center(img)) -# Base.flipdim not type-stable for AbstractArray's -applyeager(::FlipY, img::Array, param) = plain_array(flipdim(img,1)) +applyeager(::FlipY, img::Array, param) = plain_array(reverse(img; dims=1)) applyeager(op::FlipY, img::AbstractArray, param) = plain_array(applystepview(op, img, param)) applylazy_fallback(op::FlipY, img::AbstractMatrix, param) = applystepview(op, img, param) diff --git a/src/operations/mapfun.jl b/src/operations/mapfun.jl index 49429f7d..5b262628 100644 --- a/src/operations/mapfun.jl +++ b/src/operations/mapfun.jl @@ -51,7 +51,7 @@ end @inline supports_lazy(::Type{<:MapFun}) = true function applyeager(op::MapFun, img::AbstractArray, param) - maybe_copy(map(op.fun, img)) + contiguous(map(op.fun, img)) end function applylazy(op::MapFun, img::AbstractArray, param) @@ -130,7 +130,7 @@ end function applyeager(op::AggregateThenMapFun, img::AbstractArray, param) agg = op.aggfun(img) - maybe_copy(map(x -> op.mapfun(x, agg), img)) + contiguous(map(x -> op.mapfun(x, agg), img)) end function applylazy(op::AggregateThenMapFun, img::AbstractArray, param) diff --git a/src/operations/noop.jl b/src/operations/noop.jl index a4082056..8b9eb7bd 100644 --- a/src/operations/noop.jl +++ b/src/operations/noop.jl @@ -15,7 +15,7 @@ struct NoOp <: AffineOperation end # TODO: implement method for n-dim arrays toaffinemap(::NoOp, img::AbstractMatrix) = AffineMap(@SMatrix([1. 0; 0 1.]), @SVector([0.,0.])) -applyeager(::NoOp, img::AbstractArray, param) = maybe_copy(img) +applyeager(::NoOp, img::AbstractArray, param) = contiguous(img) applylazy(::NoOp, img::AbstractArray, param) = img function applyview(::NoOp, img::AbstractArray, param) diff --git a/src/operations/resize.jl b/src/operations/resize.jl index 44e868ff..734b92de 100644 --- a/src/operations/resize.jl +++ b/src/operations/resize.jl @@ -58,8 +58,8 @@ Resize(; width=64, height=64) = Resize((height,width)) function toaffinemap(op::Resize{2}, img::AbstractMatrix) # emulate behaviour of ImageTransformations.imresize! - Rin = CartesianRange(axes(img)) - sf = map(/, op.size, (last(Rin)-first(Rin)+1).I) + Rin = CartesianIndices(axes(img)) + sf = map(/, op.size, (last(Rin)-first(Rin)+CartesianIndex(1, 1)).I) offset = map((io,ir,s)->io - 0.5 - s*(ir-0.5), first(Rin).I, (1, 1), map(inv,sf)) ttrans = AffineMap(@SMatrix([1. 0.; 0. 1.]), SVector(offset)) tscale = recenter(@SMatrix([sf[1] 0.; 0. sf[2]]), @SVector([1., 1.])) @@ -79,8 +79,8 @@ function padrange(range::AbstractUnitRange, pad) end function applyaffineview(op::Resize{N}, img::AbstractArray{T,N}, param) where {T,N} - Rin, Rout = CartesianRange(axes(img)), CartesianRange(op.size) - sf = map(/, (last(Rout)-first(Rout)+1).I, (last(Rin)-first(Rin)+1).I) + Rin, Rout = CartesianIndices(axes(img)), CartesianIndices(op.size) + sf = Tuple(last(Rout)) ./ Tuple(last(Rin) - first(Rin) + CartesianIndex(1, 1)) # We have to extrapolate if the image is upscaled, # otherwise the original border will only cause a single pixel tinv = toaffinemap(op, img, param) @@ -103,6 +103,7 @@ function Base.show(io::IO, op::Resize{N}) where {N} print(io, "Resize to $(op.size)") end else + print(io, "Augmentor.") print(io, typeof(op), "($(op.size))") end end diff --git a/src/operations/rotation.jl b/src/operations/rotation.jl index 25132340..cbc5a8ff 100644 --- a/src/operations/rotation.jl +++ b/src/operations/rotation.jl @@ -70,13 +70,13 @@ function applypermute(::Rotate90, img::AbstractMatrix{T}, param) where T end function applypermute(::Rotate90, sub::SubArray{T,2,IT,<:NTuple{2,AbstractRange}}, param) where {T,IT<:PermutedDimsArray{T,2,(2,1)}} - idx = map(StepRange, sub.indices) + idx = map(x->convert(StepRange, x), sub.indices) img = parent(parent(sub)) view(img, reverse(idx[2]), idx[1]) end function applypermute(::Rotate90, sub::SubArray{T,2,IT,<:NTuple{2,AbstractRange}}, param) where {T,IT} - idx = map(StepRange, sub.indices) + idx = map(x->convert(StepRange, x), sub.indices) img = parent(sub) perm_img = PermutedDimsArray{T,2,(2,1),(2,1),typeof(img)}(img) view(perm_img, reverse(idx[2]), idx[1]) @@ -224,7 +224,7 @@ function applypermute(::Rotate270, img::AbstractMatrix{T}, param) where T end function applypermute(::Rotate270, sub::SubArray{T,2,IT,<:NTuple{2,AbstractRange}}, param) where {T,IT<:PermutedDimsArray{T,2,(2,1)}} - idx = map(StepRange, sub.indices) + idx = map(x->convert(StepRange, x), sub.indices) img = parent(parent(sub)) view(img, idx[2], reverse(idx[1])) end @@ -247,6 +247,7 @@ for deg in (90, 180, 270) if get(io, :compact, false) print(io, "Rotate ", $deg, " degree") else + print(io, "Augmentor.") print(io, $T, "()") end end diff --git a/src/operations/scale.jl b/src/operations/scale.jl index c5f648dd..c9948752 100644 --- a/src/operations/scale.jl +++ b/src/operations/scale.jl @@ -70,7 +70,9 @@ struct Scale{N,T<:AbstractVector} <: AffineOperation new{N,T}(factors) end end +Scale{N}() where N = throw(MethodError(Scale{N}, ())) Scale() = throw(MethodError(Scale, ())) +Scale{N}(::Tuple{}) where N = throw(MethodError(Scale{N}, ((), ))) Scale(::Tuple{}) = throw(MethodError(Scale, ((),))) Scale(factors...) = Scale(factors) Scale(factor::Union{AbstractVector,Real}) = Scale((factor, factor)) @@ -89,7 +91,7 @@ randparam(op::Scale, imgs::Tuple) = randparam(op, imgs[1]) function randparam(op::Scale, img::AbstractArray{T,N}) where {T,N} i = safe_rand(1:length(op.factors[1])) - ntuple(j -> Float64(op.factors[j][i]), Val{N}) + ntuple(j -> Float64(op.factors[j][i]), Val(N)) end function toaffinemap(op::Scale{2}, img::AbstractMatrix, idx) @@ -111,6 +113,7 @@ function Base.show(io::IO, op::Scale{N}) where N print(io, "Scale by I ∈ {$(str)}") end else + print(io, "Augmentor.") fct = length(op.factors[1]) == 1 ? map(first,op.factors) : op.factors print(io, typeof(op).name, "{$N}($(fct))") end diff --git a/src/operations/zoom.jl b/src/operations/zoom.jl index ec97fc31..0f5405f1 100644 --- a/src/operations/zoom.jl +++ b/src/operations/zoom.jl @@ -74,7 +74,9 @@ struct Zoom{N,T<:AbstractVector} <: ImageOperation new{N,T}(factors) end end +Zoom{N}() where N = throw(MethodError(Zoom{N}, ())) Zoom() = throw(MethodError(Zoom, ())) +Zoom{N}(::Tuple{}) where N = throw(MethodError(Zoom{N}, ((), ))) Zoom(::Tuple{}) = throw(MethodError(Zoom, ((),))) Zoom(factors...) = Zoom(factors) Zoom(factor::Union{AbstractVector,Real}) = Zoom((factor, factor)) @@ -94,7 +96,7 @@ randparam(op::Zoom, imgs::Tuple) = randparam(op, imgs[1]) function randparam(op::Zoom, img::AbstractArray{T,N}) where {T,N} i = safe_rand(1:length(op.factors[1])) - ntuple(j -> Float64(op.factors[j][i]), Val{N}) + ntuple(j -> Float64(op.factors[j][i]), Val(N)) end function toaffinemap(op::Zoom{2}, img::AbstractMatrix, idx) @@ -134,6 +136,7 @@ function Base.show(io::IO, op::Zoom{N}) where N end else fct = length(op.factors[1]) == 1 ? map(first,op.factors) : op.factors + print(io, "Augmentor.") print(io, typeof(op).name, "{$N}($(fct))") end end diff --git a/src/pipeline.jl b/src/pipeline.jl index d87c2638..a448187f 100644 --- a/src/pipeline.jl +++ b/src/pipeline.jl @@ -44,7 +44,7 @@ function Base.show(io::IO, pipeline::Pipeline) for (i, op) in enumerate(ops) println(io) print(io, lpad(string(i), k+1, " "), ".) ") - Base.showcompact(io, op) + Base.show(IOContext(io, :compact => true), op) end end end diff --git a/src/utils.jl b/src/utils.jl index d5c72e66..048e00f1 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -16,29 +16,21 @@ function use_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}() - -# 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 +""" + contiguous(A::AbstractArray) + contiguous(A::Tuple) -# -------------------------------------------------------------------- +Return a memory contiguous array for better performance. -@inline maybe_copy(A::OffsetArray) = A -@inline maybe_copy(A::Array) = A -@inline maybe_copy(A::SArray) = A -@inline maybe_copy(A::MArray) = A -@inline maybe_copy(A::AbstractArray) = match_idx(collect(A), axes(A)) -@inline maybe_copy(A::Tuple) = map(maybe_copy, A) +Data copy only happens when necessary. For example, views returned by `view`, +`permuteddimsview` are such cases. +""" +@inline contiguous(A::OffsetArray) = A +@inline contiguous(A::Array) = A +@inline contiguous(A::SArray) = A +@inline contiguous(A::MArray) = A +@inline contiguous(A::AbstractArray) = match_idx(collect(A), axes(A)) +@inline contiguous(A::Tuple) = map(contiguous, A) # -------------------------------------------------------------------- @@ -48,10 +40,15 @@ end @inline _plain_array(A::MArray) = A @inline _plain_array(A::Tuple) = map(_plain_array, A) # avoid recursion -@inline plain_array(A) = _plain_array(maybe_copy(A)) +@inline plain_array(A) = _plain_array(contiguous(A)) # -------------------------------------------------------------------- +""" + plain_axes(A::AbstractArray) + +Generate a 1-based array from `A` without data copy. +""" @inline plain_axes(A::Array) = A @inline plain_axes(A::OffsetArray) = parent(A) @inline plain_axes(A::AbstractArray) = _plain_axes(A, axes(A)) @@ -65,7 +62,7 @@ end view(A, axes(A)...) end -@inline function _plain_axes(A::AbstractArray{T,N}, ids::NTuple{N,Base.Slice}) where {T, N} +@inline function _plain_axes(A::AbstractArray{T,N}, ids::NTuple{N, IdentityUnitRange}) where {T, N} view(A, map(i->i.indices, ids)...) end @@ -76,7 +73,7 @@ end # -------------------------------------------------------------------- @inline match_idx(buffer::AbstractArray, inds::Tuple) = buffer -@inline match_idx(buffer::Union{Array,SubArray}, inds::NTuple{N,Union{UnitRange,Base.Slice{<:UnitRange}}}) where {N} = +@inline match_idx(buffer::Union{Array,SubArray}, inds::NTuple{N,Union{UnitRange, IdentityUnitRange}}) where {N} = OffsetArray(buffer, inds) # -------------------------------------------------------------------- diff --git a/test/REQUIRE b/test/REQUIRE deleted file mode 100644 index 5bd464bb..00000000 --- a/test/REQUIRE +++ /dev/null @@ -1,7 +0,0 @@ -FixedPointNumbers 0.3 -ImageInTerminal -TestImages -ReferenceTests -@windows ImageMagick -@linux ImageMagick -@osx QuartzImageIO diff --git a/test/operations/tst_cache.jl b/test/operations/tst_cache.jl index dc01caaa..18f63552 100644 --- a/test/operations/tst_cache.jl +++ b/test/operations/tst_cache.jl @@ -74,11 +74,11 @@ end @test Augmentor.CacheImageInto(buf) === op @test str_show(op) == "Augmentor.CacheImageInto(::Array{Gray{N0f8},2})" @test str_showconst(op) == "CacheImage(Array{Gray{N0f8}}(2, 3))" - op2 = @inferred CacheImage(Array{Gray{N0f8}}(2, 3)) + op2 = @inferred CacheImage(Array{Gray{N0f8}}(undef, 2, 3)) @test typeof(op) == typeof(op2) @test typeof(op.buffer) == typeof(op2.buffer) @test size(op.buffer) == size(op2.buffer) - @test str_showcompact(op) == "Cache into preallocated 2×3 Array{Gray{N0f8},2}" + @test str_showcompact(op) == "Cache into preallocated 2×3 Array{Gray{N0f8},2} with eltype Gray{Normed{UInt8,8}}" v = Augmentor.applylazy(Resize(2,3), camera) res = @inferred Augmentor.applyeager(op, v) @@ -116,11 +116,11 @@ end @test Augmentor.CacheImageInto((buf1,buf2)) === op @test str_show(op) == "Augmentor.CacheImageInto((::Array{Gray{N0f8},2}, ::Array{RGB{N0f8},2}))" @test str_showconst(op) == "CacheImage(Array{Gray{N0f8}}(3, 3), Array{RGB{N0f8}}(2, 3))" - op2 = @inferred CacheImage(Array{Gray{N0f8}}(3, 3), Array{RGB{N0f8}}(2, 3)) + op2 = @inferred CacheImage(Array{Gray{N0f8}}(undef, 3, 3), Array{RGB{N0f8}}(undef, 2, 3)) @test typeof(op) == typeof(op2) @test typeof(op.buffer) == typeof(op2.buffer) @test size.(op.buffer) === size.(op2.buffer) - @test str_showcompact(op) == "Cache into preallocated (3×3 Array{Gray{N0f8},2}, 2×3 Array{RGB{N0f8},2})" + @test str_showcompact(op) == "Cache into preallocated (3×3 Array{Gray{N0f8},2} with eltype Gray{Normed{UInt8,8}}, 2×3 Array{RGB{N0f8},2} with eltype RGB{Normed{UInt8,8}})" @test buf1 == square @test buf2 == rgb_rect diff --git a/test/operations/tst_convert.jl b/test/operations/tst_convert.jl index 643c5281..105211aa 100644 --- a/test/operations/tst_convert.jl +++ b/test/operations/tst_convert.jl @@ -57,7 +57,7 @@ let img = @inferred(Augmentor.applylazy(ConvertEltype(Gray{Float32}), OffsetArray(rect,-2,-1))) @test parent(parent(img)) === rect @test typeof(img) <: ReadonlyMappedArray{Gray{Float32},2} - @test axes(img) === (Base.Slice(-1:0), Base.Slice(0:2)) + @test axes(img) === (OffsetArrays.IdentityUnitRange(-1:0), OffsetArrays.IdentityUnitRange(0:2)) @test img[0,0] isa Gray{Float32} @test collect(img) == convert.(Gray{Float32}, rect) end diff --git a/test/operations/tst_distortions.jl b/test/operations/tst_distortions.jl index 6702efd7..5dc3816c 100644 --- a/test/operations/tst_distortions.jl +++ b/test/operations/tst_distortions.jl @@ -122,7 +122,15 @@ res = @inferred(Augmentor.applylazy(ElasticDistortion(4,4), img)) @test size(res) == size(rect) @test typeof(res) <: Augmentor.DistortedView{eltype(rect)} - @test parent(res) === img + @test parent(res) == rect + # wapper type may change, but storage data isn't copied + if img isa InvWarpedView + @test parent(parent(res)) === img + elseif img isa SubArray + @test parent(parent(res)) === rect + else + @test parent(parent(res)) === rect + end end end @testset "multiple images" begin diff --git a/test/operations/tst_either.jl b/test/operations/tst_either.jl index f7677758..f98c44d3 100644 --- a/test/operations/tst_either.jl +++ b/test/operations/tst_either.jl @@ -73,21 +73,21 @@ end @testset "show" begin @test str_show(Either((Rotate90(),Rotate270(),NoOp()), (0.2,0.3,0.5))) == """ - Augmentor.Either (1 out of 3 operation(s)): + Either (1 out of 3 operation(s)): - 20% chance to: Rotate 90 degree - 30% chance to: Rotate 270 degree - 50% chance to: No operation""" @test str_showcompact(Either((Rotate90(),Rotate270(),NoOp()), (0.2,0.3,0.5))) == "Either: (20%) Rotate 90 degree. (30%) Rotate 270 degree. (50%) No operation." @test str_show(Either((Rotate90(),Rotate270(),NoOp()), (0.15,0.8,0.05))) == """ - Augmentor.Either (1 out of 3 operation(s)): + Either (1 out of 3 operation(s)): - 15% chance to: Rotate 90 degree - 80% chance to: Rotate 270 degree - 5% chance to: No operation""" @test str_showcompact(Either((Rotate90(),Rotate270(),NoOp()), (0.15,0.8,0.05))) == "Either: (15%) Rotate 90 degree. (80%) Rotate 270 degree. (5%) No operation." @test str_show(Either((Rotate90(),Rotate270(),NoOp()), (0.155,0.8,0.045))) == """ - Augmentor.Either (1 out of 3 operation(s)): + Either (1 out of 3 operation(s)): - 15.5% chance to: Rotate 90 degree - 80.0% chance to: Rotate 270 degree - 4.5% chance to: No operation""" @@ -162,12 +162,12 @@ end 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 @inferred(Augmentor.applyeager(op, img)) == reverse(rect; dims=2) @test typeof(Augmentor.applyeager(op, img)) <: OffsetArray 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 @inferred(Augmentor.applyeager(op, img)) == reverse(rect; dims=1) @test typeof(Augmentor.applyeager(op, img)) <: OffsetArray end let op = @inferred Either((Rotate90(),Resize(5,5)), (0,1)) @@ -231,7 +231,7 @@ end @test @inferred(Augmentor.toaffinemap(op, rect, 2)) ≈ AffineMap([1. 0.; 0. -1.], [0,4]) wv = @inferred Augmentor.applyaffine(op, Augmentor.prepareaffine(square)) @test parent(wv).itp.coefs === square - @test wv == flipdim(square,2) + @test wv == reverse(square; dims=2) @test typeof(wv) <: InvWarpedView{eltype(square),2} end let op = @inferred Either((Rotate90(),FlipY()), (0,1)) @@ -245,7 +245,7 @@ end @test @inferred(Augmentor.toaffinemap(op, rect, 2)) ≈ AffineMap([-1. 0.; 0. 1.], [3,0]) wv = @inferred Augmentor.applyaffine(op, Augmentor.prepareaffine(square)) @test parent(wv).itp.coefs === square - @test wv == flipdim(square,1) + @test wv == reverse(square; dims=1) @test typeof(wv) <: InvWarpedView{eltype(square),2} end let op = @inferred Either((Rotate90(),Rotate270()), (0,1)) @@ -400,6 +400,7 @@ end @test Augmentor.supports_permute(op) === false res1, res2 = @inferred(Augmentor.applyaffineview(op, Augmentor.prepareaffine.((N0f8.(square), square)))) @test res1 == res2 + @test typeof(res1) <: SubArray{N0f8,2,<:InvWarpedView} @test typeof(res2) <: SubArray{eltype(square),2,<:InvWarpedView} end @@ -487,7 +488,7 @@ end v = @inferred Augmentor.applylazy(op, rect) @test v === @inferred(Augmentor.applystepview(op, rect)) @test v === view(rect,1:1:2,3:-1:1) - @test v == flipdim(rect,2) + @test v == reverse(rect; dims=2) @test typeof(v) <: SubArray end let op = @inferred Either((FlipY(),FlipX()), (1,0)) @@ -502,7 +503,7 @@ end v = @inferred Augmentor.applylazy(op, rect) @test v === @inferred(Augmentor.applystepview(op, rect)) @test v === view(rect,2:-1:1,1:1:3) - @test v == flipdim(rect,1) + @test v == reverse(rect; dims=1) @test typeof(v) <: SubArray end let op = @inferred Either((Rotate180(),NoOp()), (1,0)) diff --git a/test/operations/tst_flip.jl b/test/operations/tst_flip.jl index 53ab8719..471ee915 100644 --- a/test/operations/tst_flip.jl +++ b/test/operations/tst_flip.jl @@ -9,7 +9,7 @@ @testset "eager" begin @test_throws MethodError Augmentor.applyeager(FlipX(), nothing) @test Augmentor.supports_eager(FlipX) === true - res1 = flipdim(rect, 2) + res1 = reverse(rect; dims=2) imgs = [ (rect, res1), (Augmentor.prepareaffine(rect), res1), @@ -40,15 +40,15 @@ @testset "single image" begin wv = @inferred Augmentor.applyaffine(FlipX(), Augmentor.prepareaffine(square)) @test parent(wv).itp.coefs === square - @test wv == flipdim(square,2) + @test wv == reverse(square; dims=2) @test typeof(wv) <: InvWarpedView{eltype(square),2} end @testset "multiple images" begin img_in = Augmentor.prepareaffine.((rgb_rect, square)) res1, res2 = @inferred(Augmentor.applyaffine(FlipX(), img_in)) # make sure affine map is specific to image - @test res1 == flipdim(rgb_rect, 2) - @test res2 == flipdim(square, 2) + @test res1 == reverse(rgb_rect; dims=2) + @test res2 == reverse(square; dims=2) @test typeof(res1) <: InvWarpedView{eltype(rgb_rect),2} @test typeof(res2) <: InvWarpedView{eltype(square),2} end @@ -60,25 +60,25 @@ @test typeof(wv) <: SubArray{eltype(square),2} @test typeof(parent(wv)) <: InvWarpedView @test parent(parent(wv)).itp.coefs === square - @test wv == flipdim(square,2) + @test wv == reverse(square; dims=2) end @testset "lazy" begin @test Augmentor.supports_lazy(FlipX) === true @testset "single image" begin v = @inferred Augmentor.applylazy(FlipX(), rect) @test v === view(rect, 1:1:2, 3:-1:1) - @test v == flipdim(rect,2) + @test v == reverse(rect; dims=2) @test typeof(v) <: SubArray wv = @inferred Augmentor.applylazy(FlipX(), Augmentor.prepareaffine(square)) @test parent(wv).itp.coefs === square - @test wv == flipdim(square,2) + @test wv == reverse(square; dims=2) @test typeof(wv) <: InvWarpedView{eltype(square),2} end @testset "multiple images" begin img_in = (rgb_rect, square) res1, res2 = @inferred(Augmentor.applylazy(FlipX(), img_in)) - @test res1 == flipdim(rgb_rect, 2) - @test res2 == flipdim(square, 2) + @test res1 == reverse(rgb_rect; dims=2) + @test res2 == reverse(square; dims=2) @test typeof(res1) <: SubArray{eltype(rgb_rect),2} @test typeof(res2) <: SubArray{eltype(square),2} end @@ -90,7 +90,7 @@ @test Augmentor.supports_stepview(FlipX) === true v = @inferred Augmentor.applylazy(FlipX(), rect) @test v === view(rect, 1:1:2, 3:-1:1) - @test v == flipdim(rect,2) + @test v == reverse(rect; dims=2) @test typeof(v) <: SubArray end @testset "permute" begin @@ -111,7 +111,7 @@ end @testset "eager" begin @test_throws MethodError Augmentor.applyeager(FlipY(), nothing) @test Augmentor.supports_eager(FlipY) === true - res1 = flipdim(rect, 1) + res1 = reverse(rect; dims=1) imgs = [ (rect, res1), (Augmentor.prepareaffine(rect), res1), @@ -142,15 +142,15 @@ end @testset "single image" begin wv = @inferred Augmentor.applyaffine(FlipY(), Augmentor.prepareaffine(square)) @test parent(wv).itp.coefs === square - @test wv == flipdim(square,1) + @test wv == reverse(square; dims=1) @test typeof(wv) <: InvWarpedView{eltype(square),2} end @testset "multiple images" begin img_in = Augmentor.prepareaffine.((rgb_rect, square)) res1, res2 = @inferred(Augmentor.applyaffine(FlipY(), img_in)) # make sure affine map is specific to image - @test res1 == flipdim(rgb_rect, 1) - @test res2 == flipdim(square, 1) + @test res1 == reverse(rgb_rect; dims=1) + @test res2 == reverse(square; dims=1) @test typeof(res1) <: InvWarpedView{eltype(rgb_rect),2} @test typeof(res2) <: InvWarpedView{eltype(square),2} end @@ -162,25 +162,25 @@ end @test typeof(wv) <: SubArray{eltype(square),2} @test typeof(parent(wv)) <: InvWarpedView @test parent(parent(wv)).itp.coefs === square - @test wv == flipdim(square,1) + @test wv == reverse(square; dims=1) end @testset "lazy" begin @test Augmentor.supports_lazy(FlipY) === true @testset "single image" begin v = @inferred Augmentor.applylazy(FlipY(), rect) @test v === view(rect, 2:-1:1, 1:1:3) - @test v == flipdim(rect,1) + @test v == reverse(rect; dims=1) @test typeof(v) <: SubArray wv = @inferred Augmentor.applylazy(FlipY(), Augmentor.prepareaffine(square)) @test parent(wv).itp.coefs === square - @test wv == flipdim(square,1) + @test wv == reverse(square; dims=1) @test typeof(wv) <: InvWarpedView{eltype(square),2} end @testset "multiple images" begin img_in = (rgb_rect, square) res1, res2 = @inferred(Augmentor.applylazy(FlipY(), img_in)) - @test res1 == flipdim(rgb_rect, 1) - @test res2 == flipdim(square, 1) + @test res1 == reverse(rgb_rect; dims=1) + @test res2 == reverse(square; dims=1) @test typeof(res1) <: SubArray{eltype(rgb_rect),2} @test typeof(res2) <: SubArray{eltype(square),2} end @@ -192,7 +192,7 @@ end @test Augmentor.supports_stepview(FlipY) === true v = @inferred Augmentor.applylazy(FlipY(), rect) @test v === view(rect, 2:-1:1, 1:1:3) - @test v == flipdim(rect,1) + @test v == reverse(rect; dims=1) @test typeof(v) <: SubArray end @testset "permute" begin diff --git a/test/operations/tst_mapfun.jl b/test/operations/tst_mapfun.jl index 94b48eab..751a6c13 100644 --- a/test/operations/tst_mapfun.jl +++ b/test/operations/tst_mapfun.jl @@ -54,7 +54,7 @@ res = @inferred(Augmentor.applylazy(MapFun(x->x-RGB(.1,.1,.1)), rgb_rect)) @test res == mappedarray(x->x-RGB(.1,.1,.1), rgb_rect) res = @inferred(Augmentor.applylazy(MapFun(x->x-RGB(.1,.1,.1)), OffsetArray(rgb_rect,-2,-1))) - @test axes(res) === Base.Slice.((-1:0, 0:2)) + @test axes(res) === OffsetArrays.IdentityUnitRange.((-1:0, 0:2)) @test @inferred(getindex(res,0,0)) isa RGB{Float64} @test res == mappedarray(x->x-RGB(.1,.1,.1), OffsetArray(rgb_rect,-2,-1)) @test typeof(res) <: MappedArrays.ReadonlyMappedArray{ColorTypes.RGB{Float64}} @@ -80,8 +80,9 @@ end @test_throws MethodError AggregateThenMapFun(x->x) @test typeof(@inferred(AggregateThenMapFun(x->x, x->x))) <: AggregateThenMapFun <: Augmentor.Operation @test typeof(@inferred(AggregateThenMapFun(mean, identity))) <: AggregateThenMapFun <: Augmentor.Operation - @test str_show(AggregateThenMapFun(mean, identity)) == "Augmentor.AggregateThenMapFun(Statistics.mean, identity)" - @test str_showconst(AggregateThenMapFun(mean, identity)) == "AggregateThenMapFun(Statistics.mean, identity)" + mean_str = VERSION >= v"1.2.0" ? "mean" : "Statistics.mean" + @test str_show(AggregateThenMapFun(mean, identity)) == "Augmentor.AggregateThenMapFun($mean_str, identity)" + @test str_showconst(AggregateThenMapFun(mean, identity)) == "AggregateThenMapFun($mean_str, identity)" @test str_showcompact(AggregateThenMapFun(mean, identity)) == "Map result of \"mean\" using \"identity\" over image" end @testset "eager" begin diff --git a/test/operations/tst_resize.jl b/test/operations/tst_resize.jl index f0d7c212..4f90a918 100644 --- a/test/operations/tst_resize.jl +++ b/test/operations/tst_resize.jl @@ -72,7 +72,7 @@ @test parent(parent(wv)).itp.coefs === square # round because `imresize` computes as float space, # while applyaffineview doesn't - @test round.(Float64.(wv),1) == round.(Float64.(imresize(square, h, w)),1) + @test round.(Float64.(wv); digits=1) == round.(Float64.(imresize(square, h, w)); digits=1) end for h in (1,2,3,4,5,9), w in (1,2,3,4,5,9) # bigger show drift wv = @inferred Augmentor.applyaffineview(Resize(h,w), Augmentor.prepareaffine(checkers)) diff --git a/test/operations/tst_scale.jl b/test/operations/tst_scale.jl index d8ae02b1..0f59ca7f 100644 --- a/test/operations/tst_scale.jl +++ b/test/operations/tst_scale.jl @@ -90,7 +90,7 @@ @test typeof(res2) <: OffsetArray{N0f8} res1, res2 = @inferred(Augmentor.applyeager(Scale(0.2), (img_in, N0f8.(img_in)))) @test parent(res1) ≈ parent(img_out2) - @test parent(res2) == parent(img_out2) + @test parent(res2) == parent(img_out2) @test typeof(res1) == typeof(img_out2) @test typeof(res2) <: OffsetArray{N0f8} end diff --git a/test/reference/scale_cropratio.txt b/test/reference/scale_cropratio.txt index 29fbdec2..e19f28c9 100644 --- a/test/reference/scale_cropratio.txt +++ b/test/reference/scale_cropratio.txt @@ -8,7 +8,7 @@ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index ca511fcb..567a22c3 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,4 +1,5 @@ -using ImageCore, ImageFiltering, ImageTransformations, CoordinateTransformations, Interpolations, OffsetArrays, StaticArrays, ColorTypes, FixedPointNumbers, TestImages, IdentityRanges, MappedArrays, ComputationalResources, MLDataPattern, ImageInTerminal, Statistics, ReferenceTests, Test +using ImageCore, ImageFiltering, ImageTransformations, CoordinateTransformations, Interpolations, OffsetArrays, StaticArrays, ColorTypes, TestImages, IdentityRanges, MappedArrays, ComputationalResources, MLDataPattern, ImageInTerminal, Statistics +using ReferenceTests, Test # check for ambiguities refambs = detect_ambiguities(ImageTransformations, Base, Core) @@ -23,6 +24,7 @@ checkers = Gray{N0f8}[1 0 1 0 1; 0 1 0 1 0; 1 0 1 0 1] rgb_rect = rand(RGB{N0f8}, 2, 3) tests = [ + "tst_compat.jl", "tst_utils.jl", "operations/tst_channels.jl", "operations/tst_dims.jl", diff --git a/test/tst_augment.jl b/test/tst_augment.jl index bf284c72..32d25f51 100644 --- a/test/tst_augment.jl +++ b/test/tst_augment.jl @@ -90,14 +90,14 @@ ops = (Resize(2,2),Rotate90()) # forces affine @test typeof(wv.indices) <: Tuple{Vararg{IdentityRange}} @test typeof(parent(wv)) <: InvWarpedView @test parent(parent(wv)).itp.coefs === rect - @test round.(Float64.(wv),1) == round.(Float64.(rotl90(imresize(rect,2,2))),1) + @test round.(Float64.(wv); digits=1) == round.(Float64.(rotl90(imresize(rect,2,2))); digits=1) end ops = (Resize(2,2),Rotate90(),CacheImage()) # forces affine then eager @testset "$(str_showcompact(ops))" begin img = @inferred Augmentor._augment(rect, ops) @test typeof(img) <: OffsetArray - @test round.(Float64.(img),1) == round.(Float64.(rotl90(imresize(rect,2,2))),1) + @test round.(Float64.(img); digits=1) == round.(Float64.(rotl90(imresize(rect,2,2))); digits=1) end buf = rand(Gray{N0f8}, 2, 2) @@ -105,20 +105,22 @@ ops = (Resize(2,2),Rotate90(),CacheImage(buf)) # forces affine then eager @testset "$(str_showcompact(ops))" begin img = @inferred Augmentor._augment(rect, ops) @test typeof(img) <: OffsetArray - @test round.(Float64.(img),1) == round.(Float64.(rotl90(imresize(rect,2,2))),1) + @test round.(Float64.(img); digits=1) == round.(Float64.(rotl90(imresize(rect,2,2))); digits=1) @test img == ops[3].buffer @test parent(img) === ops[3].buffer end ops = (Rotate180(),Crop(5:200,200:500),Rotate90(1),Crop(1:250, 1:150)) @testset "$(str_showcompact(ops))" begin - wv = @inferred Augmentor._augment(camera, ops) + # wv = @inferred Augmentor._augment(camera, ops) # TODO: update1.0 + wv = Augmentor._augment(camera, ops) @test typeof(wv) <: SubArray @test typeof(wv.indices) <: Tuple{Vararg{IdentityRange}} @test typeof(parent(wv)) <: InvWarpedView @test parent(parent(wv)).itp.coefs === camera @test_reference "reference/rot_crop_either_crop.txt" wv - img = @inferred augment(camera, ops) + # img = @inferred augment(camera, ops) # TODO: update1.0 + img = augment(camera, ops) @test img == parent(copy(wv)) @test typeof(img) <: Array @test eltype(img) <: eltype(camera) @@ -148,14 +150,16 @@ end ops = (Rotate180(),Crop(5:200,200:500),Rotate90(),Crop(50:300, 50:195),Resize(25,15)) @testset "$(str_showcompact(ops))" begin - wv = @inferred Augmentor._augment(camera, ops) + # wv = @inferred Augmentor._augment(camera, ops) # TODO: update1.0 + wv = Augmentor._augment(camera, ops) @test typeof(wv) <: SubArray @test eltype(wv) <: eltype(camera) @test typeof(wv.indices) <: Tuple{Vararg{IdentityRange}} @test typeof(parent(wv)) <: InvWarpedView @test parent(parent(wv)).itp.coefs === camera @test_reference "reference/rot_crop_rot_crop_resize.txt" wv - img = @inferred Augmentor.augment(camera, ops) + # img = @inferred Augmentor.augment(camera, ops) # TODO: update1.0 + img = Augmentor.augment(camera, ops) @test img == parent(copy(wv)) @test typeof(img) <: Array @test eltype(img) <: eltype(camera) diff --git a/test/tst_compat.jl b/test/tst_compat.jl new file mode 100644 index 00000000..f17f7983 --- /dev/null +++ b/test/tst_compat.jl @@ -0,0 +1,16 @@ +# TODO: remove this testset when we dicide to drop 1.0 compatibility +if VERSION < v"1.3" + @testset "safe_rand" begin + mutex = Augmentor.rand_mutex[] + typeof(mutex) <: Threads.Mutex + # check that its not a null pointer + @test reinterpret(Int, mutex.handle) > 0 + + 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 +end \ No newline at end of file diff --git a/test/tst_distortedview.jl b/test/tst_distortedview.jl index a871d08a..ea99362e 100644 --- a/test/tst_distortedview.jl +++ b/test/tst_distortedview.jl @@ -116,16 +116,16 @@ end @test parent(dv) === camera @test size(dv) == size(camera) @test eltype(dv) == eltype(camera) - @test summary(dv) == "512×512 Augmentor.DistortedView(::Array{Gray{N0f8},2}, ::Array{Float64,3} as 3×3 vector field) with element type ColorTypes.Gray{FixedPointNumbers.Normed{UInt8,8}}" + @test summary(dv) == "512×512 Augmentor.DistortedView(::Array{Gray{N0f8},2}, ::Array{Float64,3} as 3×3 vector field) with eltype Gray{Normed{UInt8,8}}" @test_reference "reference/distort_static.txt" dv camerao = OffsetArray(camera, (-5,-10)) dv2 = @inferred Augmentor.DistortedView(camerao, A) @test size(dv2) == size(camera) @test eltype(dv2) == eltype(camera) - @test summary(dv2) == "512×512 Augmentor.DistortedView(::OffsetArray{Gray{N0f8},2}, ::Array{Float64,3} as 3×3 vector field) with element type ColorTypes.Gray{FixedPointNumbers.Normed{UInt8,8}}" + @test summary(dv2) == "512×512 Augmentor.DistortedView(::Array{Gray{N0f8},2}, ::Array{Float64,3} as 3×3 vector field) with eltype Gray{Normed{UInt8,8}}" @test_reference "reference/distort_static.txt" dv2 v = view(Augmentor.DistortedView(rand(10,10), A), 2:8, 3:10) - @test summary(v) == "7×8 view(Augmentor.DistortedView(::Array{Float64,2}, ::Array{Float64,3} as 3×3 vector field), 2:8, 3:10) with element type Float64" + @test summary(v) == "7×8 view(Augmentor.DistortedView(::Array{Float64,2}, ::Array{Float64,3} as 3×3 vector field), 2:8, 3:10) with eltype Float64" end diff --git a/test/tst_utils.jl b/test/tst_utils.jl index 4cd150ea..d0c679b0 100644 --- a/test/tst_utils.jl +++ b/test/tst_utils.jl @@ -7,69 +7,53 @@ @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 "maybe_copy" begin +@testset "contiguous" begin A = [1 2 3; 4 5 6; 7 8 9] Ao = OffsetArray(A, (-2,-1)) - @test @inferred(Augmentor.maybe_copy(A)) === A - @test @inferred(Augmentor.maybe_copy(Ao)) === Ao + @test @inferred(Augmentor.contiguous(A)) === A + @test @inferred(Augmentor.contiguous(Ao)) === Ao let Ast = @SMatrix [1 2 3; 4 5 6; 7 8 9] - @test @inferred(Augmentor.maybe_copy(Ast)) === Ast + @test @inferred(Augmentor.contiguous(Ast)) === Ast end let v = view(A, 2:3, 1:2) - @test @inferred(Augmentor.maybe_copy(v)) == A[2:3, 1:2] - @test typeof(Augmentor.maybe_copy(v)) <: Array + @test @inferred(Augmentor.contiguous(v)) == A[2:3, 1:2] + @test typeof(Augmentor.contiguous(v)) <: Array end let v = view(OffsetArray(A, (-2,-1)), 0:1, 0:1) - @test @inferred(Augmentor.maybe_copy(v)) == A[2:3, 1:2] - @test typeof(Augmentor.maybe_copy(v)) <: Array + @test @inferred(Augmentor.contiguous(v)) == A[2:3, 1:2] + @test typeof(Augmentor.contiguous(v)) <: Array end let v = view(A, IdentityRange(2:3), IdentityRange(1:2)) - @test @inferred(Augmentor.maybe_copy(v)) == OffsetArray(A[2:3,1:2], (1, 0)) - @test typeof(Augmentor.maybe_copy(v)) <: OffsetArray + @test @inferred(Augmentor.contiguous(v)) == OffsetArray(A[2:3,1:2], (1, 0)) + @test typeof(Augmentor.contiguous(v)) <: OffsetArray end let v = channelview(rect) - @test @inferred(Augmentor.maybe_copy(v)) == channelview(rect) - @test typeof(Augmentor.maybe_copy(v)) <: Array + @test @inferred(Augmentor.contiguous(v)) == channelview(rect) + @test typeof(Augmentor.contiguous(v)) <: Array end let p = permuteddimsview(A, (2,1)) - @test @inferred(Augmentor.maybe_copy(p)) == A' - @test typeof(Augmentor.maybe_copy(p)) <: Array + @test @inferred(Augmentor.contiguous(p)) == A' + @test typeof(Augmentor.contiguous(p)) <: Array end let p = permuteddimsview(Ao, (2,1)) - @test @inferred(Augmentor.maybe_copy(p)) == Ao' - @test typeof(Augmentor.maybe_copy(p)) <: OffsetArray + @test @inferred(Augmentor.contiguous(p)) == Ao' + @test typeof(Augmentor.contiguous(p)) <: OffsetArray end let p = view(permuteddimsview(A, (2,1)), IdentityRange(2:3), IdentityRange(1:2)) - @test @inferred(Augmentor.maybe_copy(p)) == OffsetArray(A'[2:3, 1:2],1,0) - @test typeof(Augmentor.maybe_copy(p)) <: OffsetArray + @test @inferred(Augmentor.contiguous(p)) == OffsetArray(A'[2:3, 1:2],1,0) + @test typeof(Augmentor.contiguous(p)) <: OffsetArray end let Aa = Augmentor.prepareaffine(A) - @test @inferred(Augmentor.maybe_copy(Aa)) == OffsetArray(A, (0,0)) - @test typeof(Augmentor.maybe_copy(Aa)) <: OffsetArray + @test @inferred(Augmentor.contiguous(Aa)) == OffsetArray(A, (0,0)) + @test typeof(Augmentor.contiguous(Aa)) <: OffsetArray end let Ar = reshape(view(A,:,:),1,3,3) - @test @inferred(Augmentor.maybe_copy(Ar)) == reshape(A,1,3,3) - @test typeof(Augmentor.maybe_copy(Ar)) <: Array{Int,3} + @test @inferred(Augmentor.contiguous(Ar)) == reshape(A,1,3,3) + @test typeof(Augmentor.contiguous(Ar)) <: Array{Int,3} end let Ar = reshape(view(Ao,:,:),1,3,3) - @test @inferred(Augmentor.maybe_copy(Ar)) == reshape(A,1,3,3) - @test typeof(Augmentor.maybe_copy(Ar)) <: Array{Int,3} + @test @inferred(Augmentor.contiguous(Ar)) == reshape(A,1,3,3) + @test typeof(Augmentor.contiguous(Ar)) <: Array{Int,3} end end @@ -157,7 +141,7 @@ end A = [1 2 3; 4 5 6; 7 8 9] @test @inferred(Augmentor.match_idx(A, axes(A))) === A let img = @inferred Augmentor.match_idx(A, (2:4, 2:4)) - @test axes(img) === Base.Slice.((2:4, 2:4)) + @test axes(img) === OffsetArrays.IdentityUnitRange.((2:4, 2:4)) @test typeof(img) <: OffsetArray end let B = view(A,1:3,1:3) @@ -165,11 +149,11 @@ end end let B = view(A,1:3,1:3) img = @inferred(Augmentor.match_idx(B, B.indices)) - @test axes(img) === Base.Slice.((1:3, 1:3)) + @test axes(img) === OffsetArrays.IdentityUnitRange.((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 axes(img) === Base.Slice.((2:4, 2:4)) + @test axes(img) === OffsetArrays.IdentityUnitRange.((2:4, 2:4)) @test typeof(img) <: OffsetArray end let C = Augmentor.prepareaffine(A)