diff --git a/Project.toml b/Project.toml index 96d2461..8e81b5b 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "ImageBase" uuid = "c817782e-172a-44cc-b673-b171935fbb9e" -version = "0.1.2" +version = "0.1.3" [deps] ImageCore = "a09fc81d-aa75-5fe9-8630-4744c3626534" @@ -18,8 +18,9 @@ ImageIO = "82e4d734-157c-48bb-816b-45c225c6df19" ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1" OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" StackViews = "cae243ae-269e-4f55-b966-ac2d0dc13c15" +Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TestImages = "5e47fb64-e119-507b-a336-dd2b206d9990" [targets] -test = ["Aqua", "Documenter", "Test", "ImageIO", "ImageMagick", "OffsetArrays", "StackViews", "TestImages"] +test = ["Aqua", "Documenter", "Test", "ImageIO", "ImageMagick", "OffsetArrays", "Statistics", "StackViews", "TestImages"] diff --git a/src/ImageBase.jl b/src/ImageBase.jl index 619be8f..d466214 100644 --- a/src/ImageBase.jl +++ b/src/ImageBase.jl @@ -8,7 +8,13 @@ export # finite difference on one-dimension # originally from Images.jl fdiff, - fdiff! + fdiff!, + + # basic image statistics, from Images.jl + minfinite, + maxfinite, + maxabsfinite, + meanfinite using Reexport @@ -19,6 +25,7 @@ using ImageCore.OffsetArrays include("diff.jl") include("restrict.jl") +include("statistics.jl") include("compat.jl") include("deprecated.jl") diff --git a/src/deprecated.jl b/src/deprecated.jl index 28c6a6c..7cb30f1 100644 --- a/src/deprecated.jl +++ b/src/deprecated.jl @@ -2,4 +2,6 @@ @deprecate restrict(A::AbstractArray, region::Vector{Int}) restrict(A, (region...,)) +@deprecate meanfinite(A, region) meanfinite(A; dims=region) + # END 0.1 deprecation diff --git a/src/statistics.jl b/src/statistics.jl new file mode 100644 index 0000000..83289f0 --- /dev/null +++ b/src/statistics.jl @@ -0,0 +1,66 @@ +""" + Map12(condition, f1, f2) + +Creates a function mapping `x -> condition(x) ? f1(x) : f2(x)`. +""" +struct Map12{C,F1,F2} + condition::C + f1::F1 + f2::F2 +end +(m::Map12)(x) = m.condition(x) ? m.f1(x) : m.f2(x) + +minc(x, y) = min(x, y) +minc(x::Color, y::Color) = mapc(min, x, y) +maxc(x, y) = max(x, y) +maxc(x::Color, y::Color) = mapc(max, x, y) + +""" + minfinite(A; kwargs...) + +Calculate the minimum value in `A`, ignoring any values that are not finite (Inf or NaN). + +The supported `kwargs` are those of `minimum(f, A; kwargs...)`. +""" +minfinite(A; kwargs...) = mapreduce(Map12(isfinite, identity, typemax), minc, A; kwargs...) + +""" + maxfinite(A; kwargs...) + +Calculate the maximum value in `A`, ignoring any values that are not finite (Inf or NaN). + +The supported `kwargs` are those of `maximum(f, A; kwargs...)`. +""" +maxfinite(A; kwargs...) = mapreduce(Map12(isfinite, identity, typemin), maxc, A; kwargs...) + +""" + maxabsfinite(A; kwargs...) + +Calculate the maximum absolute value in `A`, ignoring any values that are not finite (Inf or NaN). + +The supported `kwargs` are those of `maximum(f, A; kwargs...)`. +""" +maxabsfinite(A; kwargs...) = mapreduce(Map12(isfinite, abs, typemin), maxc, A; kwargs...) + +""" + meanfinite(A; kwargs...) + +Compute the mean value of `A`, ignoring any non-finite values. + +The supported `kwargs` are those of `sum(f, A; kwargs...)`. +""" +function meanfinite end + +if Base.VERSION >= v"1.1" + function meanfinite(A; kwargs...) + s = sum(Map12(isfinite, identity, zero), A; kwargs...) + n = sum(Map12(isfinite, x->true, x->false), A; kwargs...) # TODO: replace with `Returns` + return s./n + end +else + function meanfinite(A; kwargs...) + s = sum(Map12(isfinite, identity, zero).(A); kwargs...) + n = sum(Map12(isfinite, x->true, x->false).(A); kwargs...) + return s./n + end +end diff --git a/test/runtests.jl b/test/runtests.jl index 3a77287..ca5f0e1 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -7,11 +7,7 @@ using OffsetArrays: IdentityUnitRange @testset "Project meta quality checks" begin # Not checking compat section for test-only dependencies - ambiguity_exclude_list = [ - # https://github.com/JuliaDiff/ChainRulesCore.jl/pull/367#issuecomment-869071000 - Base.:(==), - ] - Aqua.test_ambiguities([ImageCore, Base, Core], exclude=ambiguity_exclude_list) + Aqua.test_ambiguities(ImageBase) Aqua.test_all(ImageBase; ambiguities=false, project_extras=true, @@ -23,9 +19,10 @@ using OffsetArrays: IdentityUnitRange doctest(ImageBase,manual = false) end end - + include("diff.jl") include("restrict.jl") + include("statistics.jl") @info "deprecations are expected" include("deprecated.jl") diff --git a/test/statistics.jl b/test/statistics.jl new file mode 100644 index 0000000..2c051b8 --- /dev/null +++ b/test/statistics.jl @@ -0,0 +1,38 @@ +using ImageBase +using Statistics +using Test + +@testset "Reductions" begin + _abs(x::Colorant) = mapreducec(abs, +, 0, x) + + A = rand(5,5,3) + img = colorview(RGB, PermutedDimsArray(A, (3,1,2))) + s12 = sum(img, dims=(1,2)) + @test eltype(s12) <: RGB + A = [NaN, 1, 2, 3] + @test meanfinite(A, 1) ≈ [2] + A = [NaN 1 2 3; + NaN 6 5 4] + mf = meanfinite(A, 1) + @test isnan(mf[1]) + @test mf[1,2:end] ≈ [3.5,3.5,3.5] + @test meanfinite(A, 2) ≈ reshape([2, 5], 2, 1) + @test meanfinite(A, (1,2)) ≈ [3.5] + @test minfinite(A) == 1 + @test maxfinite(A) == 6 + @test maxabsfinite(A) == 6 + A = rand(10:20, 5, 5) + @test minfinite(A) == minimum(A) + @test maxfinite(A) == maximum(A) + A = reinterpret(N0f8, rand(0x00:0xff, 5, 5)) + @test minfinite(A) == minimum(A) + @test maxfinite(A) == maximum(A) + A = rand(Float32,3,5,5) + img = colorview(RGB, A) + dc = meanfinite(img, 1)-reshape(reinterpretc(RGB{Float32}, mean(A, dims=2)), (1,5)) + @test maximum(map(_abs, dc)) < 1e-6 + dc = minfinite(img)-RGB{Float32}(minimum(A, dims=(2,3))...) + @test _abs(dc) < 1e-6 + dc = maxfinite(img)-RGB{Float32}(maximum(A, dims=(2,3))...) + @test _abs(dc) < 1e-6 +end