Skip to content

Commit

Permalink
Merge pull request #19 from Evizero/mapfun
Browse files Browse the repository at this point in the history
Add new element-wise map operations
  • Loading branch information
Evizero authored Apr 8, 2018
2 parents dd57be7 + 1836ea0 commit cbb8a23
Show file tree
Hide file tree
Showing 11 changed files with 313 additions and 1 deletion.
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

0 comments on commit cbb8a23

Please sign in to comment.