From aac513eb4b27f3da5198bf243e06cbeaba61a793 Mon Sep 17 00:00:00 2001 From: Christof Stocker Date: Mon, 3 Jul 2017 14:20:10 +0200 Subject: [PATCH] add ConvertEltype operation --- NEWS.md | 6 +++ README.md | 1 + REQUIRE | 1 + docs/usersguide/operations.rst | 38 ++++++++++++++++ src/Augmentor.jl | 7 +++ src/operations/convert.jl | 80 ++++++++++++++++++++++++++++++++++ test/operations/tst_convert.jl | 75 +++++++++++++++++++++++++++++++ test/runtests.jl | 3 +- 8 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 src/operations/convert.jl create mode 100644 test/operations/tst_convert.jl diff --git a/NEWS.md b/NEWS.md index 7b521848..ff3e9e06 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,11 @@ # v0.2.0 +New operations: + +- `ConvertEltype`: Convert the array elements to the given type + +Other changes: + - `Either` can now lazily combine affine operations with operations such as `Crop`, `Zoom`, and `Resize`. This is because a new kind of support was introduced called `Augmentor.supports_affineview`, diff --git a/README.md b/README.md index 7a115275..6dca75e4 100644 --- a/README.md +++ b/README.md @@ -226,6 +226,7 @@ look at the corresponding section of the | | `CropSize` | Crop area around the center with specified size. | | `CropRatio` | Crop to specified aspect ratio. | | `RCropRatio` | Crop random window of specified aspect ratio. +| *Conversion:* | `ConvertEltype` | Convert the array elements to the given type. | *Layout:* | `SplitChannels` | Separate the color channels into a dedicated array dimension. | | `CombineChannels` | Collapse the first dimension into a specific colorant. | | `PermuteDims` | Reorganize the array dimensions into a specific order. diff --git a/REQUIRE b/REQUIRE index 418abebf..9c212277 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,4 +1,5 @@ julia 0.6 +MappedArrays 0.0.3 ImageCore 0.1.2 ImageTransformations 0.3.0 ImageFiltering 0.1.4 diff --git a/docs/usersguide/operations.rst b/docs/usersguide/operations.rst index ac2a037c..a6ef17a6 100644 --- a/docs/usersguide/operations.rst +++ b/docs/usersguide/operations.rst @@ -21,6 +21,8 @@ functionality. | Cropping | :class:`Crop` :class:`CropNative` :class:`CropSize` :class:`CropRatio` | | | :class:`RCropRatio` | +-----------------------+----------------------------------------------------------------------------+ +| Conversion | :class:`ConvertEltype` | ++-----------------------+----------------------------------------------------------------------------+ | Information Layout | :class:`SplitChannels` :class:`CombineChannels` :class:`PermuteDims` | | | :class:`Reshape` | +-----------------------+----------------------------------------------------------------------------+ @@ -554,6 +556,42 @@ Resizing | .. image:: https://raw.githubusercontent.com/JuliaML/FileStorage/master/Augmentor/testpattern_small.png | .. image:: https://raw.githubusercontent.com/JuliaML/FileStorage/master/Augmentor/operations/Resize.png | +---------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------+ +Conversion +-------------------- + +.. class:: ConvertEltype + + Convert the element type of the given array/image into the + given ``eltype``. This operation is especially useful for + converting color images to grayscale (or the other way + around). That said the operation is not specific to color + types and can also be used for numeric arrays (e.g. with + separated channels). + + Note that this is an element-wise convert function. Thus it + can not be used to combine or separate color channels. Use + :class:`SplitChannels` or :class:`CombineChannels` for those + purposes. + +.. code-block:: jlcon + + julia> op = ConvertEltype(Gray) + Convert eltype to Gray + + julia> img = testpattern() + 300×400 Array{RGBA{N0f8},2}: + [...] + + julia> augment(img, op) + 300×400 Array{Gray{N0f8},2}: + [...] + ++----------------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------+ +| Input | Output for ``ConvertEltype(GrayA)`` | ++================================================================================================================+================================================================================================================+ +| .. image:: https://raw.githubusercontent.com/JuliaML/FileStorage/master/Augmentor/testpattern_small.png | .. image:: https://raw.githubusercontent.com/JuliaML/FileStorage/master/Augmentor/operations/ConvertEltype.png | ++----------------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------+ + Information Layout -------------------- diff --git a/src/Augmentor.jl b/src/Augmentor.jl index 77de7422..76c6dd89 100644 --- a/src/Augmentor.jl +++ b/src/Augmentor.jl @@ -3,6 +3,7 @@ module Augmentor using ColorTypes using ColorTypes: AbstractGray +using MappedArrays using ImageCore using ImageTransformations using ImageFiltering @@ -19,11 +20,16 @@ using Base.PermutedDimsArrays: PermutedDimsArray export + Gray, + RGB, + SplitChannels, CombineChannels, PermuteDims, Reshape, + ConvertEltype, + Rotate90, Rotate180, Rotate270, @@ -61,6 +67,7 @@ include("types.jl") include("operation.jl") include("operations/channels.jl") +include("operations/convert.jl") include("operations/noop.jl") include("operations/cache.jl") diff --git a/src/operations/convert.jl b/src/operations/convert.jl new file mode 100644 index 00000000..e5806402 --- /dev/null +++ b/src/operations/convert.jl @@ -0,0 +1,80 @@ +""" + ConvertEltype <: Augmentor.Operation + +Description +-------------- + +Convert the element type of the given array/image into the given +`eltype`. This operation is especially useful for converting +color images to grayscale (or the other way around). That said +the operation is not specific to color types and can also be used +for numeric arrays (e.g. with separated channels). + +Note that this is an element-wise convert function. Thus it can +not be used to combine or separate color channels. Use +[`SplitChannels`](@ref) or [`CombineChannels`](@ref) for those +purposes. + +Usage +-------------- + + ConvertEltype(eltype) + +Arguments +-------------- + +- **`eltype`** : The eltype of the resulting array/image. + +Examples +-------------- + +```julia +julia> using Augmentor, Colors + +julia> A = rand(RGB, 10, 10) # three color channels +10×10 Array{RGB{Float64},2}: +[...] + +julia> augment(A, ConvertEltype(Gray)) # convert to grayscale +10×10 Array{Gray{Float64},2}: +[...] + +julia> augment(A, ConvertEltype(Gray{Float32})) # more specific +10×10 Array{Gray{Float32},2}: +[...] +``` + +see also +-------------- + +[`CombineChannels`](@ref), [`SplitChannels`](@ref), [`augment`](@ref) +""" +struct ConvertEltype{T} <: Operation + eltype::Type{T} +end + +@inline supports_lazy(::Type{<:ConvertEltype}) = true + +function applyeager(op::ConvertEltype{T}, img::AbstractArray) where T + convert(Array{T}, img) +end + +function applylazy(op::ConvertEltype{T}, img::AbstractArray) where T + mappedarray(c->convert(T,c), img) +end + +function showconstruction(io::IO, op::ConvertEltype) + print(io, typeof(op).name.name, '(') + ImageCore.showcoloranttype(io, op.eltype) + print(io, ')') +end + +function Base.show(io::IO, op::ConvertEltype) + if get(io, :compact, false) + print(io, "Convert eltype to ") + ImageCore.showcoloranttype(io, op.eltype) + else + print(io, "Augmentor.") + showconstruction(io, op) + end +end diff --git a/test/operations/tst_convert.jl b/test/operations/tst_convert.jl new file mode 100644 index 00000000..45a9baf1 --- /dev/null +++ b/test/operations/tst_convert.jl @@ -0,0 +1,75 @@ +@testset "ConvertEltype" begin + @test (ConvertEltype <: Augmentor.AffineOperation) == false + @test (ConvertEltype <: Augmentor.ImageOperation) == false + @test (ConvertEltype <: Augmentor.Operation) == true + + @testset "constructor" begin + @test_throws MethodError ConvertEltype() + @test typeof(@inferred(ConvertEltype(Float64))) <: ConvertEltype <: Augmentor.Operation + @test typeof(@inferred(ConvertEltype(RGB))) <: ConvertEltype <: Augmentor.Operation + @test typeof(@inferred(ConvertEltype(RGB{N0f8}))) <: ConvertEltype <: Augmentor.Operation + @test str_show(ConvertEltype(Float64)) == "Augmentor.ConvertEltype(Float64)" + @test str_show(ConvertEltype(RGB)) == "Augmentor.ConvertEltype(RGB{Any})" + @test str_show(ConvertEltype(Gray{N0f8})) == "Augmentor.ConvertEltype(Gray{N0f8})" + @test str_showconst(ConvertEltype(Float64)) == "ConvertEltype(Float64)" + @test str_showconst(ConvertEltype(RGB{N0f8})) == "ConvertEltype(RGB{N0f8})" + @test str_showcompact(ConvertEltype(Float64)) == "Convert eltype to Float64" + @test str_showcompact(ConvertEltype(Gray)) == "Convert eltype to Gray{Any}" + end + @testset "eager" begin + @test Augmentor.supports_eager(ConvertEltype) === true + @test Augmentor.supports_eager(ConvertEltype{Float64}) === true + let img = @inferred(Augmentor.applyeager(ConvertEltype(Gray), rgb_rect)) + @test typeof(img) == Array{Gray{N0f8},2} + @test img == convert.(Gray, rgb_rect) + end + let img = @inferred(Augmentor.applyeager(ConvertEltype(Gray{Float32}), rgb_rect)) + @test typeof(img) == Array{Gray{Float32},2} + @test img == convert.(Gray{Float32}, rgb_rect) + end + let img = @inferred(Augmentor.applyeager(ConvertEltype(Float32), checkers)) + @test typeof(img) == Array{Float32,2} + @test img == convert(Array{Float32}, checkers) + end + let img = @inferred(Augmentor.applyeager(ConvertEltype(RGB), checkers)) + @test typeof(img) == Array{RGB{N0f8},2} + @test img == convert.(RGB, checkers) + end + end + @testset "affine" begin + @test Augmentor.supports_affine(ConvertEltype) === false + end + @testset "affineview" begin + @test Augmentor.supports_affineview(ConvertEltype) === false + end + @testset "lazy" begin + @test Augmentor.supports_lazy(ConvertEltype) === true + @test Augmentor.supports_lazy(ConvertEltype{Float64}) === true + @test @inferred(Augmentor.supports_lazy(typeof(ConvertEltype(Gray)))) === true + let img = @inferred(Augmentor.applylazy(ConvertEltype(Gray), rgb_rect)) + @test typeof(img) <: ReadonlyMappedArray{Gray{N0f8},2} + @test img == convert.(Gray, rgb_rect) + end + let img = @inferred(Augmentor.applylazy(ConvertEltype(Gray{Float32}), rgb_rect)) + @test typeof(img) <: ReadonlyMappedArray{Gray{Float32},2} + @test img == convert.(Gray{Float32}, rgb_rect) + end + let img = @inferred(Augmentor.applylazy(ConvertEltype(Float32), checkers)) + @test typeof(img) <: ReadonlyMappedArray{Float32,2} + @test img == convert(Array{Float32}, checkers) + end + let img = @inferred(Augmentor.applylazy(ConvertEltype(RGB), checkers)) + @test typeof(img) <: ReadonlyMappedArray{RGB{N0f8},2} + @test img == convert.(RGB, checkers) + end + end + @testset "view" begin + @test Augmentor.supports_view(ConvertEltype) === false + end + @testset "stepview" begin + @test Augmentor.supports_stepview(ConvertEltype) === false + end + @testset "permute" begin + @test Augmentor.supports_permute(ConvertEltype) === false + end +end diff --git a/test/runtests.jl b/test/runtests.jl index 97179740..b5974162 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,4 +1,4 @@ -using ImageCore, ImageFiltering, ImageTransformations, CoordinateTransformations, Interpolations, OffsetArrays, StaticArrays, ColorTypes, FixedPointNumbers, TestImages, IdentityRanges, Base.Test +using ImageCore, ImageFiltering, ImageTransformations, CoordinateTransformations, Interpolations, OffsetArrays, StaticArrays, ColorTypes, FixedPointNumbers, TestImages, IdentityRanges, MappedArrays, Base.Test using ImageInTerminal # check for ambiguities @@ -35,6 +35,7 @@ rgb_rect = rand(RGB{N0f8}, 2, 3) tests = [ "tst_utils.jl", "operations/tst_channels.jl", + "operations/tst_convert.jl", "operations/tst_noop.jl", "operations/tst_cache.jl", "operations/tst_rotation.jl",