Skip to content

Commit 2b2d5c7

Browse files
committed
WIP: introduce log gabor filter
1 parent 285ae3c commit 2b2d5c7

File tree

2 files changed

+125
-1
lines changed

2 files changed

+125
-1
lines changed

docs/src/function_reference.md

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ Kernel.DoG
2424
Kernel.LoG
2525
Kernel.Laplacian
2626
Kernel.Gabor
27+
Kernel.LogGabor
28+
Kernel.LogGaborComplex
2729
Kernel.moffat
2830
```
2931

src/gaborkernels.jl

+123-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module GaborKernels
22

3-
export Gabor
3+
export Gabor, LogGabor, LogGaborComplex
44

55
"""
66
Gabor(kernel_size; kwargs...)
@@ -145,6 +145,128 @@ end
145145
return exp(-(xr^2 + yr^2)/(2σ^2)) * cis(xr*λ_scaled + ψ)
146146
end
147147

148+
149+
"""
150+
LogGaborComplex(kernel_size; ω, θ, σω=1, σθ=1)
151+
LogGaborComplex(kernel_axes; ω, θ, σω=1, σθ=1)
152+
153+
Generates the 2-D Log Gabor kernel in frequency space by `Complex(r, a)`, where `r` and `a`
154+
are the frequency and angular components, respectively.
155+
156+
More detailed documentation can be found in the `r * a` version [`LogGabor`](@ref).
157+
"""
158+
struct LogGaborComplex{T, TP,R<:AbstractUnitRange} <: AbstractMatrix{T}
159+
ax::Tuple{R,R}
160+
ω::TP
161+
θ::TP
162+
σω::TP
163+
σθ::TP
164+
165+
# cache values
166+
ω_denom::TP # 1/(2(log(σω/ω))^2)
167+
θ_denom::TP # 1/(2σθ^2)
168+
function LogGaborComplex{T,TP,R}(ax::Tuple{R,R}, ω::TP, θ::TP, σω::TP, σθ::TP) where {T,TP,R}
169+
σω > 0 || throw(ArgumentError("`σω` should be positive: $σω"))
170+
σθ > 0 || throw(ArgumentError("`σθ` should be positive: $σθ"))
171+
new{T,TP,R}(ax, ω, θ, σω, σθ, 1/(2(log(σω/ω))^2), 1/(2σθ^2))
172+
end
173+
end
174+
function LogGaborComplex(size_or_axes::Tuple; ω::Real, θ::Real, σω::Real=1, σθ::Real=1)
175+
params = float.(promote(ω, θ, σω, σθ))
176+
T = typeof(params[1])
177+
ax = _to_axes(size_or_axes)
178+
LogGaborComplex{Complex{T}, T, typeof(first(ax))}(ax, params...)
179+
end
180+
181+
@inline Base.size(kern::LogGaborComplex) = map(length, kern.ax)
182+
@inline Base.axes(kern::LogGaborComplex) = kern.ax
183+
184+
@inline function Base.getindex(kern::LogGaborComplex, x::Int, y::Int)
185+
ω_denom, θ_denom = kern.ω_denom, kern.θ_denom
186+
# normalize: from reference [1]
187+
x, y = (x, y)./size(kern)
188+
ω = sqrt(x^2 + y^2) # this is faster than hypot(x, y)
189+
θ = atan(y, x)
190+
r = exp((-(log/kern.ω))^2)*ω_denom) # radial component
191+
a = exp((--kern.θ).^2)*θ_denom) # angular component
192+
return r + a*im
193+
end
194+
195+
196+
"""
197+
LogGabor(kernel_size; ω, θ, σω=1, σθ=1)
198+
LogGabor(kernel_axes; ω, θ, σω=1, σθ=1)
199+
200+
Generates the 2-D Log Gabor kernel in frequency space by `r * a`, where `r` and `a` are the
201+
frequency and angular components, respectively.
202+
203+
See also [`LogGabor`](@ref) for the `Complex(r, a)` version.
204+
205+
# Arguments
206+
207+
- `kernel_size::Dims{2}`: the Log Gabor kernel size. The axes at each dimension will be
208+
`-r:r` if the size is odd.
209+
- `kernel_axes::NTuple{2, <:AbstractUnitRange}`: the axes of the Log Gabor kernel.
210+
211+
# Keywords
212+
213+
- `ω`: the center frequency.
214+
- `θ`: the center orientation.
215+
- `σω=1`: scale component for `ω`. Larger `σω` makes the filter more sensitive to center region.
216+
- `σθ=1`: scale component for `θ`. Larger `σθ` makes the filter less sensitive to orientation.
217+
218+
# Examples
219+
220+
To apply log gabor filter `g` on image `X`, one need to use convolution theorem, i.e.,
221+
`conv(A, K) == ifft(fft(A) .* fft(K))`. Because this `LogGabor` function generates Log Gabor
222+
kernel directly in frequency space, we don't need to apply `fft(K)` here:
223+
224+
```jldoctest
225+
julia> using ImageFiltering, FFTW, TestImages, ImageCore
226+
227+
julia> img = TestImages.shepp_logan(256);
228+
229+
julia> kern = Kernel.LogGabor(size(img); ω=1/6, θ=0);
230+
231+
julia> g_img = ifft(centered(fft(channelview(img))) .* fftshift(kern)); # apply convolution theorem
232+
233+
julia> @. Gray(abs(g_img));
234+
235+
```
236+
237+
# Extended help
238+
239+
Mathematically, log gabor filter is defined in frequency space as the product of
240+
its frequency component `r` and angular component `a`:
241+
242+
```math
243+
r(\\omega, \\theta) = \\exp(-\\frac{(\\log(\\omega/\\omega_0))^2}{2\\sigma_\\omega^2}) \\
244+
a(\\omega, \\theta) = \\exp(-\\frac{(\\theta - \\theta_0)^2}{2\\sigma_\\theta^2})
245+
```
246+
247+
# References
248+
249+
- [1] [What Are Log-Gabor Filters and Why Are They
250+
Good?](https://www.peterkovesi.com/matlabfns/PhaseCongruency/Docs/convexpl.html)
251+
- [2] Kovesi, Peter. "Image features from phase congruency." _Videre: Journal of computer
252+
vision research_ 1.3 (1999): 1-26.
253+
- [3] Field, David J. "Relations between the statistics of natural images and the response
254+
properties of cortical cells." _Josa a_ 4.12 (1987): 2379-2394.
255+
"""
256+
struct LogGabor{T, AT<:LogGaborComplex} <: AbstractMatrix{T}
257+
complex_data::AT
258+
end
259+
LogGabor(complex_data::AT) where AT<:LogGaborComplex = LogGabor{eltype(AT), AT}(complex_data)
260+
LogGabor(size_or_axes::Tuple; kwargs...) = LogGabor(LogGaborComplex(size_or_axes; kwargs...))
261+
262+
@inline Base.size(kern::LogGabor) = size(kern.complex_data)
263+
@inline Base.axes(kern::LogGabor) = axes(kern.complex_data)
264+
Base.@propagate_inbounds function Base.getindex(kern::LogGabor, inds::Int...)
265+
v = kern.complex_data[inds...]
266+
return real(v) * imag(v)
267+
end
268+
269+
148270
# Utils
149271

150272
function _to_axes(sz::Dims)

0 commit comments

Comments
 (0)