Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GaussianBlur and ColorJitter ops. #84

Merged
merged 14 commits into from
Jul 12, 2021
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
6 changes: 6 additions & 0 deletions docs/operations/blur/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"title": "Blurring",
"order": [
"gaussianblur.jl"
]
}
34 changes: 34 additions & 0 deletions docs/operations/blur/gaussianblur.jl
Original file line number Diff line number Diff line change
@@ -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
32 changes: 32 additions & 0 deletions docs/operations/color/colorjitter.jl
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions docs/operations/color/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"title": "Color adjustments",
"order": [
"colorjitter.jl"
],
"description": ""
}
4 changes: 3 additions & 1 deletion docs/operations/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
"order": [
"affine",
"distortions",
"color",
"blur",
"size",
"misc"
]
}


5 changes: 5 additions & 0 deletions src/Augmentor.jl
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ export

ElasticDistortion,

ColorJitter,
GaussianBlur,

augment,
augment!,
augmentbatch!,
Expand All @@ -95,6 +98,8 @@ include("operations/resize.jl")
include("operations/scale.jl")
include("operations/zoom.jl")
include("operations/either.jl")
include("operations/color.jl")
include("operations/blur.jl")

include("distortionfields.jl")
include("distortedview.jl")
Expand Down
75 changes: 75 additions & 0 deletions src/operations/blur.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
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

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(σ))
GaussianBlur(k) = GaussianBlur(k, 0.3 * ((k - 1) / 2 - 1) + 0.8)
barucden marked this conversation as resolved.
Show resolved Hide resolved

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))
barucden marked this conversation as resolved.
Show resolved Hide resolved
return imfilter(img, kernel)
end
barucden marked this conversation as resolved.
Show resolved Hide resolved

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

117 changes: 117 additions & 0 deletions src/operations/color.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
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()
ColorJitter(α, β; [usemax])

Arguments
--------------

- **`α`** : `Real` or `AbstractVector` of `Real` that denote the coefficient(s)
for contrast adjustment. Defaults to `0.8:0.1:1.2`.
- **`β`** : `Real` or `AbstractVector` of `Real` that denote the coefficient(s)
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`.

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() = ColorJitter(0.8:0.1:1.2, -0.2:0.1:0.2, true)
barucden marked this conversation as resolved.
Show resolved Hide resolved
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)
function _get_M(op::ColorJitter, img)
if op.usemax
T = eltype(img)
return T(gamutmax(eltype(img))...)
barucden marked this conversation as resolved.
Show resolved Hide resolved
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.
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])
barucden marked this conversation as resolved.
Show resolved Hide resolved
34 changes: 34 additions & 0 deletions test/operations/tst_blur.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
@testset "GaussianBlur" begin
@testset "constructor" begin
@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)
@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, KernelFactors.gaussian((σ, σ), (k, k)))
res = Augmentor.applyeager(GaussianBlur(k, σ), img)
@test ref == res
end
end
end
Loading