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

Add new element-wise map operations #19

Merged
merged 2 commits into from
Apr 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@ look at the corresponding section of the
| | [`CropRatio`](https://evizero.github.io/Augmentor.jl/operations/cropratio) | ![](https://evizero.github.io/Augmentor.jl/assets/tiny_CropRatio.png) | Crop to specified aspect ratio.
| | [`RCropRatio`](https://evizero.github.io/Augmentor.jl/operations/rcropratio) | ![](https://evizero.github.io/Augmentor.jl/assets/tiny_RCropRatio.png) | Crop random window of specified aspect ratio.
| *Conversion:* | [`ConvertEltype`](https://evizero.github.io/Augmentor.jl/operations/converteltype) | ![](https://evizero.github.io/Augmentor.jl/assets/tiny_ConvertEltype.png) | Convert the array elements to the given type.
| *Mapping:* | [`MapFun`](https://evizero.github.io/Augmentor.jl/operations/mapfun) | - | Map custom function over image
| | [`AggregateThenMapFun`](https://evizero.github.io/Augmentor.jl/operations/aggmapfun) | - | Map aggregated value over image
| *Layout:* | [`SplitChannels`](https://evizero.github.io/Augmentor.jl/operations/splitchannels) | - | Separate the color channels into a dedicated array dimension.
| | [`CombineChannels`](https://evizero.github.io/Augmentor.jl/operations/combinechannels) | - | Collapse the first dimension into a specific colorant.
| | [`PermuteDims`](https://evizero.github.io/Augmentor.jl/operations/permutedims) | - | Reorganize the array dimensions into a specific order.
Expand Down
2 changes: 2 additions & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ op_fnames = [
"rcropratio",
"resize",
"converteltype",
"mapfun",
"aggmapfun",
"splitchannels",
"combinechannels",
"permutedims",
Expand Down
3 changes: 2 additions & 1 deletion docs/src/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ in order to preserve type-stability.
|:---------:|:--:|:------------------:|:------------------------:|:----------------------:|:-----------------------:|:------------------------:|
| ![](assets/tiny_pattern.png) | → | [![](assets/tiny_Crop.png)](@ref Crop) | [![](assets/tiny_CropNative.png)](@ref CropNative) | [![](assets/tiny_CropSize.png)](@ref CropSize) | [![](assets/tiny_CropRatio.png)](@ref CropRatio) | [![](assets/tiny_RCropRatio.png)](@ref RCropRatio) |

## Conversion and Layout
## Element-wise Transformations and Layout

It is not uncommon that machine learning frameworks require the
data in a specific form and layout. For example many deep
Expand All @@ -115,6 +115,7 @@ or end of a augmentation pipeline.
Category | Available Operations
----------------------|-----------------------------------------------
Conversion | [`ConvertEltype`](@ref ConvertEltype) (e.g. convert to grayscale)
Mapping | [`MapFun`](@ref MapFun), [`AggregateThenMapFun`](@ref AggregateThenMapFun)
Information Layout | [`SplitChannels`](@ref SplitChannels), [`CombineChannels`](@ref CombineChannels), [`PermuteDims`](@ref PermuteDims), [`Reshape`](@ref Reshape)

## Utility Operations
Expand Down
5 changes: 5 additions & 0 deletions docs/src/operations/aggmapfun.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# [AggregateThenMapFun: Aggregate and Map over Image](@id AggregateThenMapFun)

```@docs
AggregateThenMapFun
```
5 changes: 5 additions & 0 deletions docs/src/operations/mapfun.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# [MapFun: Map function over Image](@id MapFun)

```@docs
MapFun
```
3 changes: 3 additions & 0 deletions src/Augmentor.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export
Reshape,

ConvertEltype,
MapFun,
AggregateThenMapFun,

Rotate90,
Rotate180,
Expand Down Expand Up @@ -75,6 +77,7 @@ include("operation.jl")

include("operations/channels.jl")
include("operations/convert.jl")
include("operations/mapfun.jl")

include("operations/noop.jl")
include("operations/cache.jl")
Expand Down
152 changes: 152 additions & 0 deletions src/operations/mapfun.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
"""
MapFun <: Augmentor.Operation

Description
--------------

Maps the given function over all individual array elements.

This means that the given function is called with an individual
elements and is expected to return a transformed element that
should take the original's place. This further implies that the
function is expected to be unary. It is encouraged that the
function should be consistent with its return type and
type-stable.

Usage
--------------

MapFun(fun)

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

- **`fun`** : The unary function that should be mapped over all
individual array elements.

See also
--------------

[`AggregateThenMapFun`](@ref), [`ConvertEltype`](@ref), [`augment`](@ref)

Examples
--------------

```julia
using Augmentor, ColorTypes
img = testpattern()

# subtract the constant RGBA value from each pixel
augment(img, MapFun(px -> px - RGBA(0.5, 0.3, 0.7, 0.0)))

# separate channels to scale each numeric element by a constant value
pl = SplitChannels() |> MapFun(el -> el * 0.5) |> CombineChannels(RGBA)
augment(img, pl)
```
"""
struct MapFun{T} <: Operation
fun::T
end

@inline supports_lazy(::Type{<:MapFun}) = true

function applyeager(op::MapFun, img::AbstractArray)
plain_array(map(op.fun, img))
end

function applylazy(op::MapFun, img::AbstractArray)
mappedarray(op.fun, img)
end

function showconstruction(io::IO, op::MapFun)
print(io, typeof(op).name.name, '(', op.fun, ')')
end

function Base.show(io::IO, op::MapFun)
if get(io, :compact, false)
print(io, "Map function \"", op.fun, "\" over image")
else
print(io, "Augmentor.")
showconstruction(io, op)
end
end

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

"""
AggregateThenMapFun <: Augmentor.Operation

Description
--------------

Compute some aggregated value of the current image using the
given function `aggfun`, and map that value over the current
image using the given function `mapfun`.

This is particularly useful for achieving effects such as
per-image normalization.

Usage
--------------

AggregateThenMapFun(aggfun, mapfun)

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

- **`aggfun`** : A function that takes the whole current image as
input and which result will also be passed to `mapfun`. It
should have a signature of `img -> agg`, where `img` will the
the current image. What type and value `agg` should be is up
to the user.

- **`mapfun`** : The binary function that should be mapped over
all individual array elements. It should have a signature of
`(px, agg) -> new_px` where `px` is a single element of the
current image, and `agg` is the output of `aggfun`.

See also
--------------

[`MapFun`](@ref), [`ConvertEltype`](@ref), [`augment`](@ref)

Examples
--------------

```julia
using Augmentor
img = testpattern()

# subtract the average RGB value of the current image
augment(img, AggregateThenMapFun(img -> mean(img), (px, agg) -> px - agg))
```
"""
struct AggregateThenMapFun{A,M} <: Operation
aggfun::A
mapfun::M
end

@inline supports_lazy(::Type{<:AggregateThenMapFun}) = true

function applyeager(op::AggregateThenMapFun, img::AbstractArray)
agg = op.aggfun(img)
plain_array(map(x -> op.mapfun(x, agg), img))
end

function applylazy(op::AggregateThenMapFun, img::AbstractArray)
agg = op.aggfun(img)
mappedarray(x -> op.mapfun(x, agg), img)
end

function showconstruction(io::IO, op::AggregateThenMapFun)
print(io, typeof(op).name.name, '(', op.aggfun, ", ", op.mapfun, ')')
end

function Base.show(io::IO, op::AggregateThenMapFun)
if get(io, :compact, false)
print(io, "Map result of \"", op.aggfun, "\" using \"", op.mapfun, "\" over image")
else
print(io, "Augmentor.")
showconstruction(io, op)
end
end
117 changes: 117 additions & 0 deletions test/operations/tst_mapfun.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
@testset "MapFun" begin
@test (MapFun <: Augmentor.ImageOperation) == false
@test (MapFun <: Augmentor.Operation) == true
@testset "constructor" begin
@test_throws MethodError MapFun()
@test typeof(@inferred(MapFun((x)->x))) <: MapFun <: Augmentor.Operation
@test typeof(@inferred(MapFun(identity))) <: MapFun <: Augmentor.Operation
@test str_show(MapFun(identity)) == "Augmentor.MapFun(identity)"
@test str_showconst(MapFun(identity)) == "MapFun(identity)"
@test str_showcompact(MapFun(identity)) == "Map function \"identity\" over image"
end
@testset "eager" begin
@test_throws MethodError Augmentor.applyeager(MapFun(identity), nothing)
@test Augmentor.supports_eager(MapFun) === true
for img in (Augmentor.prepareaffine(rect), rect, view(rect, IdentityRange(1:2), IdentityRange(1:3)))
res = @inferred(Augmentor.applyeager(MapFun(identity), img))
@test res == rect
@test typeof(res) <: Array{eltype(img)}
res = @inferred(Augmentor.applyeager(MapFun(x->x .- Gray(0.1)), img))
@test res ≈ rect .- 0.1
@test typeof(res) <: Array{Gray{Float64}}
end
img = OffsetArray(rect, -2, -1)
res = @inferred(Augmentor.applyeager(MapFun(identity), img))
@test res == rect
@test typeof(res) <: Array{eltype(img)}
img = OffsetArray(rgb_rect, -2, -1)
res = @inferred(Augmentor.applyeager(MapFun(x -> x - RGB(.1,.1,.1)), img))
@test res == rgb_rect .- RGB(.1,.1,.1)
@test typeof(res) <: Array{RGB{Float64}}
end
@testset "affine" begin
@test Augmentor.supports_affine(MapFun) === false
end
@testset "affineview" begin
@test Augmentor.supports_affineview(MapFun) === false
end
@testset "lazy" begin
@test Augmentor.supports_lazy(MapFun) === true
res = @inferred(Augmentor.applylazy(MapFun(identity), rect))
@test res === mappedarray(identity, rect)
res = @inferred(Augmentor.applylazy(MapFun(identity), rgb_rect))
@test res === mappedarray(identity, rgb_rect)
res = @inferred(Augmentor.applylazy(MapFun(x->x-RGB(.1,.1,.1)), rgb_rect))
@test res == mappedarray(x->x-RGB(.1,.1,.1), rgb_rect)
@test typeof(res) <: MappedArrays.ReadonlyMappedArray{ColorTypes.RGB{Float64}}
end
@testset "view" begin
@test Augmentor.supports_view(MapFun) === false
end
@testset "stepview" begin
@test Augmentor.supports_stepview(MapFun) === false
end
@testset "permute" begin
@test Augmentor.supports_permute(MapFun) === false
end
end

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

@testset "AggregateThenMapFun" begin
@test (AggregateThenMapFun <: Augmentor.ImageOperation) == false
@test (AggregateThenMapFun <: Augmentor.Operation) == true
@testset "constructor" begin
@test_throws MethodError AggregateThenMapFun()
@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(mean, identity)"
@test str_showconst(AggregateThenMapFun(mean, identity)) == "AggregateThenMapFun(mean, identity)"
@test str_showcompact(AggregateThenMapFun(mean, identity)) == "Map result of \"mean\" using \"identity\" over image"
end
@testset "eager" begin
@test_throws MethodError Augmentor.applyeager(AggregateThenMapFun(mean, identity), nothing)
@test Augmentor.supports_eager(AggregateThenMapFun) === true
for img in (Augmentor.prepareaffine(rect), rect, view(rect, IdentityRange(1:2), IdentityRange(1:3)))
res = @inferred(Augmentor.applyeager(AggregateThenMapFun(mean, (x,a)->x), img))
@test res == rect
@test typeof(res) <: Array{eltype(img)}
res = @inferred(Augmentor.applyeager(AggregateThenMapFun(mean, (x,a)->x-a), img))
@test res ≈ rect .- mean(rect)
@test typeof(res) <: Array{Gray{Float64}}
end
img = OffsetArray(rect, -2, -1)
res = @inferred(Augmentor.applyeager(AggregateThenMapFun(mean, (x,a)->x), img))
@test res == rect
@test typeof(res) <: Array{eltype(img)}
img = OffsetArray(rgb_rect, -2, -1)
res = @inferred(Augmentor.applyeager(AggregateThenMapFun(mean, (x,a)->x-a), img))
@test res == rgb_rect .- mean(rgb_rect)
@test typeof(res) <: Array{RGB{Float64}}
end
@testset "affine" begin
@test Augmentor.supports_affine(AggregateThenMapFun) === false
end
@testset "affineview" begin
@test Augmentor.supports_affineview(AggregateThenMapFun) === false
end
@testset "lazy" begin
@test Augmentor.supports_lazy(AggregateThenMapFun) === true
res = @inferred(Augmentor.applylazy(AggregateThenMapFun(mean, (x,a)->x), rect))
@test res == rect
@test res isa ReadonlyMappedArray
res = @inferred(Augmentor.applylazy(AggregateThenMapFun(mean, (x,a)->x-a), rgb_rect))
@test res == mappedarray(x->x-mean(rgb_rect), rgb_rect)
@test typeof(res) <: MappedArrays.ReadonlyMappedArray{ColorTypes.RGB{Float64}}
end
@testset "view" begin
@test Augmentor.supports_view(AggregateThenMapFun) === false
end
@testset "stepview" begin
@test Augmentor.supports_stepview(AggregateThenMapFun) === false
end
@testset "permute" begin
@test Augmentor.supports_permute(AggregateThenMapFun) === false
end
end
Loading