Skip to content

Commit 000a4d1

Browse files
timholyjohnnychen94
andcommitted
🧹 Move *finite stats functions to ImageBase
This commit moves some of the basic stats-related functions to ImageBase so that other packages don't need to depend on the large Images dependency. A few deprecations are involved to support the more generic version. See also: JuliaImages/ImageBase.jl#17 Co-authored-by: JohnnyChen <[email protected]>
1 parent 503411d commit 000a4d1

File tree

6 files changed

+53
-195
lines changed

6 files changed

+53
-195
lines changed

‎Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
88
FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
99
Graphics = "a2bd30eb-e257-5431-a919-1863eab51364"
1010
ImageAxes = "2803e5a7-5153-5ecf-9a86-9b4c37f5f5ac"
11+
ImageBase = "c817782e-172a-44cc-b673-b171935fbb9e"
1112
ImageContrastAdjustment = "f332f351-ec65-5f6a-b3d1-319c6670881a"
1213
ImageCore = "a09fc81d-aa75-5fe9-8630-4744c3626534"
1314
ImageDistances = "51556ac3-7006-55f5-8cb3-34580c88182d"
@@ -34,6 +35,7 @@ AxisArrays = "0.3, 0.4"
3435
FileIO = "1"
3536
Graphics = "0.4, 1.0"
3637
ImageAxes = "0.6"
38+
ImageBase = "0.1.5"
3739
ImageContrastAdjustment = "0.3.3"
3840
ImageCore = "0.9.3"
3941
ImageDistances = "0.2.5"

‎src/Images.jl

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ using SparseArrays: findnz
2626

2727
using Reexport
2828
@reexport using ImageCore
29+
@reexport using ImageBase
2930
if isdefined(ImageCore, :permuteddimsview)
3031
export permuteddimsview
3132
end
@@ -127,9 +128,6 @@ export
127128
copyproperties,
128129
data,
129130
height,
130-
maxabsfinite,
131-
maxfinite,
132-
minfinite,
133131
nimages,
134132
pixelspacing,
135133
properties,
@@ -180,7 +178,6 @@ export
180178

181179
magnitude,
182180
magnitude_phase,
183-
meanfinite,
184181
entropy,
185182
orientation,
186183
padarray,

‎src/algorithms.jl

Lines changed: 7 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -5,77 +5,6 @@ using Statistics: mean, var
55
import AxisArrays
66
using ImageMorphology: dilate!, erode!
77

8-
# Compat.@dep_vectorize_2arg Gray atan2
9-
# Compat.@dep_vectorize_2arg Gray hypot
10-
11-
"""
12-
`M = meanfinite(img, region)` calculates the mean value along the dimensions listed in `region`, ignoring any non-finite values.
13-
"""
14-
meanfinite(A::AbstractArray{T}, region) where {T<:Real} = _meanfinite(A, T, region)
15-
meanfinite(A::AbstractArray{CT}, region) where {CT<:Colorant} = _meanfinite(A, eltype(CT), region)
16-
function _meanfinite(A::AbstractArray, ::Type{T}, region) where T<:AbstractFloat
17-
inds = Base.reduced_indices(A, region)
18-
K = similar(Array{Int}, inds)
19-
S = similar(Array{eltype(A)}, inds)
20-
fill!(K, 0)
21-
fill!(S, zero(eltype(A)))
22-
sumfinite!(S, K, A)
23-
S./K
24-
end
25-
_meanfinite(A::AbstractArray, ::Type, region) = mean(A, dims=region) # non floating-point
26-
27-
using Base: check_reducedims, reducedim1, safe_tail
28-
using Base.Broadcast: newindex
29-
30-
"""
31-
sumfinite!(S, K, A)
32-
33-
Compute the sum `S` and number of contributing pixels `K` for
34-
reductions of the array `A` over dimensions. `S` and `K` must have
35-
identical indices, and either match `A` or have singleton-dimensions for
36-
the dimensions that are being summed over. Only pixels with finite
37-
value are included in the tallies of `S` and `K`.
38-
39-
Note that the pixel mean is just S./K.
40-
"""
41-
function sumfinite!(S, K, A::AbstractArray{T,N}) where {T,N}
42-
check_reducedims(S, A)
43-
isempty(A) && return S, K
44-
axes(S) == axes(K) || throw(DimensionMismatch("S and K must have identical axes"))
45-
46-
indsAt, indsSt = safe_tail(axes(A)), safe_tail(axes(S))
47-
keep, Idefault = _newindexer(indsSt)
48-
if reducedim1(S, A)
49-
# keep the accumulators as a local variable when reducing along the first dimension
50-
i1 = first(axes1(S))
51-
@inbounds for IA in CartesianIndices(indsAt)
52-
IS = newindex(IA, keep, Idefault)
53-
s, k = S[i1,IS], K[i1,IS]
54-
for i in axes(A, 1)
55-
tmp = A[i, IA]
56-
if isfinite(tmp)
57-
s += tmp
58-
k += 1
59-
end
60-
end
61-
S[i1,IS], K[i1,IS] = s, k
62-
end
63-
else
64-
@inbounds for IA in CartesianIndices(indsAt)
65-
IS = newindex(IA, keep, Idefault)
66-
for i in axes(A, 1)
67-
tmp = A[i, IA]
68-
if isfinite(tmp)
69-
S[i, IS] += tmp
70-
K[i, IS] += 1
71-
end
72-
end
73-
end
74-
end
75-
S, K
76-
end
77-
_newindexer(ax) = Base.Broadcast.shapeindexer(ax)
78-
798
function Statistics.var(A::AbstractArray{C}; kwargs...) where C<:AbstractGray
809
imgc = channelview(A)
8110
base_colorant_type(C)(var(imgc; kwargs...))
@@ -143,88 +72,6 @@ end
14372

14473
entropy(img::AbstractArray{C}; kind=:shannon) where {C<:AbstractGray} = entropy(channelview(img), kind=kind)
14574

146-
"""
147-
`m = minfinite(A)` calculates the minimum value in `A`, ignoring any values that are not finite (Inf or NaN).
148-
"""
149-
function minfinite(A::AbstractArray{T}) where T
150-
ret = sentinel_min(T)
151-
for a in A
152-
ret = minfinite_scalar(a, ret)
153-
end
154-
ret
155-
end
156-
function minfinite(f, A::AbstractArray)
157-
ret = sentinel_min(typeof(f(first(A))))
158-
for a in A
159-
ret = minfinite_scalar(f(a), ret)
160-
end
161-
ret
162-
end
163-
164-
"""
165-
`m = maxfinite(A)` calculates the maximum value in `A`, ignoring any values that are not finite (Inf or NaN).
166-
"""
167-
function maxfinite(A::AbstractArray{T}) where T
168-
ret = sentinel_max(T)
169-
for a in A
170-
ret = maxfinite_scalar(a, ret)
171-
end
172-
ret
173-
end
174-
function maxfinite(f, A::AbstractArray)
175-
ret = sentinel_max(typeof(f(first(A))))
176-
for a in A
177-
ret = maxfinite_scalar(f(a), ret)
178-
end
179-
ret
180-
end
181-
182-
"""
183-
`m = maxabsfinite(A)` calculates the maximum absolute value in `A`, ignoring any values that are not finite (Inf or NaN).
184-
"""
185-
function maxabsfinite(A::AbstractArray{T}) where T
186-
# ColorVectorSpace v0.9 un-defines abs/abs2
187-
# https://github.com/JuliaGraphics/ColorVectorSpace.jl/pull/131
188-
_abs(a) = mapreducec(abs, +, 0, a)
189-
ret = sentinel_min(typeof(_abs(A[1])))
190-
for a in A
191-
ret = maxfinite_scalar(_abs(a), ret)
192-
end
193-
ret
194-
end
195-
196-
minfinite_scalar(a::T, b::T) where {T} = isfinite(a) ? (b < a ? b : a) : b
197-
maxfinite_scalar(a::T, b::T) where {T} = isfinite(a) ? (b > a ? b : a) : b
198-
minfinite_scalar(a::T, b::T) where {T<:Union{Integer,FixedPoint}} = b < a ? b : a
199-
maxfinite_scalar(a::T, b::T) where {T<:Union{Integer,FixedPoint}} = b > a ? b : a
200-
minfinite_scalar(a, b) = minfinite_scalar(promote(a, b)...)
201-
maxfinite_scalar(a, b) = maxfinite_scalar(promote(a, b)...)
202-
203-
function minfinite_scalar(c1::C, c2::C) where C<:AbstractRGB
204-
C(minfinite_scalar(c1.r, c2.r),
205-
minfinite_scalar(c1.g, c2.g),
206-
minfinite_scalar(c1.b, c2.b))
207-
end
208-
function maxfinite_scalar(c1::C, c2::C) where C<:AbstractRGB
209-
C(maxfinite_scalar(c1.r, c2.r),
210-
maxfinite_scalar(c1.g, c2.g),
211-
maxfinite_scalar(c1.b, c2.b))
212-
end
213-
214-
sentinel_min(::Type{T}) where {T<:Union{Integer,FixedPoint}} = typemax(T)
215-
sentinel_max(::Type{T}) where {T<:Union{Integer,FixedPoint}} = typemin(T)
216-
sentinel_min(::Type{T}) where {T<:AbstractFloat} = convert(T, NaN)
217-
sentinel_max(::Type{T}) where {T<:AbstractFloat} = convert(T, NaN)
218-
sentinel_min(::Type{C}) where {C<:AbstractRGB} = _sentinel_min(C, eltype(C))
219-
_sentinel_min(::Type{C},::Type{T}) where {C<:AbstractRGB,T} = (s = sentinel_min(T); C(s,s,s))
220-
sentinel_max(::Type{C}) where {C<:AbstractRGB} = _sentinel_max(C, eltype(C))
221-
_sentinel_max(::Type{C},::Type{T}) where {C<:AbstractRGB,T} = (s = sentinel_max(T); C(s,s,s))
222-
sentinel_min(::Type{C}) where {C<:AbstractGray} = _sentinel_min(C, eltype(C))
223-
_sentinel_min(::Type{C},::Type{T}) where {C<:AbstractGray,T} = C(sentinel_min(T))
224-
sentinel_max(::Type{C}) where {C<:AbstractGray} = _sentinel_max(C, eltype(C))
225-
_sentinel_max(::Type{C},::Type{T}) where {C<:AbstractGray,T} = C(sentinel_max(T))
226-
227-
22875
# FIXME: replace with IntegralImage
22976
# average filter
23077
"""
@@ -269,7 +116,7 @@ macro test_approx_eq_sigma_eps(A, B, sigma, eps)
269116
kern = KernelFactors.IIRGaussian($(esc(sigma)))
270117
Af = imfilter($(esc(A)), kern, NA())
271118
Bf = imfilter($(esc(B)), kern, NA())
272-
diffscale = max(maxabsfinite($(esc(A))), maxabsfinite($(esc(B))))
119+
diffscale = max(_abs(maximum_finite(abs, $(esc(A)))), _abs(maximum_finite(abs, $(esc(B)))))
273120
d = sad(Af, Bf)
274121
if d > length(Af)*diffscale*($(esc(eps)))
275122
error("Arrays A and B differ")
@@ -305,7 +152,7 @@ function test_approx_eq_sigma_eps(A::AbstractArray, B::AbstractArray,
305152
kern = KernelFactors.IIRGaussian(sigma)
306153
Af = imfilter(A, kern, NA())
307154
Bf = imfilter(B, kern, NA())
308-
diffscale = max(maxabsfinite(A), maxabsfinite(B))
155+
diffscale = max(_abs(maximum_finite(abs, A)), _abs(maximum_finite(abs, B)))
309156
d = sad(Af, Bf)
310157
diffpct = d / (length(Af) * diffscale)
311158
if diffpct > eps
@@ -314,6 +161,11 @@ function test_approx_eq_sigma_eps(A::AbstractArray, B::AbstractArray,
314161
diffpct
315162
end
316163

164+
# This should be removed when upstream ImageBase is updated
165+
# In ImageBase v0.1.3: `maxabsfinite` returns a RGB instead of a Number
166+
_abs(c::CT) where CT<:Color = mapreducec(abs, +, zero(eltype(CT)), c)
167+
_abs(c::Number) = abs(c)
168+
317169
"""
318170
BlobLoG stores information about the location of peaks as discovered by `blob_LoG`.
319171
It has fields:

‎test/algorithms.jl

Lines changed: 5 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -76,45 +76,14 @@ using Test, Suppressor
7676
@test all([entropy(img3, kind=kind) for kind in [:shannon,:nat,:hartley]] .≥ 0)
7777
end
7878

79-
@testset "Reductions" begin
80-
A = rand(5,5,3)
81-
img = colorview(RGB, PermutedDimsArray(A, (3,1,2)))
82-
s12 = sum(img, dims=(1,2))
83-
@test eltype(s12) <: RGB
84-
A = [NaN, 1, 2, 3]
85-
@test meanfinite(A, 1) ≈ [2]
86-
A = [NaN 1 2 3;
87-
NaN 6 5 4]
88-
mf = meanfinite(A, 1)
89-
@test isnan(mf[1])
90-
@test mf[1,2:end] ≈ [3.5,3.5,3.5]
91-
@test meanfinite(A, 2) ≈ reshape([2, 5], 2, 1)
92-
@test meanfinite(A, (1,2)) ≈ [3.5]
93-
@test minfinite(A) == 1
94-
@test maxfinite(A) == 6
95-
@test maxabsfinite(A) == 6
96-
A = rand(10:20, 5, 5)
97-
@test minfinite(A) == minimum(A)
98-
@test maxfinite(A) == maximum(A)
99-
A = reinterpret(N0f8, rand(0x00:0xff, 5, 5))
100-
@test minfinite(A) == minimum(A)
101-
@test maxfinite(A) == maximum(A)
102-
A = rand(Float32,3,5,5)
103-
img = colorview(RGB, A)
104-
dc = meanfinite(img, 1)-reshape(reinterpretc(RGB{Float32}, mean(A, dims=2)), (1,5))
105-
_abs(x::Colorant) = mapreducec(abs, +, 0, x)
106-
@test maximum(map(_abs, dc)) < 1e-6
107-
dc = minfinite(img)-RGB{Float32}(minimum(A, dims=(2,3))...)
108-
@test _abs(dc) < 1e-6
109-
dc = maxfinite(img)-RGB{Float32}(maximum(A, dims=(2,3))...)
110-
@test _abs(dc) < 1e-6
11179

80+
@testset "Reductions" begin
11281
a = rand(15,15)
11382
@test_throws ErrorException (@test_approx_eq_sigma_eps a rand(13,15) [1,1] 0.01)
11483
@test_throws ErrorException (@test_approx_eq_sigma_eps a rand(15,15) [1,1] 0.01)
11584
@test (@test_approx_eq_sigma_eps a a [1,1] 0.01) == nothing
116-
@test (@test_approx_eq_sigma_eps a a+0.01*rand(Float64,size(a)) [1,1] 0.01) == nothing
117-
@test_throws ErrorException (@test_approx_eq_sigma_eps a a+0.5*rand(Float64,size(a)) [1,1] 0.01)
85+
@test (@test_approx_eq_sigma_eps a a+0.01*rand(eltype(a),size(a)) [1,1] 0.01) == nothing
86+
@test_throws ErrorException (@test_approx_eq_sigma_eps a a+0.5*rand(eltype(a),size(a)) [1,1] 0.01)
11887
a = colorview(RGB, rand(3,15,15))
11988
@test (@test_approx_eq_sigma_eps a a [1,1] 0.01) == nothing
12089
@test_throws ErrorException (@test_approx_eq_sigma_eps a colorview(RGB, rand(3,15,15)) [1,1] 0.01)
@@ -123,8 +92,8 @@ using Test, Suppressor
12392
@test_throws ErrorException Images.test_approx_eq_sigma_eps(a, rand(13,15), [1,1], 0.01)
12493
@test_throws ErrorException Images.test_approx_eq_sigma_eps(a, rand(15,15), [1,1], 0.01)
12594
@test Images.test_approx_eq_sigma_eps(a, a, [1,1], 0.01) == 0.0
126-
@test Images.test_approx_eq_sigma_eps(a, a+0.01*rand(Float64,size(a)), [1,1], 0.01) > 0.0
127-
@test_throws ErrorException Images.test_approx_eq_sigma_eps(a, a+0.5*rand(Float64,size(a)), [1,1], 0.01)
95+
@test Images.test_approx_eq_sigma_eps(a, a+0.01*rand(eltype(a),size(a)), [1,1], 0.01) > 0.0
96+
@test_throws ErrorException Images.test_approx_eq_sigma_eps(a, a+0.5*rand(eltype(a),size(a)), [1,1], 0.01)
12897
a = colorview(RGB, rand(3,15,15))
12998
@test Images.test_approx_eq_sigma_eps(a, a, [1,1], 0.01) == 0.0
13099
@test_throws ErrorException Images.test_approx_eq_sigma_eps(a, colorview(RGB, rand(3,15,15)), [1,1], 0.01)

‎test/legacy.jl

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,39 @@
1717
@test complement.([RGBA{N0f8}(0,0.6,1,0.7)]) == [RGBA{N0f8}(1.0,0.4,0.0,0.7)]
1818
end
1919

20+
# Moved to ImageBase
21+
@testset "Reductions" begin
22+
_abs(x::Colorant) = mapreducec(abs, +, 0, x)
23+
24+
A = rand(5,5,3)
25+
img = colorview(RGB, PermutedDimsArray(A, (3,1,2)))
26+
s12 = sum(img, dims=(1,2))
27+
@test eltype(s12) <: RGB
28+
A = [NaN, 1, 2, 3]
29+
@test meanfinite(A, 1) ≈ [2]
30+
A = [NaN 1 2 3;
31+
NaN 6 5 4]
32+
mf = meanfinite(A, 1)
33+
@test isnan(mf[1])
34+
@test mf[1,2:end] ≈ [3.5,3.5,3.5]
35+
@test meanfinite(A, 2) ≈ reshape([2, 5], 2, 1)
36+
@test meanfinite(A, (1,2)) ≈ [3.5]
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, 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
54+
end
2055
end

‎test/runtests.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
using Images
2+
using OffsetArrays
3+
using Statistics
14
using Test
25
using Suppressor
36

0 commit comments

Comments
 (0)