Skip to content

Commit 51fd8f3

Browse files
committed
Use new stateful filter API for filtfilt
This provides reduces the memory allocation and provides a small speed boost (on 0.4; on 0.3, where SubArrays are slow, it's probably substantially slower). We could conceivably use this to provide in-place filtfilt, although that is not yet implemented. I removed support for non-1 initial values of a in filt_stepstate, since we don't need it if the filter is normalized before it is used.
1 parent 74a06e7 commit 51fd8f3

File tree

2 files changed

+73
-86
lines changed

2 files changed

+73
-86
lines changed

src/Filters/filt.jl

Lines changed: 68 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -183,52 +183,64 @@ DF2TFilter(coef::FilterCoefficients) = DF2TFilter(convert(SecondOrderSections, c
183183
# filtfilt
184184
#
185185

186-
# Extrapolate the beginning of a signal for use by filtfilt. This
187-
# computes:
188-
#
189-
# [(2 * x[1]) .- x[pad_length+1:-1:2],
190-
# x,
191-
# (2 * x[end]) .- x[end-1:-1:end-pad_length]]
192-
#
193-
# in place in output. The istart and n parameters determine the portion
194-
# of the input signal x to extrapolate.
195-
function extrapolate_signal!(out, ostart, sig, istart, n, pad_length)
196-
length(out) >= n+2*pad_length || error("output is incorrectly sized")
197-
x = 2*sig[istart]
186+
# Extrapolate the beginning of a signal for use by filtfilt. Computes:
187+
# [(2 * x[1]) .- x[pad_length+1:-1:2]]
188+
# in place in output.
189+
function extrapolate_start!(out, sig)
190+
x = 2*sig[1]
191+
pad_length = length(out)
198192
for i = 1:pad_length
199-
out[ostart+i-1] = x - sig[istart+pad_length+1-i]
193+
out[i] = x - sig[pad_length+2-i]
200194
end
201-
copy!(out, ostart+pad_length, sig, istart, n)
202-
x = 2*sig[istart+n-1]
203-
for i = 1:pad_length
204-
out[ostart+n+pad_length+i-1] = x - sig[istart+n-1-i]
195+
out
196+
end
197+
198+
# Extrapolate the end of a signal for use by filtfilt. Computes:
199+
# (2 * x[end]) .- x[end-1:-1:end-pad_length]]
200+
# in place in output.
201+
function extrapolate_end!(out, sig)
202+
x = 2*sig[end]
203+
for i = 1:length(out)
204+
out[i] = x - sig[end-i]
205205
end
206206
out
207207
end
208208

209-
# Zero phase digital filtering by processing data in forward and reverse direction
210-
function iir_filtfilt(b::AbstractVector, a::AbstractVector, x::AbstractArray)
211-
zi = filt_stepstate(b, a)
212-
zitmp = copy(zi)
213-
pad_length = 3 * (max(length(a), length(b)) - 1)
214-
t = Base.promote_eltype(b, a, x)
215-
extrapolated = Array(t, size(x, 1)+pad_length*2)
209+
padlength(coefs::PolynomialRatio) = 3 * (max(length(coefs.a), length(coefs.b)) - 1)
210+
padlength(coefs::SecondOrderSections) = 6 * length(coefs.biquads)
211+
function filtfilt{T}(coefs::Union(PolynomialRatio{T}, SecondOrderSections{T}), x::AbstractArray)
212+
zi = filt_stepstate(coefs)
213+
zitmp = similar(zi)
214+
t = Base.promote_eltype(T, x)
215+
extrapolated = Array(t, padlength(coefs))
216216
out = similar(x, t)
217+
f = DF2TFilter(coefs, zitmp)
217218

218-
istart = 1
219219
for i = 1:Base.trailingsize(x, 2)
220-
extrapolate_signal!(extrapolated, 1, x, istart, size(x, 1), pad_length)
221-
reverse!(filt!(extrapolated, b, a, extrapolated, scale!(zitmp, zi, extrapolated[1])))
222-
filt!(extrapolated, b, a, extrapolated, scale!(zitmp, zi, extrapolated[1]))
223-
for j = 1:size(x, 1)
224-
@inbounds out[j, i] = extrapolated[end-pad_length+1-j]
225-
end
226-
istart += size(x, 1)
220+
# Forward pass
221+
sigout = sub(out, :, i)
222+
sig = sub(x, :, i)
223+
extrapolate_start!(extrapolated, sig)
224+
scale!(zitmp, zi, extrapolated[1])
225+
filt!(extrapolated, f, extrapolated)
226+
filt!(sigout, f, sig)
227+
extrapolate_end!(extrapolated, sig)
228+
filt!(extrapolated, f, extrapolated)
229+
230+
# Reverse pass
231+
sigoutrev = sub(out, size(x, 1):-1:1, i)
232+
reverse!(extrapolated)
233+
scale!(zitmp, zi, extrapolated[1])
234+
filt!(extrapolated, f, extrapolated)
235+
filt!(sigoutrev, f, sigoutrev)
227236
end
228237

229238
out
230239
end
231240

241+
# Support for other filter types
242+
filtfilt(f::FilterCoefficients, x) = filtfilt(convert(SecondOrderSections, f), x)
243+
232244
# Zero phase digital filtering with an FIR filter in a single pass
233245
function fir_filtfilt(b::AbstractVector, x::AbstractArray)
234246
nb = length(b)
@@ -245,7 +257,10 @@ function fir_filtfilt(b::AbstractVector, x::AbstractArray)
245257

246258
# Extrapolate each column
247259
for i = 1:size(extrapolated, 2)
248-
extrapolate_signal!(extrapolated, (i-1)*size(extrapolated, 1)+1, x, (i-1)*size(x, 1)+1, size(x, 1), nb-1)
260+
sig = sub(x, :, i)
261+
extrapolate_start!(sub(extrapolated, 1:nb-1, i), sig)
262+
copy!(extrapolated, (i-1)*size(extrapolated, 1)+nb, x, (i-1)*size(x, 1)+1, size(x, 1))
263+
extrapolate_end!(sub(extrapolated, size(extrapolated, 1)-nb+2:size(extrapolated, 1), i), sig)
249264
end
250265

251266
# Filter
@@ -255,73 +270,45 @@ function fir_filtfilt(b::AbstractVector, x::AbstractArray)
255270
reshape(out[2nb-1:end, :], size(x))
256271
end
257272

258-
# Choose whether to use fir_filtfilt or iir_filtfilt depending on
259-
# length of a
260-
function filtfilt(b::AbstractVector, a::AbstractVector, x::AbstractArray)
273+
# Support for passing b and a
274+
function filtfilt(b::AbstractVector, a::AbstractVector, x)
261275
if length(a) == 1
262276
if a[1] != 1
263277
b /= a[1]
264278
end
265279
fir_filtfilt(b, x)
266280
else
267-
iir_filtfilt(b, a, x)
268-
end
269-
end
281+
bs = length(b)
282+
as = length(a)
283+
sz = max(bs, as)
270284

271-
# Zero phase digital filtering for second order sections
272-
function filtfilt{T,G,S}(f::SecondOrderSections{T,G}, x::AbstractArray{S})
273-
zi = filt_stepstate(f)
274-
zitmp = similar(zi)
275-
pad_length = 6 * length(f.biquads)
276-
t = Base.promote_type(T, G, S)
277-
extrapolated = Array(t, size(x, 1)+pad_length*2)
278-
out = similar(x, t)
285+
# Pad the coefficients with zeros if needed
286+
bs<sz && (b = copy!(zeros(eltype(b), sz), b))
287+
as<sz && (a = copy!(zeros(eltype(a), sz), a))
288+
sz > 0 || error("a and b must have at least one element each")
279289

280-
istart = 1
281-
for i = 1:Base.trailingsize(x, 2)
282-
extrapolate_signal!(extrapolated, 1, x, istart, size(x, 1), pad_length)
283-
reverse!(filt!(extrapolated, f, extrapolated, scale!(zitmp, zi, extrapolated[1])))
284-
filt!(extrapolated, f, extrapolated, scale!(zitmp, zi, extrapolated[1]))
285-
for j = 1:size(x, 1)
286-
@inbounds out[j, i] = extrapolated[end-pad_length+1-j]
287-
end
288-
istart += size(x, 1)
290+
filtfilt(PolynomialRatio(b, a), x)
289291
end
290-
291-
out
292292
end
293293

294-
# Support for other filter types
295-
filtfilt(f::FilterCoefficients, x) = filtfilt(convert(SecondOrderSections, f), x)
296-
filtfilt(f::PolynomialRatio, x) = filtfilt(coefb(f), coefa(f), x)
297-
298294
## Initial filter state
299295

300296
# Compute an initial state for filt with coefficients (b,a) such that its
301297
# response to a step function is steady state.
302-
function filt_stepstate{T<:Number}(b::Union(AbstractVector{T}, T), a::Union(AbstractVector{T}, T))
303-
scale_factor = a[1]
304-
if scale_factor != 1.0
305-
a = a ./ scale_factor
306-
b = b ./ scale_factor
307-
end
308-
309-
bs = length(b)
310-
as = length(a)
311-
sz = max(bs, as)
312-
sz > 0 || error("a and b must have at least one element each")
313-
sz == 1 && return T[]
314-
315-
# Pad the coefficients with zeros if needed
316-
bs<sz && (b = copy!(zeros(eltype(b), sz), b))
317-
as<sz && (a = copy!(zeros(eltype(a), sz), a))
318-
319-
# construct the companion matrix A and vector B:
298+
function filt_stepstate{T<:Number}(f::PolynomialRatio{T})
299+
b = coefb(f)
300+
a = coefa(f)
301+
sz = length(b)
302+
sz == length(a) || throw(ArgumentError("a and b must have same length"))
303+
a[1] == 1 || throw(ArgumentError("first element of a must be 1"))
304+
max(length(b), length(a)) == 1 && return T[]
305+
306+
# Construct the companion matrix A and vector B:
320307
A = [-a[2:end] [eye(T, sz-2); zeros(T, 1, sz-2)]]
321308
B = b[2:end] - a[2:end] * b[1]
322309
# Solve si = A*si + B
323310
# (I - A)*si = B
324-
scale_factor \ (I - A) \ B
311+
(I - A) \ B
325312
end
326313

327314
function filt_stepstate{T}(f::SecondOrderSections{T})

test/filt.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ zi_python = [ 0.99672078, -1.49409147, 1.28412268, -0.45244173, 0.07559489]
7070
b = [ 0.00327922, 0.01639608, 0.03279216, 0.03279216, 0.01639608, 0.00327922]
7171
a = [ 1. , -2.47441617, 2.81100631, -1.70377224, 0.54443269, -0.07231567]
7272

73-
@test_approx_eq_eps zi_python DSP.Filters.filt_stepstate(b, a) 1e-7
73+
@test_approx_eq_eps zi_python DSP.Filters.filt_stepstate(PolynomialRatio(b, a)) 1e-7
7474

7575

7676
##############
@@ -88,7 +88,7 @@ zi_matlab = [0.6580, 0.5184]
8888
b = [0.222, 0.43, 0.712]
8989
a = [1, 0.33, 0.22]
9090

91-
@test_approx_eq zi_matlab DSP.Filters.filt_stepstate(b, a)
91+
@test_approx_eq zi_matlab DSP.Filters.filt_stepstate(PolynomialRatio(b, a))
9292

9393

9494
##############
@@ -107,7 +107,7 @@ zi_python = [0.55996501, -0.72343165, 0.68312446, -0.2220676 , 0.04030775]
107107
b = [ 0.00327922, 0.01639608, 0.03279216, 0.03279216, 0.01639608, 0.00327922]
108108
a = [ 1.1 , -2.47441617, 2.81100631, -1.70377224, 0.54443269, -0.07231567]
109109

110-
@test_approx_eq_eps zi_python DSP.Filters.filt_stepstate(b, a) 1e-7
110+
@test_approx_eq_eps zi_python 1.1 * DSP.Filters.filt_stepstate(PolynomialRatio(b, a)) 1e-7
111111

112112

113113
##############
@@ -239,6 +239,6 @@ end
239239

240240
b = randn(10)
241241
for x in (randn(100), randn(100, 2))
242-
@test_approx_eq DSP.Filters.fir_filtfilt(b, x) DSP.Filters.iir_filtfilt(b, [1.0], x)
243-
@test_approx_eq DSP.Filters.filtfilt(b, [2.0], x) DSP.Filters.iir_filtfilt(b, [2.0], x)
242+
@test_approx_eq DSP.Filters.fir_filtfilt(b, x) DSP.Filters.filtfilt(PolynomialRatio(b, [1.0; zeros(9)]), x)
243+
@test_approx_eq DSP.Filters.filtfilt(b, [2.0], x) DSP.Filters.filtfilt(PolynomialRatio(b, [2.0; zeros(9)]), x)
244244
end

0 commit comments

Comments
 (0)