Skip to content

Commit 0df8e1f

Browse files
authored
Add freqkernel and spacekernel (#100)
1 parent 61b04b4 commit 0df8e1f

File tree

3 files changed

+92
-4
lines changed

3 files changed

+92
-4
lines changed

src/ImageFiltering.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ export Kernel, KernelFactors,
1414
Algorithm,
1515
imfilter, imfilter!,
1616
mapwindow, mapwindow!,
17-
imgradients, padarray, centered, kernelfactors, reflect
17+
imgradients, padarray, centered, kernelfactors, reflect,
18+
freqkernel, spacekernel
1819

1920
FixedColorant{T<:Normed} = Colorant{T}
2021
StaticOffsetArray{T,N,A<:StaticArray} = OffsetArray{T,N,A}

src/utils.jl

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
centered(kernel) -> shiftedkernel
2+
shiftedkernel = centered(kernel)
33
44
Shift the origin-of-coordinates to the center of `kernel`. The
55
center-element of `kernel` will be accessed by `shiftedkernel[0, 0,
@@ -13,6 +13,71 @@ See also: [`imfilter`](@ref).
1313
"""
1414
centered(A::AbstractArray) = OffsetArray(A, map(n->-((n+1)>>1), size(A)))
1515

16+
"""
17+
kernfft = freqkernel([T::Type], kern, sz=size(kern); rfft=false)
18+
19+
Return a frequency-space representation of `kern`.
20+
This embeds `kern` in an array of size `sz`, in a manner that implicitly imposes periodic boundary
21+
conditions, and then returns the fourier transform. This is sometimes called the optical transfer
22+
function, and known in some frameworks as `psf2otf`. If `rfft` is `true`, the fft for real-valued
23+
arrays (`rfft`) is returned instead and the first dimension size will be approximately half of `sz[1]`.
24+
25+
`kern` should be zero-centered, i.e., `kern[0, 0]` should reference the center of your kernel.
26+
See [`centered`](@ref). Optionally specify the numeric type `T` (which must be one of the
27+
types supported by FFTW, either `Float32` or `Float64`).
28+
29+
The inverse of `freqkernel` is [`spacekernel`](@ref).
30+
"""
31+
function freqkernel(::Type{T}, kern::AbstractArray, sz::Dims=size(kern); rfft=false) where T<:Union{Float32,Float64}
32+
wrapindex(i, s) = i<1 ? i+s : i
33+
all(size(kern) .<= sz) || throw(DimensionMismatch("kernel size $(size(kern)) is bigger than supplied size $sz"))
34+
kernw = zeros(T, sz...)
35+
for I in CartesianIndices(kern)
36+
J = CartesianIndex(map(wrapindex, Tuple(I), sz))
37+
kernw[J] = kern[I]
38+
end
39+
return rfft ? FFTW.rfft(kernw) : fft(kernw)
40+
end
41+
freqkernel(kern::AbstractArray{T}, args...; rfft=false) where T =
42+
freqkernel(ffteltype(T), kern, args...; rfft=rfft)
43+
44+
"""
45+
kern = spacekernel(kernfft, axs; rfftsz=0)
46+
47+
Return a real-space representation of `kernfft`, a frequency-space representation of a kernel.
48+
This performs an inverse fourier transform, implicitly imposes periodic boundary conditions,
49+
and then trims & truncates axes of the output to `axs`. By default `kernfft` is assumed to have
50+
been generated by `fft`; if it was instead generated by `rfft`, specify the original size
51+
of the first dimension. (If `kernfft` was generated by [`freqkernel`](@ref), this is just `sz[1]`.)
52+
53+
The inverse of `spacekernel` is [`freqkernel`](@ref).
54+
"""
55+
function spacekernel(kernfft::AbstractArray, axs::Indices; rfftsz=0)
56+
wrapindex(i, s) = i<1 ? i+s : i
57+
kernw = rfftsz > 0 ? irfft(kernfft, rfftsz) : ifft(kernfft)
58+
kern = zeros(eltype(kernw), axs...)
59+
sz = size(kernw)
60+
for I in CartesianIndices(kern)
61+
J = CartesianIndex(map(wrapindex, Tuple(I), sz))
62+
kern[I] = kernw[J]
63+
end
64+
return kern
65+
end
66+
function spacekernel(kernfft::AbstractArray; rfftsz=0)
67+
sz = size(kernfft)
68+
if rfftsz > 0
69+
sz = (rfftsz, Base.tail(sz)...)
70+
end
71+
upper = map(s->s>>1, sz)
72+
axs = map((u, s)-> u-s+1:u, upper, sz)
73+
return spacekernel(kernfft, axs; rfftsz=rfftsz)
74+
end
75+
76+
ffteltype(::Type{T}) where T<:Union{Float32,Float64} = T
77+
ffteltype(::Type{Float16}) = Float32
78+
ffteltype(::Type{T}) where T<:Normed = ffteltype(FixedPointNumbers.floattype(T))
79+
ffteltype(::Type{T}) where T = Float64
80+
1681
dummyind(::Base.OneTo) = Base.OneTo(1)
1782
dummyind(::AbstractUnitRange) = 0:0
1883

test/basic.jl

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using ImageFiltering, OffsetArrays, Logging, ImageMetadata, Test
1+
using ImageFiltering, OffsetArrays, Logging, ImageMetadata, FixedPointNumbers, Test
22
import AxisArrays
33
using AxisArrays: AxisArray, Axis
44

@@ -88,4 +88,26 @@ end
8888
check_range_axes(axs[1].val, -1, 1)
8989
end
9090

91-
nothing
91+
@testset "freqkernel/spacekernel" begin
92+
k = centered(reshape([1,-1], 2, 1))
93+
kfft = freqkernel(k, (31, 31))
94+
@test size(kfft) == (31, 31)
95+
k2 = real.(spacekernel(kfft, axes(k)))
96+
@test k2 k
97+
kfft = freqkernel(k, (31, 31); rfft=true)
98+
@test size(kfft) == (16, 31)
99+
k2 = spacekernel(kfft, axes(k); rfftsz=31)
100+
@test k2 k
101+
102+
for T in (Float64, Float32, Float16, N0f16)
103+
k = T.(Kernel.gaussian(3))
104+
kfft = freqkernel(k)
105+
@test size(kfft) == size(k)
106+
k2 = real.(spacekernel(kfft))
107+
@test T.(k2) k
108+
109+
kfft = freqkernel(k; rfft=true)
110+
k2 = spacekernel(kfft; rfftsz=size(k, 1))
111+
@test T.(k2) k
112+
end
113+
end

0 commit comments

Comments
 (0)