From f5b640fba61d09ee73c814c3bf655128b2c47b0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Baru=C4=8Di=C4=87?= Date: Sat, 10 Jul 2021 20:21:37 +0200 Subject: [PATCH 01/14] GaussianBlur and AdjustContrastBrightness ops. --- Project.toml | 1 + src/Augmentor.jl | 5 ++ src/operations/blur.jl | 38 +++++++++++++ src/operations/brightness.jl | 101 +++++++++++++++++++++++++++++++++++ 4 files changed, 145 insertions(+) create mode 100644 src/operations/blur.jl create mode 100644 src/operations/brightness.jl diff --git a/Project.toml b/Project.toml index 5dd2e581..1254c1eb 100644 --- a/Project.toml +++ b/Project.toml @@ -18,6 +18,7 @@ MLDataPattern = "9920b226-0b2a-5f5f-9153-9aa70a013f8b" OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" Rotations = "6038ab10-8711-5258-84ad-4b1120ba62dc" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" +Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" [compat] ComputationalResources = "0.3" diff --git a/src/Augmentor.jl b/src/Augmentor.jl index c2974a5a..d7d4bd98 100644 --- a/src/Augmentor.jl +++ b/src/Augmentor.jl @@ -69,6 +69,9 @@ export ElasticDistortion, + AdjustContrastBrightness, + GaussianBlur, + augment, augment!, augmentbatch!, @@ -95,6 +98,8 @@ include("operations/resize.jl") include("operations/scale.jl") include("operations/zoom.jl") include("operations/either.jl") +include("operations/brightness.jl") +include("operations/blur.jl") include("distortionfields.jl") include("distortedview.jl") diff --git a/src/operations/blur.jl b/src/operations/blur.jl new file mode 100644 index 00000000..41c5e941 --- /dev/null +++ b/src/operations/blur.jl @@ -0,0 +1,38 @@ +import ImageFiltering: imfilter, KernelFactors.gaussian + +struct GaussianBlur{K <: AbstractVector, S <: AbstractVector} <: ImageOperation + k::K + σ::S + + function GaussianBlur(k::K, σ::S) where {K <: AbstractVector{<:Integer}, + S <: AbstractVector{<:Real}} + minimum(k) > 0 || throw(ArgumentError("Kernel size must be positive: $(k)")) + minimum(σ) > 0 || throw(ArgumentError("σ must be positive: $(σ)")) + new{K, S}(k, σ) + end +end + +GaussianBlur(k, σ) = GaussianBlur(vectorize(k), vectorize(σ)) + +randparam(op::GaussianBlur, img) = (safe_rand(op.k), safe_rand(op.σ)) + +@inline supports_eager(::GaussianBlur) = true + +function applyeager(op::GaussianBlur, img::AbstractArray, (k, σ)) + kernel = gaussian((σ, σ), (k, k)) + return imfilter(img, kernel) +end + +function showconstruction(io::IO, op::GaussianBlur) + print(io, typeof(op).name.name, '(', op.k, ", ", op.σ,')') +end + +function Base.show(io::IO, op::GaussianBlur) + if get(io, :compact, false) + print(io, "GaussianBlur with k=$(op.k) and σ=$(op.σ)") + else + print(io, "Augmentor.") + showconstruction(io, op) + end +end + diff --git a/src/operations/brightness.jl b/src/operations/brightness.jl new file mode 100644 index 00000000..0e3b36c1 --- /dev/null +++ b/src/operations/brightness.jl @@ -0,0 +1,101 @@ +import ImageCore: clamp01 +import Statistics: mean + +""" + AdjustContrastBrightness <: ImageOperation + +Description +-------------- + +Adjusts the brightness and contrast of each pixel. + +Usage +-------------- + + AdjustContrastBrightness(α, β) + +Arguments +-------------- + +- **`α`** : `Real` or `AbstractVector` of `Real` that denote + the coefficient(s) for contrast adjustment. +- **`β`** : `Real` or `AbstractVector` of `Real` that denote + the coefficient(s) for brightness adjustment. + +Examples +-------------- + +``` +using Augmentor +img = testpattern() + +# use exactly 1.2 for contrast, and one of 0.5 and 0.8 for brightness +augment(img, AdjustContrastBrightness(1.2, [0.5, 0.8])) + +# pick the coefficients randomly from the specified ranges +augment(img, AdjustContrastBrightness(0.8:0.1:2.0, 0.5:0.1:1.1)) +``` +""" +struct AdjustContrastBrightness{T<:AbstractVector} <: ImageOperation + α::T + β::T + + function AdjustContrastBrightness(α::T, β::T) where {T<:AbstractVector{<:Real}} + length(α) > 0 || throw(ArgumentError("Range $(α) is empty")) + length(β) > 0 || throw(ArgumentError("Range $(β) is empty")) + new{T}(α, β) + end +end + +AdjustContrastBrightness(α, β) = AdjustContrastBrightness(vectorize(α), + vectorize(β)) + +randparam(op::AdjustContrastBrightness, img) = (safe_rand(op.α), safe_rand(op.β)) + +@inline supports_eager(::AdjustContrastBrightness) = true +@inline supports_lazy(::AdjustContrastBrightness) = true + +function applyeager(op::AdjustContrastBrightness, img::AbstractArray, (α, β)) + M = _get_M(op, img) + return _map_pix.(α, β, M, img) +end + +function applylazy(op::AdjustContrastBrightness, img::AbstractArray, (α, β)) + M = _get_M(op, img) + return AdjustedView(img, α, β, M) +end + +function showconstruction(io::IO, op::AdjustContrastBrightness) + print(io, typeof(op).name.name, '(', op.α, ", ", op.β,')') +end + +function Base.show(io::IO, op::AdjustContrastBrightness) + if get(io, :compact, false) + print(io, "Adjust contrast & brightness with coffecients from $(op.α) and $(op.β), respectively") + else + print(io, "Augmentor.") + showconstruction(io, op) + end +end + +_map_pix(α, β, M, pix) = clamp01(α * pix + β * M) +_get_M(op::AdjustContrastBrightness, img) = mean(img) + +# This wraps an image so that its pixels appear as after the contrast and +# brightness adjustment. This is done in the `getindex` method. +struct AdjustedView{T, P<:AbstractMatrix{T}, R} <: AbstractArray{T, 2} + orig::P + α::R + β::R + M::T + + function AdjustedView(img::P, α::R, β::R, M) where {P <: AbstractMatrix, + R <: Real} + new{eltype(P), P, R}(img, α, β, M) + end +end + +Base.parent(A::AdjustedView) = A.parent +Base.size(A::AdjustedView) = size(A.orig) +Base.axes(A::AdjustedView) = axes(A.orig) +Base.getindex(A::AdjustedView, i, j) = _map_pix(A.α, A.β, A.M, A.orig[i, j]) From 9c5412a8041b5c65c89f18bbe564a13507feffb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Baru=C4=8Di=C4=87?= Date: Sun, 11 Jul 2021 16:36:56 +0200 Subject: [PATCH 02/14] Add tests for GaussianBlur --- test/operations/tst_blur.jl | 36 ++++++++++++++++++++++++++++++++++++ test/runtests.jl | 1 + 2 files changed, 37 insertions(+) create mode 100644 test/operations/tst_blur.jl diff --git a/test/operations/tst_blur.jl b/test/operations/tst_blur.jl new file mode 100644 index 00000000..30455c0e --- /dev/null +++ b/test/operations/tst_blur.jl @@ -0,0 +1,36 @@ +import ImageFiltering: imfilter, KernelFactors.gaussian + +@testset "GaussianBlur" begin + @testset "constructor" begin + @test_throws ArgumentError GaussianBlur(0, 1) + @test_throws ArgumentError GaussianBlur(3, 0) + @test_throws ArgumentError GaussianBlur([1, 3], 0) + @test_throws ArgumentError GaussianBlur([1, 0], 1) + @test_throws ArgumentError GaussianBlur(3, -1:1) + end + @testset "randparam" begin + img = testpattern() + ks = [3, 1:2:5, [1, 3, 5]] + σs = [1, 1:0.1:2, [1, 2]] + for k in ks, σ in σs + op = GaussianBlur(k, σ) + p = @inferred Augmentor.randparam(op, img) + @test p[1] in k + @test p[2] in σ + end + end + @testset "eager" begin + @test Augmentor.supports_eager(GaussianBlur) + + k = 3 + σ = 3.2 + + imgs = [testpattern(), camera] + + for img in imgs + ref = imfilter(img, gaussian((σ, σ), (k, k))) + res = Augmentor.applyeager(GaussianBlur(k, σ), img) + @test ref == res + end + end +end diff --git a/test/runtests.jl b/test/runtests.jl index 6636bbb7..f9481d5c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -52,6 +52,7 @@ tests = [ "operations/tst_zoom.jl", "operations/tst_distortions.jl", "operations/tst_either.jl", + "operations/tst_blur.jl", "tst_operations.jl", "tst_pipeline.jl", "tst_augment.jl", From 604d15c34fa9aac28e930fbf05079e4c2000d43c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Baru=C4=8Di=C4=87?= Date: Sun, 11 Jul 2021 16:55:20 +0200 Subject: [PATCH 03/14] Docs and additional constructor for GaussianBlur --- src/operations/blur.jl | 37 +++++++++++++++++++++++++++++++++++++ test/operations/tst_blur.jl | 2 +- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/operations/blur.jl b/src/operations/blur.jl index 41c5e941..f9865cdf 100644 --- a/src/operations/blur.jl +++ b/src/operations/blur.jl @@ -1,5 +1,41 @@ import ImageFiltering: imfilter, KernelFactors.gaussian +""" + GaussianBlur <: ImageOperation + +Description +-------------- + +Blurs an image using a Gaussian filter. + +Usage +-------------- + + GaussianBlur(k, [σ]) + +Arguments +-------------- + +- **`k`** : `Integer` or `AbstractVector` of `Integer` that denote + the kernel size. It must be an odd positive number. +- **`σ`** : Optional. `Real` or `AbstractVector` of `Real` that denote the + standard deviation. It must be a positive number. + Defaults to `0.3 * ((k - 1) / 2 - 1) + 0.8`. + +Examples +-------------- + +``` +using Augmentor +img = testpattern() + +# use exactly k=3 and σ=1.0 +augment(img, GaussianBlur(3, 1.0)) + +# pick k and σ randomly from the specified ranges +augment(img, GaussianBlur(3:2:7, 1.0:0.1:2.0)) +``` +""" struct GaussianBlur{K <: AbstractVector, S <: AbstractVector} <: ImageOperation k::K σ::S @@ -13,6 +49,7 @@ struct GaussianBlur{K <: AbstractVector, S <: AbstractVector} <: ImageOperation end GaussianBlur(k, σ) = GaussianBlur(vectorize(k), vectorize(σ)) +GaussianBlur(k) = GaussianBlur(k, 0.3 * ((k - 1) / 2 - 1) + 0.8) randparam(op::GaussianBlur, img) = (safe_rand(op.k), safe_rand(op.σ)) diff --git a/test/operations/tst_blur.jl b/test/operations/tst_blur.jl index 30455c0e..6937462c 100644 --- a/test/operations/tst_blur.jl +++ b/test/operations/tst_blur.jl @@ -2,7 +2,7 @@ import ImageFiltering: imfilter, KernelFactors.gaussian @testset "GaussianBlur" begin @testset "constructor" begin - @test_throws ArgumentError GaussianBlur(0, 1) + @test_throws ArgumentError GaussianBlur(0) @test_throws ArgumentError GaussianBlur(3, 0) @test_throws ArgumentError GaussianBlur([1, 3], 0) @test_throws ArgumentError GaussianBlur([1, 0], 1) From 371316ccbb98f5092da4b895e187e426dfb13a10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Baru=C4=8Di=C4=87?= Date: Mon, 12 Jul 2021 14:37:07 +0200 Subject: [PATCH 04/14] Rename `AdjustContrastBrightness` to `ColorJitter` --- src/Augmentor.jl | 4 +- src/operations/brightness.jl | 101 -------------------------------- src/operations/color.jl | 108 +++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 103 deletions(-) delete mode 100644 src/operations/brightness.jl create mode 100644 src/operations/color.jl diff --git a/src/Augmentor.jl b/src/Augmentor.jl index d7d4bd98..80813cf9 100644 --- a/src/Augmentor.jl +++ b/src/Augmentor.jl @@ -69,7 +69,7 @@ export ElasticDistortion, - AdjustContrastBrightness, + ColorJitter, GaussianBlur, augment, @@ -98,7 +98,7 @@ include("operations/resize.jl") include("operations/scale.jl") include("operations/zoom.jl") include("operations/either.jl") -include("operations/brightness.jl") +include("operations/color.jl") include("operations/blur.jl") include("distortionfields.jl") diff --git a/src/operations/brightness.jl b/src/operations/brightness.jl deleted file mode 100644 index 0e3b36c1..00000000 --- a/src/operations/brightness.jl +++ /dev/null @@ -1,101 +0,0 @@ -import ImageCore: clamp01 -import Statistics: mean - -""" - AdjustContrastBrightness <: ImageOperation - -Description --------------- - -Adjusts the brightness and contrast of each pixel. - -Usage --------------- - - AdjustContrastBrightness(α, β) - -Arguments --------------- - -- **`α`** : `Real` or `AbstractVector` of `Real` that denote - the coefficient(s) for contrast adjustment. -- **`β`** : `Real` or `AbstractVector` of `Real` that denote - the coefficient(s) for brightness adjustment. - -Examples --------------- - -``` -using Augmentor -img = testpattern() - -# use exactly 1.2 for contrast, and one of 0.5 and 0.8 for brightness -augment(img, AdjustContrastBrightness(1.2, [0.5, 0.8])) - -# pick the coefficients randomly from the specified ranges -augment(img, AdjustContrastBrightness(0.8:0.1:2.0, 0.5:0.1:1.1)) -``` -""" -struct AdjustContrastBrightness{T<:AbstractVector} <: ImageOperation - α::T - β::T - - function AdjustContrastBrightness(α::T, β::T) where {T<:AbstractVector{<:Real}} - length(α) > 0 || throw(ArgumentError("Range $(α) is empty")) - length(β) > 0 || throw(ArgumentError("Range $(β) is empty")) - new{T}(α, β) - end -end - -AdjustContrastBrightness(α, β) = AdjustContrastBrightness(vectorize(α), - vectorize(β)) - -randparam(op::AdjustContrastBrightness, img) = (safe_rand(op.α), safe_rand(op.β)) - -@inline supports_eager(::AdjustContrastBrightness) = true -@inline supports_lazy(::AdjustContrastBrightness) = true - -function applyeager(op::AdjustContrastBrightness, img::AbstractArray, (α, β)) - M = _get_M(op, img) - return _map_pix.(α, β, M, img) -end - -function applylazy(op::AdjustContrastBrightness, img::AbstractArray, (α, β)) - M = _get_M(op, img) - return AdjustedView(img, α, β, M) -end - -function showconstruction(io::IO, op::AdjustContrastBrightness) - print(io, typeof(op).name.name, '(', op.α, ", ", op.β,')') -end - -function Base.show(io::IO, op::AdjustContrastBrightness) - if get(io, :compact, false) - print(io, "Adjust contrast & brightness with coffecients from $(op.α) and $(op.β), respectively") - else - print(io, "Augmentor.") - showconstruction(io, op) - end -end - -_map_pix(α, β, M, pix) = clamp01(α * pix + β * M) -_get_M(op::AdjustContrastBrightness, img) = mean(img) - -# This wraps an image so that its pixels appear as after the contrast and -# brightness adjustment. This is done in the `getindex` method. -struct AdjustedView{T, P<:AbstractMatrix{T}, R} <: AbstractArray{T, 2} - orig::P - α::R - β::R - M::T - - function AdjustedView(img::P, α::R, β::R, M) where {P <: AbstractMatrix, - R <: Real} - new{eltype(P), P, R}(img, α, β, M) - end -end - -Base.parent(A::AdjustedView) = A.parent -Base.size(A::AdjustedView) = size(A.orig) -Base.axes(A::AdjustedView) = axes(A.orig) -Base.getindex(A::AdjustedView, i, j) = _map_pix(A.α, A.β, A.M, A.orig[i, j]) diff --git a/src/operations/color.jl b/src/operations/color.jl new file mode 100644 index 00000000..1729de1a --- /dev/null +++ b/src/operations/color.jl @@ -0,0 +1,108 @@ +import ImageCore: clamp01, gamutmax +import Statistics: mean + +""" + ColorJitter <: ImageOperation + +Description +-------------- + +Adjusts the brightness and contrast of an image according to the formula +`α * image[i] + β * M`, where `M` is either `mean(image)` or the maximum +intensity value. + +Usage +-------------- + + ColorJitter(α, β; [usemax]) + +Arguments +-------------- + +- **`α`** : `Real` or `AbstractVector` of `Real` that denote the coefficient(s) + for contrast adjustment. +- **`β`** : `Real` or `AbstractVector` of `Real` that denote the coefficient(s) + for brightness adjustment. +- **`usemax::Bool`**: Optional. If `true`, the brightness will be adjusted by + the maximum intensity value; otherwise, the image mean will be used. + Defaults to `true`. + +Examples +-------------- + +``` +using Augmentor +img = testpattern() + +# use exactly 1.2 for contrast, and one of 0.5 and 0.8 for brightness +augment(img, ColorJitter(1.2, [0.5, 0.8])) + +# pick the coefficients randomly from the specified ranges +augment(img, ColorJitter(0.8:0.1:2.0, 0.5:0.1:1.1)) +``` +""" +struct ColorJitter{A<:AbstractVector, B<:AbstractVector} <: ImageOperation + α::A + β::B + usemax::Bool + + function ColorJitter(α::A, β::B, usemax) where {A<:AbstractVector{<:Real}, + B<:AbstractVector{<:Real}} + length(α) > 0 || throw(ArgumentError("Range $(α) is empty")) + length(β) > 0 || throw(ArgumentError("Range $(β) is empty")) + new{A, B}(α, β, usemax) + end +end + +ColorJitter(α, β; usemax=true) = ColorJitter(vectorize(α), vectorize(β), usemax) + +randparam(op::ColorJitter, img) = (safe_rand(op.α), safe_rand(op.β)) + +@inline supports_eager(::Type{<:ColorJitter}) = true +@inline supports_lazy(::Type{<:ColorJitter}) = true + +function applyeager(op::ColorJitter, img::AbstractArray, (α, β)) + M = _get_M(op, img) + return _map_pix.(α, β, M, img) +end + +function applylazy(op::ColorJitter, img::AbstractArray, (α, β)) + M = _get_M(op, img) + return ColorJitterView(img, α, β, M) +end + +function showconstruction(io::IO, op::ColorJitter) + print(io, typeof(op).name.name, '(', op.α, ", ", op.β, ", ", op.usemax, ')') +end + +function Base.show(io::IO, op::ColorJitter) + if get(io, :compact, false) + maxmsg = op.usemax ? "max. intensity" : "mean value"; + print(io, "Color jitter with coffecients α=$(op.α) and β=$(op.β) (w.r.t. $(maxmsg))") + else + print(io, "Augmentor.") + showconstruction(io, op) + end +end + +_map_pix(α, β, M, pix) = clamp01(α * pix + β * M) +_get_M(op::ColorJitter, img) = op.usemax ? gamutmax(eltype(img)) : mean(img) + +# This wraps an image so that its pixels appear as after the contrast and +# brightness adjustment. This is done in the `getindex` method. +struct ColorJitterView{T, P<:AbstractMatrix{T}, R} <: AbstractArray{T, 2} + orig::P + α::R + β::R + M::T + + function ColorJitterView(img::P, α::R, β::R, M) where {P <: AbstractMatrix, + R <: Real} + new{eltype(P), P, R}(img, α, β, M) + end +end + +Base.parent(A::ColorJitterView) = A.parent +Base.size(A::ColorJitterView) = size(A.orig) +Base.axes(A::ColorJitterView) = axes(A.orig) +Base.getindex(A::ColorJitterView, i, j) = _map_pix(A.α, A.β, A.M, A.orig[i, j]) From 73a50374dc229705c2739f1397371c7add79251a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Baru=C4=8Di=C4=87?= Date: Mon, 12 Jul 2021 14:37:47 +0200 Subject: [PATCH 05/14] Add tests for `ColorJitter` --- test/operations/tst_color.jl | 59 ++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 test/operations/tst_color.jl diff --git a/test/operations/tst_color.jl b/test/operations/tst_color.jl new file mode 100644 index 00000000..b583814e --- /dev/null +++ b/test/operations/tst_color.jl @@ -0,0 +1,59 @@ +@testset "ColorJitter" begin + @testset "constructor" begin + @test_throws ArgumentError ColorJitter(1., 3.0:2.0) + @test_throws ArgumentError ColorJitter(5.0:3.0, 1.) + end + @testset "randparam" begin + img = testpattern() + αs = [3.0, 1.0:0.2:5.0, [1.0, 3.0, 5.0]] + βs = [1.0, 1.0:0.1:2.0, [1.0, 2.0]] + for α in αs, β in βs + op = ColorJitter(α, β) + p = @inferred Augmentor.randparam(op, img) + @test p[1] in α + @test p[2] in β + end + end + @testset "eager" begin + @test Augmentor.supports_eager(ColorJitter) + + α = 0.2 + β = 0.1 + + imgs = [testpattern(), camera] + + # M = mean value + for img in imgs + ref = clamp01.(α .* img .+ β * mean(img)) + res = Augmentor.applyeager(ColorJitter(α, β, usemax=false), img) + @test ref == res + end + # M = maximum value + for img in imgs + ref = clamp01.(α .* img .+ β * gamutmax(eltype(img))) + res = Augmentor.applyeager(ColorJitter(α, β), img) + @test ref == res + end + end + @testset "lazy" begin + @test Augmentor.supports_lazy(ColorJitter) + + α = 0.2 + β = 0.1 + + imgs = [testpattern(), camera] + + # M = mean value + for img in imgs + ref = clamp01.(α .* img .+ β * mean(img)) + res = Augmentor.applylazy(ColorJitter(α, β, usemax=false), img) + @test ref == res + end + # M = maximum value + for img in imgs + ref = clamp01.(α .* img .+ β * gamutmax(eltype(img))) + res = Augmentor.applylazy(ColorJitter(α, β), img) + @test ref == res + end + end +end From ad463097a8fda46302f6eb2b41c3484bf5085a17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Baru=C4=8Di=C4=87?= Date: Mon, 12 Jul 2021 14:42:10 +0200 Subject: [PATCH 06/14] Small changes --- src/operations/blur.jl | 2 +- test/operations/tst_blur.jl | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/operations/blur.jl b/src/operations/blur.jl index f9865cdf..7d3804f4 100644 --- a/src/operations/blur.jl +++ b/src/operations/blur.jl @@ -53,7 +53,7 @@ GaussianBlur(k) = GaussianBlur(k, 0.3 * ((k - 1) / 2 - 1) + 0.8) randparam(op::GaussianBlur, img) = (safe_rand(op.k), safe_rand(op.σ)) -@inline supports_eager(::GaussianBlur) = true +@inline supports_eager(::Type{<:GaussianBlur}) = true function applyeager(op::GaussianBlur, img::AbstractArray, (k, σ)) kernel = gaussian((σ, σ), (k, k)) diff --git a/test/operations/tst_blur.jl b/test/operations/tst_blur.jl index 6937462c..6f517279 100644 --- a/test/operations/tst_blur.jl +++ b/test/operations/tst_blur.jl @@ -1,5 +1,3 @@ -import ImageFiltering: imfilter, KernelFactors.gaussian - @testset "GaussianBlur" begin @testset "constructor" begin @test_throws ArgumentError GaussianBlur(0) @@ -28,7 +26,7 @@ import ImageFiltering: imfilter, KernelFactors.gaussian imgs = [testpattern(), camera] for img in imgs - ref = imfilter(img, gaussian((σ, σ), (k, k))) + ref = imfilter(img, KernelFactors.gaussian((σ, σ), (k, k))) res = Augmentor.applyeager(GaussianBlur(k, σ), img) @test ref == res end From 804763a9c77d490b86d58f40737e9b557d713882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Baru=C4=8Di=C4=87?= Date: Mon, 12 Jul 2021 16:40:47 +0200 Subject: [PATCH 07/14] ColorJitter and GaussianBlur cards in docs --- docs/operations/blur/config.json | 6 +++++ docs/operations/blur/gaussianblur.jl | 34 ++++++++++++++++++++++++++++ docs/operations/color/colorjitter.jl | 32 ++++++++++++++++++++++++++ docs/operations/color/config.json | 7 ++++++ docs/operations/config.json | 4 +++- 5 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 docs/operations/blur/config.json create mode 100644 docs/operations/blur/gaussianblur.jl create mode 100644 docs/operations/color/colorjitter.jl create mode 100644 docs/operations/color/config.json diff --git a/docs/operations/blur/config.json b/docs/operations/blur/config.json new file mode 100644 index 00000000..5d2444fa --- /dev/null +++ b/docs/operations/blur/config.json @@ -0,0 +1,6 @@ +{ + "title": "Blurring", + "order": [ + "gaussianblur.jl" + ] +} diff --git a/docs/operations/blur/gaussianblur.jl b/docs/operations/blur/gaussianblur.jl new file mode 100644 index 00000000..80c4299d --- /dev/null +++ b/docs/operations/blur/gaussianblur.jl @@ -0,0 +1,34 @@ +# --- +# title: GaussianBlur +# cover: gaussianblur.gif +# description: blur the input image using a gaussian kernel +# --- + +# [`GaussianBlur`](@ref) can be used to blur the input image using a gaussian +# kernel with a specified kernel size and standard deviation. + +using Augmentor +using ImageShow, ImageCore + +img_in = testpattern(RGB, ratio=0.5) + +mosaicview( + img_in, + augment(img_in, GaussianBlur(3)), + augment(img_in, GaussianBlur(5, 2.5)); + fillvalue=colorant"white", nrow=1, npad=10 +) + +# ## References + +#md # ```@docs +#md # GaussianBlur +#md # ``` + + +## save covers #src +using ImageMagick #src +using FileIO #src +include(joinpath("..", "assets", "utilities.jl")) #src +cover = make_gif(testpattern(RGB, ratio=0.5), GaussianBlur(5), 2) #src +ImageMagick.save("gaussianblur.gif", cover; fps=1) #src diff --git a/docs/operations/color/colorjitter.jl b/docs/operations/color/colorjitter.jl new file mode 100644 index 00000000..e8c938d5 --- /dev/null +++ b/docs/operations/color/colorjitter.jl @@ -0,0 +1,32 @@ +# --- +# title: ColorJitter +# cover: colorjitter.gif +# description: Adjust contrast and brightness of an image +# --- + +# [`ColorJitter`](@ref) can be used to adjust the contrast and brightness of an input image. + +using Augmentor +using ImageShow, ImageCore + +img_in = testpattern(RGB, ratio=0.5) + +mosaicview( + img_in, + augment(img_in, ColorJitter(1.2, 0.3)), + augment(img_in, ColorJitter(0.75, -0.2)); + fillvalue=colorant"white", nrow=1, npad=10 +) + +# ## References + +#md # ```@docs +#md # ColorJitter +#md # ``` + +## save covers #src +using ImageMagick #src +using FileIO #src +include(joinpath("..", "assets", "utilities.jl")) #src +cover = make_gif(testpattern(RGB, ratio=0.5), ColorJitter(), 4) #src +ImageMagick.save("colorjitter.gif", cover; fps=1) #src diff --git a/docs/operations/color/config.json b/docs/operations/color/config.json new file mode 100644 index 00000000..9705db1f --- /dev/null +++ b/docs/operations/color/config.json @@ -0,0 +1,7 @@ +{ + "title": "Color adjustments", + "order": [ + "colorjitter.jl" + ], + "description": "" +} diff --git a/docs/operations/config.json b/docs/operations/config.json index 3acad879..5413633f 100644 --- a/docs/operations/config.json +++ b/docs/operations/config.json @@ -2,8 +2,10 @@ "order": [ "affine", "distortions", + "color", + "blur", "size", "misc" ] } - \ No newline at end of file + From 389571dee419c22acc2db863bb87b2455feb4bc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Baru=C4=8Di=C4=87?= Date: Mon, 12 Jul 2021 16:42:22 +0200 Subject: [PATCH 08/14] Fix _get_M method for ColorJitter --- src/operations/color.jl | 15 ++++++++++++--- test/operations/tst_color.jl | 2 +- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/operations/color.jl b/src/operations/color.jl index 1729de1a..0206899b 100644 --- a/src/operations/color.jl +++ b/src/operations/color.jl @@ -14,15 +14,16 @@ intensity value. Usage -------------- + ColorJitter() ColorJitter(α, β; [usemax]) Arguments -------------- - **`α`** : `Real` or `AbstractVector` of `Real` that denote the coefficient(s) - for contrast adjustment. + for contrast adjustment. Defaults to `0.8:0.1:1.2`. - **`β`** : `Real` or `AbstractVector` of `Real` that denote the coefficient(s) - for brightness adjustment. + for brightness adjustment. Defaults to `-0.2:0.1:0.2`. - **`usemax::Bool`**: Optional. If `true`, the brightness will be adjusted by the maximum intensity value; otherwise, the image mean will be used. Defaults to `true`. @@ -54,6 +55,7 @@ struct ColorJitter{A<:AbstractVector, B<:AbstractVector} <: ImageOperation end end +ColorJitter() = ColorJitter(0.8:0.1:1.2, -0.2:0.1:0.2, true) ColorJitter(α, β; usemax=true) = ColorJitter(vectorize(α), vectorize(β), usemax) randparam(op::ColorJitter, img) = (safe_rand(op.α), safe_rand(op.β)) @@ -86,7 +88,14 @@ function Base.show(io::IO, op::ColorJitter) end _map_pix(α, β, M, pix) = clamp01(α * pix + β * M) -_get_M(op::ColorJitter, img) = op.usemax ? gamutmax(eltype(img)) : mean(img) +function _get_M(op::ColorJitter, img) + if op.usemax + T = eltype(img) + return T(gamutmax(eltype(img))...) + else + return mean(img) + end +end # This wraps an image so that its pixels appear as after the contrast and # brightness adjustment. This is done in the `getindex` method. diff --git a/test/operations/tst_color.jl b/test/operations/tst_color.jl index b583814e..3747e372 100644 --- a/test/operations/tst_color.jl +++ b/test/operations/tst_color.jl @@ -31,7 +31,7 @@ # M = maximum value for img in imgs ref = clamp01.(α .* img .+ β * gamutmax(eltype(img))) - res = Augmentor.applyeager(ColorJitter(α, β), img) + res = Augmentor.applyeager(ColorJitter(α, β, usemax=true), img) @test ref == res end end From f913c1c9ec603424b2c7847ac9c207fe7ef9268a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Baru=C4=8Di=C4=87?= Date: Mon, 12 Jul 2021 18:33:11 +0200 Subject: [PATCH 09/14] Test image equality via `assess_psnr` --- Project.toml | 3 ++- test/operations/tst_blur.jl | 3 ++- test/operations/tst_color.jl | 4 ++-- test/runtests.jl | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Project.toml b/Project.toml index 1254c1eb..59f1c863 100644 --- a/Project.toml +++ b/Project.toml @@ -38,6 +38,7 @@ julia = "1" [extras] ImageDistances = "51556ac3-7006-55f5-8cb3-34580c88182d" ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1" +ImageQualityIndexes = "2996bd0c-7a13-11e9-2da2-2f5ce47296a9" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" ReferenceTests = "324d217c-45ce-50fc-942e-d289b448e8cf" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" @@ -45,4 +46,4 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TestImages = "5e47fb64-e119-507b-a336-dd2b206d9990" [targets] -test = ["ImageDistances", "ImageMagick", "Random", "ReferenceTests", "Statistics", "TestImages", "Test"] +test = ["ImageDistances", "ImageMagick", "ImageQualityIndexes", "Random", "ReferenceTests", "Statistics", "TestImages", "Test"] diff --git a/test/operations/tst_blur.jl b/test/operations/tst_blur.jl index 6f517279..7adeb826 100644 --- a/test/operations/tst_blur.jl +++ b/test/operations/tst_blur.jl @@ -26,9 +26,10 @@ imgs = [testpattern(), camera] for img in imgs + img = convert.(RGB, img) ref = imfilter(img, KernelFactors.gaussian((σ, σ), (k, k))) res = Augmentor.applyeager(GaussianBlur(k, σ), img) - @test ref == res + @test assess_psnr(res, ref) > 25 end end end diff --git a/test/operations/tst_color.jl b/test/operations/tst_color.jl index 3747e372..c7e7306b 100644 --- a/test/operations/tst_color.jl +++ b/test/operations/tst_color.jl @@ -32,7 +32,7 @@ for img in imgs ref = clamp01.(α .* img .+ β * gamutmax(eltype(img))) res = Augmentor.applyeager(ColorJitter(α, β, usemax=true), img) - @test ref == res + @test assess_psnr(res, ref) > 25 end end @testset "lazy" begin @@ -53,7 +53,7 @@ for img in imgs ref = clamp01.(α .* img .+ β * gamutmax(eltype(img))) res = Augmentor.applylazy(ColorJitter(α, β), img) - @test ref == res + @test assess_psnr(res, ref) > 25 end end end diff --git a/test/runtests.jl b/test/runtests.jl index f9481d5c..73a50dca 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -4,7 +4,7 @@ using ImageTransformations, CoordinateTransformations, Interpolations using MLDataPattern using OffsetArrays, StaticArrays, IdentityRanges, ComputationalResources using ImageCore.MappedArrays -using ReferenceTests, Test, TestImages, ImageDistances, Statistics +using ReferenceTests, Test, TestImages, ImageDistances, ImageQualityIndexes, Statistics if isdefined(OffsetArrays, :IdOffsetRange) OffsetRange = OffsetArrays.IdOffsetRange From 2f162e16dbba3607035cb9d0d611c2d22ae09996 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Baru=C4=8Di=C4=87?= Date: Mon, 12 Jul 2021 18:33:49 +0200 Subject: [PATCH 10/14] Make `_get_M` type-stable --- src/operations/color.jl | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/operations/color.jl b/src/operations/color.jl index 0206899b..f79eda14 100644 --- a/src/operations/color.jl +++ b/src/operations/color.jl @@ -89,12 +89,9 @@ end _map_pix(α, β, M, pix) = clamp01(α * pix + β * M) function _get_M(op::ColorJitter, img) - if op.usemax - T = eltype(img) - return T(gamutmax(eltype(img))...) - else - return mean(img) - end + M = op.usemax ? gamutmax(eltype(img)) : mean(img) + T = eltype(img) + return T(M...) end # This wraps an image so that its pixels appear as after the contrast and From 64db769cfbb3d673937d4a9c6f272fb7809685dd Mon Sep 17 00:00:00 2001 From: Denis Barucic Date: Mon, 12 Jul 2021 20:14:48 +0200 Subject: [PATCH 11/14] Add @propagate_inbounds Co-authored-by: Johnny Chen --- src/operations/color.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/operations/color.jl b/src/operations/color.jl index f79eda14..e6af19f9 100644 --- a/src/operations/color.jl +++ b/src/operations/color.jl @@ -111,4 +111,4 @@ end Base.parent(A::ColorJitterView) = A.parent Base.size(A::ColorJitterView) = size(A.orig) Base.axes(A::ColorJitterView) = axes(A.orig) -Base.getindex(A::ColorJitterView, i, j) = _map_pix(A.α, A.β, A.M, A.orig[i, j]) +Base.@propagate_inbounds Base.getindex(A::ColorJitterView, i, j) = _map_pix(A.α, A.β, A.M, A.orig[i, j]) From a5f192ff9e709efaffcb6344c4dd304dac239d6f Mon Sep 17 00:00:00 2001 From: Denis Barucic Date: Mon, 12 Jul 2021 20:17:09 +0200 Subject: [PATCH 12/14] Update src/operations/blur.jl Co-authored-by: Johnny Chen --- src/operations/blur.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/operations/blur.jl b/src/operations/blur.jl index 7d3804f4..9a692aa2 100644 --- a/src/operations/blur.jl +++ b/src/operations/blur.jl @@ -56,7 +56,8 @@ randparam(op::GaussianBlur, img) = (safe_rand(op.k), safe_rand(op.σ)) @inline supports_eager(::Type{<:GaussianBlur}) = true function applyeager(op::GaussianBlur, img::AbstractArray, (k, σ)) - kernel = gaussian((σ, σ), (k, k)) + n = ndims(img) + kernel = gaussian(ntuple(_->σ, n), ntuple(_->k, n)) return imfilter(img, kernel) end @@ -72,4 +73,3 @@ function Base.show(io::IO, op::GaussianBlur) showconstruction(io, op) end end - From 94567fc74c08433e28aae7c75ce55ed0b71be390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Baru=C4=8Di=C4=87?= Date: Mon, 12 Jul 2021 20:13:56 +0200 Subject: [PATCH 13/14] Fix `_get_M` and add a test for it --- src/operations/color.jl | 8 ++++++-- test/operations/tst_color.jl | 28 +++++++++++++++++++++++----- test/runtests.jl | 1 + 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/operations/color.jl b/src/operations/color.jl index e6af19f9..319b7dc1 100644 --- a/src/operations/color.jl +++ b/src/operations/color.jl @@ -88,10 +88,14 @@ function Base.show(io::IO, op::ColorJitter) end _map_pix(α, β, M, pix) = clamp01(α * pix + β * M) + function _get_M(op::ColorJitter, img) - M = op.usemax ? gamutmax(eltype(img)) : mean(img) T = eltype(img) - return T(M...) + if op.usemax + return T(gamutmax(T)...) + else + return convert(T, mean(img)) + end end # This wraps an image so that its pixels appear as after the contrast and diff --git a/test/operations/tst_color.jl b/test/operations/tst_color.jl index c7e7306b..5e77b4c5 100644 --- a/test/operations/tst_color.jl +++ b/test/operations/tst_color.jl @@ -14,6 +14,18 @@ @test p[2] in β end end + @testset "_get_M" begin + imgs = [testpattern()]#, camera] + for img in imgs + T = eltype(img) + op = ColorJitter(1., 0., usemax=true) + M = @inferred Augmentor._get_M(op, img) + @test M == T(gamutmax(T)...) + op = ColorJitter(1., 0., usemax=false) + M = @inferred Augmentor._get_M(op, img) + @test M == convert(T, mean(img)) + end + end @testset "eager" begin @test Augmentor.supports_eager(ColorJitter) @@ -24,13 +36,16 @@ # M = mean value for img in imgs + img = convert.(color_type(eltype(img)), img) ref = clamp01.(α .* img .+ β * mean(img)) res = Augmentor.applyeager(ColorJitter(α, β, usemax=false), img) - @test ref == res + @test assess_psnr(res, ref) > 25 end # M = maximum value for img in imgs - ref = clamp01.(α .* img .+ β * gamutmax(eltype(img))) + img = convert.(color_type(eltype(img)), img) + T = eltype(img) + ref = clamp01.(α .* img .+ β * T(gamutmax(T)...)) res = Augmentor.applyeager(ColorJitter(α, β, usemax=true), img) @test assess_psnr(res, ref) > 25 end @@ -45,14 +60,17 @@ # M = mean value for img in imgs + img = convert.(color_type(eltype(img)), img) ref = clamp01.(α .* img .+ β * mean(img)) res = Augmentor.applylazy(ColorJitter(α, β, usemax=false), img) - @test ref == res + @test assess_psnr(res, ref) > 25 end # M = maximum value for img in imgs - ref = clamp01.(α .* img .+ β * gamutmax(eltype(img))) - res = Augmentor.applylazy(ColorJitter(α, β), img) + img = convert.(color_type(eltype(img)), img) + T = eltype(img) + ref = clamp01.(α .* img .+ β * T(gamutmax(T)...)) + res = Augmentor.applylazy(ColorJitter(α, β, usemax=true), img) @test assess_psnr(res, ref) > 25 end end diff --git a/test/runtests.jl b/test/runtests.jl index 73a50dca..05f80e1b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -53,6 +53,7 @@ tests = [ "operations/tst_distortions.jl", "operations/tst_either.jl", "operations/tst_blur.jl", + "operations/tst_color.jl", "tst_operations.jl", "tst_pipeline.jl", "tst_augment.jl", From dff96babe16b3157f17df35cf962ee4c1e0296df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Baru=C4=8Di=C4=87?= Date: Mon, 12 Jul 2021 20:20:49 +0200 Subject: [PATCH 14/14] Note about where the default values come from --- src/operations/blur.jl | 3 ++- src/operations/color.jl | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/operations/blur.jl b/src/operations/blur.jl index 9a692aa2..04df9b74 100644 --- a/src/operations/blur.jl +++ b/src/operations/blur.jl @@ -48,8 +48,9 @@ struct GaussianBlur{K <: AbstractVector, S <: AbstractVector} <: ImageOperation end end -GaussianBlur(k, σ) = GaussianBlur(vectorize(k), vectorize(σ)) +# The default value for σ is taken from Albumentations GaussianBlur(k) = GaussianBlur(k, 0.3 * ((k - 1) / 2 - 1) + 0.8) +GaussianBlur(k, σ) = GaussianBlur(vectorize(k), vectorize(σ)) randparam(op::GaussianBlur, img) = (safe_rand(op.k), safe_rand(op.σ)) diff --git a/src/operations/color.jl b/src/operations/color.jl index 319b7dc1..a9529240 100644 --- a/src/operations/color.jl +++ b/src/operations/color.jl @@ -55,6 +55,7 @@ struct ColorJitter{A<:AbstractVector, B<:AbstractVector} <: ImageOperation end end +# The default values for α and β are taken from Albumentations ColorJitter() = ColorJitter(0.8:0.1:1.2, -0.2:0.1:0.2, true) ColorJitter(α, β; usemax=true) = ColorJitter(vectorize(α), vectorize(β), usemax)