Skip to content

Commit 12a7795

Browse files
authored
introduce sumfinite, enhancement to *finite, rename minfinite/maxfinite (#17)
Features: * (new function) add `sumfinite([f=identity], A)` * add `meanfinite([f=identity], A)` * add `minimum_finite([f=identity], A)` * add `maximum_finite([f=identity], a)` Bug fixes: * "fixes" `varfinite` for `RGB` array inputs using dot product. This requires ColorVectorSpace v0.9.7 * support `minimum_finite(A; dims)` and `maximum_finite(A; dims)` Deprecations: * rename: `minfinite` -> `minimum_finite` * rename: `maxfinite` -> `maximum_finite` * deprecate `maxabsfinite(A)` in favor of `maximum_finite(abs, A)` There are some type infer issues, which I leave as future work.
1 parent 78fb34c commit 12a7795

9 files changed

+221
-71
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ ImageCore = "a09fc81d-aa75-5fe9-8630-4744c3626534"
77
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
88

99
[compat]
10-
ImageCore = "0.8, 0.9"
10+
ImageCore = "0.9.3"
1111
Reexport = "0.2, 1"
1212
julia = "1"
1313

src/ImageBase.jl

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,15 @@ export
1111
fdiff!,
1212

1313
# basic image statistics, from Images.jl
14-
minfinite,
15-
maxfinite,
16-
maxabsfinite,
14+
minimum_finite,
15+
maximum_finite,
1716
meanfinite,
18-
varfinite
17+
varfinite,
18+
sumfinite
1919

20+
# Introduced in ColorVectorSpace v0.9.3
21+
# https://github.com/JuliaGraphics/ColorVectorSpace.jl/pull/172
22+
using ImageCore.ColorVectorSpace.Future: abs2
2023

2124
using Reexport
2225

src/deprecated.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
@deprecate restrict(A::AbstractArray, region::Vector{Int}) restrict(A, (region...,))
44

5-
@deprecate meanfinite(A, region) meanfinite(A; dims=region)
5+
@deprecate meanfinite(A::AbstractArray, region) meanfinite(A; dims=region)
6+
7+
@deprecate minfinite(A; kwargs...) minimum_finite(A; kwargs...)
8+
@deprecate maxfinite(A; kwargs...) maximum_finite(A; kwargs...)
9+
@deprecate maxabsfinite(A; kwargs...) maximum_finite(abs, A; kwargs...)
610

711
# END 0.1 deprecation

src/statistics.jl

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,70 @@
11
"""
2-
minfinite(A; kwargs...)
2+
minimum_finite([f=identity], A; kwargs...)
33
4-
Calculate the minimum value in `A`, ignoring any values that are not finite (Inf or NaN).
4+
Calculate `minimum(f, A)` while ignoring any values that are not finite, e.g., `Inf` or
5+
`NaN`.
6+
7+
If `A` is a colorant array with multiple channels (e.g., `Array{RGB}`), the `min` comparison
8+
is done in channel-wise sense.
59
610
The supported `kwargs` are those of `minimum(f, A; kwargs...)`.
711
"""
8-
minfinite(A; kwargs...) = mapreduce(IfElse(isfinite, identity, typemax), minc, A; kwargs...)
12+
function minimum_finite(f, A::AbstractArray{T}; kwargs...) where T
13+
# FIXME(johnnychen94): if `typeof(f(first(A))) != eltype(A)`, this function is not type-stable.
14+
mapreduce(IfElse(isfinite, f, typemax), minc, A; kwargs...)
15+
end
16+
minimum_finite(A::AbstractArray; kwargs...) = minimum_finite(identity, A; kwargs...)
917

1018
"""
11-
maxfinite(A; kwargs...)
19+
maximum_finite([f=identity], A; kwargs...)
20+
21+
Calculate `maximum(f, A)` while ignoring any values that are not finite, e.g., `Inf` or
22+
`NaN`.
1223
13-
Calculate the maximum value in `A`, ignoring any values that are not finite (Inf or NaN).
24+
If `A` is a colorant array with multiple channels (e.g., `Array{RGB}`), the `max` comparison
25+
is done in channel-wise sense.
1426
1527
The supported `kwargs` are those of `maximum(f, A; kwargs...)`.
1628
"""
17-
maxfinite(A; kwargs...) = mapreduce(IfElse(isfinite, identity, typemin), maxc, A; kwargs...)
29+
function maximum_finite(f, A::AbstractArray{T}; kwargs...) where T
30+
# FIXME(johnnychen94): if `typeof(f(first(A))) != eltype(A)`, this function is not type-stable
31+
mapreduce(IfElse(isfinite, f, typemin), maxc, A; kwargs...)
32+
end
33+
maximum_finite(A::AbstractArray; kwargs...) = maximum_finite(identity, A; kwargs...)
1834

1935
"""
20-
maxabsfinite(A; kwargs...)
36+
sumfinite([f=identity], A; kwargs...)
2137
22-
Calculate the maximum absolute value in `A`, ignoring any values that are not finite (Inf or NaN).
38+
Compute `sum(f, A)` while ignoring any non-finite values.
2339
24-
The supported `kwargs` are those of `maximum(f, A; kwargs...)`.
40+
The supported `kwargs` are those of `sum(f, A; kwargs...)`.
2541
"""
26-
maxabsfinite(A; kwargs...) = mapreduce(IfElse(isfinite, abs, typemin), maxc, A; kwargs...)
42+
sumfinite(A; kwargs...) = sumfinite(identity, A; kwargs...)
43+
44+
if Base.VERSION >= v"1.1"
45+
sumfinite(f, A; kwargs...) = sum(IfElse(isfinite, f, zero), A; kwargs...)
46+
else
47+
sumfinite(f, A; kwargs...) = sum(IfElse(isfinite, f, zero).(A); kwargs...)
48+
end
2749

2850
"""
29-
meanfinite(A; kwargs...)
51+
meanfinite([f=identity], A; kwargs...)
3052
31-
Compute the mean value of `A`, ignoring any non-finite values.
53+
Compute `mean(f, A)` while ignoring any non-finite values.
3254
3355
The supported `kwargs` are those of `sum(f, A; kwargs...)`.
3456
"""
35-
function meanfinite end
57+
meanfinite(A; kwargs...) = meanfinite(identity, A; kwargs...)
3658

3759
if Base.VERSION >= v"1.1"
38-
function meanfinite(A; kwargs...)
39-
s = sum(IfElse(isfinite, identity, zero), A; kwargs...)
60+
function meanfinite(f, A; kwargs...)
61+
s = sumfinite(f, A; kwargs...)
4062
n = sum(IfElse(isfinite, x->true, x->false), A; kwargs...) # TODO: replace with `Returns`
4163
return s./n
4264
end
4365
else
44-
function meanfinite(A; kwargs...)
45-
s = sum(IfElse(isfinite, identity, zero).(A); kwargs...)
66+
function meanfinite(f, A; kwargs...)
67+
s = sumfinite(f, A; kwargs...)
4668
n = sum(IfElse(isfinite, x->true, x->false).(A); kwargs...)
4769
return s./n
4870
end
@@ -54,21 +76,28 @@ end
5476
Compute the variance of `A`, ignoring any non-finite values.
5577
5678
The supported `kwargs` are those of `sum(f, A; kwargs...)`.
79+
80+
!!! note
81+
This function can produce a seemingly suprising result if the input array is an RGB
82+
image. To make it more clear, the implementation is made so that
83+
`varfinite(img) ≈ varfinite(RGB.(img))` holds for any gray-scale image. See also
84+
https://github.com/JuliaGraphics/ColorVectorSpace.jl#abs-and-abs2 for more information.
85+
5786
"""
5887
function varfinite end
5988

6089
if Base.VERSION >= v"1.1"
6190
function varfinite(A; kwargs...)
6291
m = meanfinite(A; kwargs...)
6392
n = sum(IfElse(isfinite, x->true, x->false), A; kwargs...) # TODO: replace with `Returns`
64-
s = sum(IfElse(isfinite, identity, zero), (A .- m).^2; kwargs...)
93+
s = sum(IfElse(isfinite, identity, zero), abs2.(A .- m); kwargs...)
6594
return s ./ max.(0, (n .- 1))
6695
end
6796
else
6897
function varfinite(A; kwargs...)
6998
m = meanfinite(A; kwargs...)
7099
n = sum(IfElse(isfinite, x->true, x->false).(A); kwargs...)
71-
s = sum(IfElse(isfinite, identity, zero).((A .- m).^2); kwargs...)
100+
s = sum(IfElse(isfinite, identity, zero).(abs2.(A .- m)); kwargs...)
72101
return s ./ max.(0, (n .- 1))
73102
end
74103
end

src/utils.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,14 @@ struct IfElse{C,F1,F2}
1313
end
1414
(m::IfElse)(x) = m.condition(x) ? m.f1(x) : m.f2(x)
1515

16+
# channelwise min/max
1617
minc(x, y) = min(x, y)
1718
minc(x::Color, y::Color) = mapc(min, x, y)
1819
maxc(x, y) = max(x, y)
1920
maxc(x::Color, y::Color) = mapc(max, x, y)
21+
22+
for (f1, f2) in ((:minc, :min), (:maxc, :max))
23+
@eval function Base.reducedim_init(f, ::typeof($f1), A::AbstractArray, region)
24+
Base.reducedim_init(f, $f2, A, region)
25+
end
26+
end

test/deprecated.jl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,15 @@
33
A = rand(N0f8, 4, 5, 3)
44
@test restrict(A, [1, 2]) == restrict(A, (1, 2))
55
end
6+
7+
@testset "statistics" begin
8+
A = rand(Float32, 4, 4) .- 0.5
9+
@test minfinite(A, dims=1) == minimum_finite(A, dims=1)
10+
@test minfinite(A) == minimum_finite(A)
11+
@test maxfinite(A, dims=1) == maximum_finite(A, dims=1)
12+
@test maxfinite(A) == maximum_finite(A)
13+
14+
@test maxabsfinite(A) == maximum_finite(abs, A)
15+
@test maxabsfinite(A, dims=1) == maximum_finite(abs, A, dims=1)
16+
end
617
end

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ using ImageBase, OffsetArrays, StackViews
22
using Test, TestImages, Aqua, Documenter
33

44
using OffsetArrays: IdentityUnitRange
5+
include("testutils.jl")
56

67
@testset "ImageBase.jl" begin
78

test/statistics.jl

Lines changed: 126 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,134 @@
11
using ImageBase
2+
using ImageBase: varmult
23
using Statistics
34
using Test
45

56
@testset "Reductions" begin
67
_abs(x::Colorant) = mapreducec(abs, +, 0, x)
78

8-
A = rand(5,5,3)
9-
img = colorview(RGB, PermutedDimsArray(A, (3,1,2)))
10-
s12 = sum(img, dims=(1,2))
11-
@test eltype(s12) <: RGB
12-
13-
A = [NaN, 1, 2, 3]
14-
@test meanfinite(A, dims=1) [2]
15-
@test varfinite(A, dims=1) [1]
16-
17-
A = [NaN NaN 1;
18-
1 2 3]
19-
vf = varfinite(A, dims=2)
20-
@test isnan(vf[1])
21-
22-
A = [NaN 1 2 3;
23-
NaN 6 5 4]
24-
mf = meanfinite(A, dims=1)
25-
vf = varfinite(A, dims=1)
26-
@test isnan(mf[1])
27-
@test mf[2:end] [3.5,3.5,3.5]
28-
@test isnan(vf[1])
29-
@test vf[2:end] [12.5,4.5,0.5]
30-
31-
@test meanfinite(A, dims=2) reshape([2, 5], 2, 1)
32-
@test varfinite(A, dims=2) reshape([1, 1], 2, 1)
33-
34-
@test meanfinite(A, dims=(1,2)) [3.5]
35-
@test varfinite(A, dims=(1,2)) [3.5]
36-
37-
@test minfinite(A) == 1
38-
@test maxfinite(A) == 6
39-
@test maxabsfinite(A) == 6
40-
A = rand(10:20, 5, 5)
41-
@test minfinite(A) == minimum(A)
42-
@test maxfinite(A) == maximum(A)
43-
A = reinterpret(N0f8, rand(0x00:0xff, 5, 5))
44-
@test minfinite(A) == minimum(A)
45-
@test maxfinite(A) == maximum(A)
46-
A = rand(Float32,3,5,5)
47-
img = colorview(RGB, A)
48-
dc = meanfinite(img, dims=1)-reshape(reinterpretc(RGB{Float32}, mean(A, dims=2)), (1,5))
49-
@test maximum(map(_abs, dc)) < 1e-6
50-
dc = minfinite(img)-RGB{Float32}(minimum(A, dims=(2,3))...)
51-
@test _abs(dc) < 1e-6
52-
dc = maxfinite(img)-RGB{Float32}(maximum(A, dims=(2,3))...)
53-
@test _abs(dc) < 1e-6
9+
@testset "sumfinite, meanfinite, varfinite" begin
10+
for T in generate_test_types([N0f8, Float32], [Gray, RGB])
11+
A = rand(T, 5, 5)
12+
s12 = sum(A, dims=(1,2))
13+
@test eltype(s12) <: Union{T, float(T), float64(T)}
14+
15+
@test sumfinite(A) sum(A)
16+
@test sumfinite(A, dims=1) sum(A, dims=1)
17+
@test sumfinite(A, dims=(1, 2)) sum(A, dims=(1, 2))
18+
19+
@test meanfinite(A) mean(A)
20+
@test meanfinite(A, dims=1) mean(A, dims=1)
21+
@test meanfinite(A, dims=(1, 2)) mean(A, dims=(1, 2))
22+
23+
@test varfinite(A) varmult(, A)
24+
@test varfinite(A, dims=1) varmult(, A, dims=1)
25+
@test varfinite(A, dims=(1, 2)) varmult(, A, dims=(1, 2))
26+
27+
# test NaN/Inf
28+
if eltype(T) != N0f8
29+
A = rand(T, 5, 5) .- 0.5 .* oneunit(T)
30+
A[1] = Inf
31+
@test sum(A) A[1]
32+
@test sum(abs, A) A[1]
33+
@test sumfinite(A) sum(A[2:end])
34+
@test sumfinite(abs, A) sum(abs, A[2:end])
35+
A[1] = NaN
36+
@test isnan(sum(A))
37+
@test isnan(sum(abs, A))
38+
@test sumfinite(A) sum(A[2:end])
39+
@test sumfinite(abs, A) sum(abs, A[2:end])
40+
41+
A = rand(T, 5, 5) .- 0.5 .* oneunit(T)
42+
A[1] = Inf
43+
@test mean(A) A[1]
44+
@test mean(abs, A) A[1]
45+
@test meanfinite(A) mean(A[2:end])
46+
@test meanfinite(abs, A) mean(abs, A[2:end])
47+
A[1] = NaN
48+
@test isnan(mean(A))
49+
@test isnan(mean(abs, A))
50+
@test meanfinite(A) mean(A[2:end])
51+
@test meanfinite(abs, A) mean(abs, A[2:end])
52+
53+
A = rand(T, 5, 5)
54+
A[1] = Inf
55+
@test isnan(varmult(, A))
56+
@test varfinite(A) varmult(, A[2:end])
57+
A[1] = NaN
58+
@test isnan(varmult(, A))
59+
@test varfinite(A) varmult(, A[2:end])
60+
end
61+
end
62+
63+
A = [NaN, 1, 2, 3]
64+
@test meanfinite(A, dims=1) [2]
65+
@test varfinite(A, dims=1) [1]
66+
67+
A = [NaN NaN 1;
68+
1 2 3]
69+
vf = varfinite(A, dims=2)
70+
@test isnan(vf[1])
71+
72+
A = [NaN 1 2 3;
73+
NaN 6 5 4]
74+
mf = meanfinite(A, dims=1)
75+
vf = varfinite(A, dims=1)
76+
@test isnan(mf[1])
77+
@test mf[2:end] [3.5,3.5,3.5]
78+
@test isnan(vf[1])
79+
@test vf[2:end] [12.5,4.5,0.5]
80+
81+
@test meanfinite(A, dims=2) reshape([2, 5], 2, 1)
82+
@test varfinite(A, dims=2) reshape([1, 1], 2, 1)
83+
84+
@test meanfinite(A, dims=(1,2)) [3.5]
85+
@test varfinite(A, dims=(1,2)) [3.5]
86+
87+
# Ensure we're consistant with our decision to `abs2` in ColorVectorSpace
88+
# See also: https://github.com/JuliaGraphics/ColorVectorSpace.jl/blob/master/README.md#abs-and-abs2
89+
A = rand(Gray, 4, 4)
90+
@test varfinite(A) varfinite(RGB.(A))
91+
A[1] = Inf
92+
@test varfinite(A) varfinite(RGB.(A))
93+
A[1] = NaN
94+
@test varfinite(A) varfinite(RGB.(A))
95+
end
96+
97+
@testset "minfinite, maxfinite, maxabsfinite" begin
98+
for T in generate_test_types([N0f8, Float32], [Gray, ])
99+
A = rand(T, 5, 5)
100+
@test @inferred(minimum_finite(A)) == minimum(A)
101+
@test @inferred(maximum_finite(A)) == maximum(A)
102+
@test minimum_finite(A; dims=1) == minimum(A; dims=1)
103+
@test maximum_finite(A; dims=1) == maximum(A; dims=1)
104+
@test_broken @inferred maximum_finite(A; dims=1)
105+
@test_broken @inferred minimum_finite(A; dims=1)
106+
107+
@test maximum_finite(abs2, A) == maximum(abs2, A)
108+
@test_broken @inferred maximum(abs2, A)
109+
@test_broken @inferred minimum(abs2, A)
110+
111+
if eltype(T) != N0f8
112+
A = rand(T, 5, 5) .- 0.5 * rand(T, 5, 5)
113+
A[1] = Inf
114+
115+
@test @inferred(minimum_finite(A)) == minimum(A[2:end])
116+
@test @inferred(maximum_finite(A)) == maximum(A[2:end])
117+
@test minimum_finite(abs, A) == minimum(abs, A[2:end])
118+
@test maximum_finite(abs2, A) == maximum(abs2, A[2:end])
119+
end
120+
end
121+
122+
# minimum_finite and maximum_finite for RGB are processed per channel
123+
A = rand(RGB{Float32}, 5, 5)
124+
@test minimum_finite(A) == RGB(minimum(channelview(A), dims=(2, 3))...)
125+
@test maximum_finite(A) == RGB(maximum(channelview(A), dims=(2, 3))...)
126+
127+
# Container of abstract type
128+
A = Any[1, 2, Gray(0.3)]
129+
@test minimum_finite(A) == 0.3
130+
@test maximum_finite(A) == 2.0
131+
@test Base.return_types(minimum_finite, (typeof(A),)) == Base.return_types(minimum, (typeof(A), ))
132+
end
133+
54134
end

test/testutils.jl

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# AbstractGray and Color3 tests should be generated seperately
2+
function generate_test_types(number_types::AbstractArray{<:DataType}, color_types::AbstractArray{<:UnionAll})
3+
test_types = map(Iterators.product(number_types, color_types)) do T
4+
try
5+
T[2]{T[1]}
6+
catch err
7+
!isa(err, TypeError) && rethrow(err)
8+
end
9+
end
10+
test_types = filter(x->x != false, test_types)
11+
if isempty(filter(x->x<:Color3, test_types))
12+
test_types = [number_types..., test_types...]
13+
end
14+
test_types
15+
end

0 commit comments

Comments
 (0)