Skip to content

Commit 04e4593

Browse files
authored
Allow different argument eltypes for filt_stepstate (fix #573) (#574)
* Relax `filt_stepstate` argument types * set maximum `pad_length` for `iir_filtfilt` * directly use `_filt_iir!` avoids unnecessary allocations esp. if x has >1 column * Document `filtfilt(::AbstractVector...)` (fixes #573)
1 parent dcef684 commit 04e4593

File tree

2 files changed

+42
-32
lines changed

2 files changed

+42
-32
lines changed

src/Filters/filt.jl

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#
44
# filt and filt!
55
#
6-
6+
using ..DSP: _filt_iir!
77

88
## PolynomialRatio
99
_zerosi(f::PolynomialRatio{:z,T}, ::AbstractArray{S}) where {T,S} =
@@ -248,13 +248,13 @@ DF2TFilter(coef::FilterCoefficients{:z}, arg::Union{Matrix,Type}) =
248248
# in place in output. The istart and n parameters determine the portion
249249
# of the input signal x to extrapolate.
250250
function extrapolate_signal!(out, ostart, sig, istart, n, pad_length)
251-
length(out) >= n+2*pad_length || throw(ArgumentError("output is incorrectly sized"))
252-
x = 2*sig[istart]
253-
for i = 1:pad_length
254-
out[ostart+i-1] = x - sig[istart+pad_length+1-i]
251+
length(out) >= n + 2 * pad_length || throw(ArgumentError("output is incorrectly sized"))
252+
x = 2 * sig[istart]
253+
for i = 0:pad_length-1
254+
out[ostart+i] = x - sig[istart+pad_length-i]
255255
end
256256
copyto!(out, ostart+pad_length, sig, istart, n)
257-
x = 2*sig[istart+n-1]
257+
x = 2 * sig[istart+n-1]
258258
for i = 1:pad_length
259259
out[ostart+n+pad_length+i-1] = x - sig[istart+n-1-i]
260260
end
@@ -263,36 +263,39 @@ end
263263

264264
# Zero phase digital filtering by processing data in forward and reverse direction
265265
function iir_filtfilt(b::AbstractVector, a::AbstractVector, x::AbstractArray)
266-
zi = filt_stepstate(b, a)
267-
pad_length = 3 * (max(length(a), length(b)) - 1)
268-
t = Base.promote_eltype(b, a, x)
266+
pad_length = min(3 * (max(length(a), length(b)) - 1), size(x, 1) - 1)
267+
zi, bn, an = filt_stepstate(b, a)
268+
t = Base.promote_eltype(bn, an, x)
269269
zitmp = similar(zi, t)
270-
extrapolated = Vector{t}(undef, size(x, 1)+pad_length*2)
270+
extrapolated = Vector{t}(undef, size(x, 1) + 2 * pad_length)
271271
out = similar(x, t)
272272

273-
istart = 1
274273
for i = 1:Base.trailingsize(x, 2)
274+
istart = 1 + (i - 1) * size(x, 1)
275275
extrapolate_signal!(extrapolated, 1, x, istart, size(x, 1), pad_length)
276-
reverse!(filt!(extrapolated, b, a, extrapolated, mul!(zitmp, zi, extrapolated[1])))
277-
filt!(extrapolated, b, a, extrapolated, mul!(zitmp, zi, extrapolated[1]))
276+
_filt_iir!(extrapolated, bn, an, extrapolated, mul!(zitmp, zi, extrapolated[1]), 1)
277+
reverse!(extrapolated)
278+
_filt_iir!(extrapolated, bn, an, extrapolated, mul!(zitmp, zi, extrapolated[1]), 1)
278279
for j = 1:size(x, 1)
279-
@inbounds out[j, i] = extrapolated[end-pad_length+1-j]
280+
out[j, i] = extrapolated[end-pad_length+1-j]
280281
end
281-
istart += size(x, 1)
282282
end
283283

284284
out
285285
end
286286

287287
"""
288288
filtfilt(coef::FilterCoefficients, x::AbstractArray)
289+
filtfilt(b::AbstractVector, x::AbstractArray)
290+
filtfilt(b::AbstractVector, a::AbstractVector, x::AbstractArray)
289291
290-
Filter `x` in the forward and reverse directions using filter
291-
coefficients `coef`. The initial state of the filter is computed so
292+
Filter `x` in the forward and reverse directions using either a
293+
`FilterCoefficients` object `coef`, or the coefficients `b` and optionally `a`
294+
as in [`filt`](@ref). The initial state of the filter is computed so
292295
that its response to a step function is steady state. Before
293296
filtering, the data is extrapolated at both ends with an
294297
odd-symmetric extension of length
295-
`3*(max(length(b), length(a))-1)`.
298+
`min(3*(max(length(b), length(a))-1), size(x, 1) - 1)`.
296299
297300
Because `filtfilt` applies the given filter twice, the effective
298301
filter order is twice the order of `coef`. The resulting signal has
@@ -368,31 +371,39 @@ filtfilt(f::PolynomialRatio{:z}, x) = filtfilt(coefb(f), coefa(f), x)
368371
## Initial filter state
369372

370373
# Compute an initial state for filt with coefficients (b,a) such that its
371-
# response to a step function is steady state.
372-
function filt_stepstate(b::Union{AbstractVector{T}, T}, a::Union{AbstractVector{T}, T}) where T<:Number
374+
# response to a step function is steady state. Also returns padded (b, a).
375+
function filt_stepstate(b::AbstractVector{V}, a::AbstractVector{V}) where V<:Number
376+
T = typeof(one(V) / one(V))
373377
scale_factor = a[1]
374378
if !isone(scale_factor)
375379
a = a ./ scale_factor
376380
b = b ./ scale_factor
381+
elseif T !== V
382+
a = convert.(T, a)
383+
b = convert.(T, b)
377384
end
378385

379386
bs = length(b)
380387
as = length(a)
381388
sz = max(bs, as)
382389
sz > 0 || throw(ArgumentError("a and b must have at least one element each"))
383-
sz == 1 && return T[]
384390

385391
# Pad the coefficients with zeros if needed
386-
bs<sz && (b = copyto!(zeros(eltype(b), sz), b))
387-
as<sz && (a = copyto!(zeros(eltype(a), sz), a))
392+
bs < sz && (b = copyto!(zeros(T, sz), b))
393+
as < sz && (a = copyto!(zeros(T, sz), a))
394+
sz == 1 && return (T[], b, a)
388395

389396
# construct the companion matrix A and vector B:
390397
A = [-a[2:end] Matrix{T}(I, sz-1, sz-2)]
391398
B = @views @. muladd(a[2:end], -b[1], b[2:end])
392399
# Solve si = A*si + B
393400
# (I - A)*si = B
394-
((I - A) \ B) .*= scale_factor
395-
end
401+
si = (((I - A) \ B) .*= scale_factor)
402+
return (si, b, a)
403+
end
404+
405+
filt_stepstate(b::AbstractVector{<:Number}, a::AbstractVector{<:Number}) =
406+
filt_stepstate(promote(b, a)...)
396407

397408
function filt_stepstate(f::SecondOrderSections{:z,T}) where T
398409
biquads = f.biquads

test/filt.jl

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ end
8282
b = [ 0.00327922, 0.01639608, 0.03279216, 0.03279216, 0.01639608, 0.00327922]
8383
a = [ 1. , -2.47441617, 2.81100631, -1.70377224, 0.54443269, -0.07231567]
8484

85-
@test (zi_python, DSP.Filters.filt_stepstate(b, a), atol=1e-7)
85+
@test (zi_python, DSP.Filters.filt_stepstate(b, a)[1], atol=1e-7)
8686

8787
##############
8888
#
@@ -99,7 +99,7 @@ end
9999
b = [0.222, 0.43, 0.712]
100100
a = [1, 0.33, 0.22]
101101

102-
@test zi_matlab DSP.Filters.filt_stepstate(b, a)
102+
@test zi_matlab DSP.Filters.filt_stepstate(b, a)[1]
103103

104104

105105
##############
@@ -118,7 +118,7 @@ end
118118
b = [ 0.00327922, 0.01639608, 0.03279216, 0.03279216, 0.01639608, 0.00327922]
119119
a = [ 1.1 , -2.47441617, 2.81100631, -1.70377224, 0.54443269, -0.07231567]
120120

121-
@test (zi_python, DSP.Filters.filt_stepstate(b, a), atol=1e-7)
121+
@test (zi_python, DSP.Filters.filt_stepstate(b, a)[1], atol=1e-7)
122122
end
123123

124124

@@ -270,9 +270,8 @@ end
270270

271271
# fir_filtfilt
272272
@testset "fir_filtfilt" begin
273-
b = randn(10)
274-
for x in (randn(100), randn(100, 2))
275-
@test filtfilt(b, x) DSP.Filters.iir_filtfilt(b, [1.0], x)
276-
@test filtfilt(b, [2.0], x) DSP.Filters.iir_filtfilt(b, [2.0], x)
273+
for b in (randn(10), [1:10;]), x in (randn(100), randn(100, 2), randn(10)) # also tests issue #537
274+
@test filtfilt(b, x) DSP.Filters.iir_filtfilt(b, [1], x)
275+
@test filtfilt(b, [2.0], x) DSP.Filters.iir_filtfilt(b, [2], x)
277276
end
278277
end

0 commit comments

Comments
 (0)