Skip to content

Commit 8af5919

Browse files
committed
WIP: introduce log gabor filter
1 parent a628c43 commit 8af5919

File tree

1 file changed

+125
-1
lines changed

1 file changed

+125
-1
lines changed

src/gaborkernels.jl

Lines changed: 125 additions & 1 deletion
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...)
@@ -148,6 +148,130 @@ end
148148
return exp(-(xr^2 + yr^2)/(2σ^2)) * cis(xr*λ_scaled + ψ)
149149
end
150150

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

153277
function _to_axes(sz::Dims)

0 commit comments

Comments
 (0)