Skip to content

Commit 2257010

Browse files
authored
Buffer array resample and reference_data (#623)
1 parent cd2912c commit 2257010

12 files changed

+203
-153
lines changed

Project.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "DSP"
22
uuid = "717857b8-e6f2-59f4-9121-6e50c889abd2"
3-
version = "0.8.0"
3+
version = "0.8.1"
44

55
[deps]
66
Bessels = "0e736298-9ec6-45e8-9647-e4fc86a2fe38"

src/Filters/stream_filt.jl

+63-36
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using ..DSP: _zeropad
2+
13
const PFB{T} = Matrix{T} # polyphase filter bank
24

35
abstract type FIRKernel{T} end
@@ -132,11 +134,11 @@ end
132134
FIRArbitrary(h::Vector, rate::Real, Nϕ::Integer) = FIRArbitrary(h, convert(Float64, rate), convert(Int, Nϕ))
133135

134136
# FIRFilter - the kernel does the heavy lifting
135-
mutable struct FIRFilter{Tk<:FIRKernel}
136-
kernel::Tk
137+
mutable struct FIRFilter{Tk<:FIRKernel,T}
138+
const kernel::Tk
139+
const h::Vector{T}
140+
const historyLen::Int
137141
history::Vector
138-
historyLen::Int
139-
h::Vector
140142
end
141143

142144
# Constructor for single-rate, decimating, interpolating, and rational resampling filters
@@ -172,7 +174,7 @@ function FIRFilter(h::Vector, resampleRatio::Union{Integer,Rational} = 1)
172174

173175
history = zeros(historyLen)
174176

175-
FIRFilter(kernel, history, historyLen, h)
177+
FIRFilter(kernel, h, historyLen, history)
176178
end
177179

178180
# Constructor for arbitrary resampling filter (polyphase interpolator w/ intra-phase linear interpolation)
@@ -193,7 +195,7 @@ function FIRFilter(h::Vector, rate::AbstractFloat, Nϕ::Integer=32)
193195
kernel = FIRArbitrary(h, rate, Nϕ)
194196
historyLen = kernel.tapsPerϕ - 1
195197
history = zeros(historyLen)
196-
FIRFilter(kernel, history, historyLen, h)
198+
FIRFilter(kernel, h, historyLen, history)
197199
end
198200

199201
# Constructor for a resampling FIR filter, where the user needs only to set the sampling rate
@@ -623,31 +625,34 @@ function filt!(
623625
return bufIdx
624626
end
625627

626-
function filt(self::FIRFilter{Tk}, x::AbstractVector{Tx}) where {Th,Tx,Tk<:FIRKernel{Th}}
627-
bufLen = outputlength(self, length(x))
628+
function filt(self::FIRFilter{Tk}, x::AbstractVector) where Tk<:FIRKernel
629+
buffer = allocate_output(self, x)
630+
bufLen = length(buffer)
631+
samplesWritten = filt!(buffer, self, x)
632+
if Tk <: FIRArbitrary
633+
samplesWritten == bufLen || resize!(buffer, samplesWritten)
634+
else
635+
samplesWritten == bufLen || throw(AssertionError("Length of resampled output different from expectation."))
636+
end
637+
return buffer
638+
end
639+
640+
function allocate_output(sf::FIRFilter{Tk}, x::AbstractVector{Tx}) where {Th,Tx,Tk<:FIRKernel{Th}}
628641
# In some cases when `filt(::FIRFilter{FIRArbitrary}, x)` is called
629642
# with certain values of `x`, `filt!(buffer, ::FIRFilter{FIRArbitrary}, x)`
630643
# tries to write one sample too many to the buffer and a `BoundsError`
631-
# is thrown. Add one extra sample to catch these exceptional cases.
644+
# is thrown. Add one extra sample to catch these exceptional cases.
632645
#
633646
# See https://github.com/JuliaDSP/DSP.jl/issues/317
634647
#
635648
# FIXME: Remove this if and when the code in
636649
# `filt!(buffer, ::FIRFilter{FIRArbitrary}, x)`
637650
# is updated to properly account for pathological arbitrary rates.
651+
outLen = outputlength(sf, length(x))
638652
if Tk <: FIRArbitrary
639-
bufLen += 1
653+
outLen += 1
640654
end
641-
buffer = Vector{promote_type(Th,Tx)}(undef, bufLen)
642-
samplesWritten = filt!(buffer, self, x)
643-
644-
if Tk <: FIRArbitrary
645-
samplesWritten == bufLen || resize!(buffer, samplesWritten)
646-
else
647-
@assert samplesWritten == bufLen
648-
end
649-
650-
return buffer
655+
return Vector{promote_type(Th, Tx)}(undef, outLen)
651656
end
652657

653658

@@ -689,24 +694,34 @@ function resample(x::AbstractVector, rate::AbstractFloat, h::Vector, Nϕ::Intege
689694
_resample!(x, rate, FIRFilter(h, rate, Nϕ))
690695
end
691696

692-
function _resample!(x::AbstractVector, rate::Real, self::FIRFilter)
697+
function _resample!(x::AbstractVector, rate::Real, sf::FIRFilter)
698+
undelay!(sf)
699+
outLen = ceil(Int, length(x) * rate)
700+
xPadded = _zeropad(x, inputlength(sf, outLen, RoundUp))
701+
702+
buffer = allocate_output(sf, xPadded)
703+
samplesWritten = filt!(buffer, sf, xPadded)
704+
return checked_resample_output!(buffer, outLen, samplesWritten, sf)
705+
end
706+
707+
function undelay!(sf::FIRFilter)
693708
# Get delay, in # of samples at the output rate, caused by filtering processes
694-
τ = timedelay(self)
709+
τ = timedelay(sf)
695710

696711
# Use setphase! to
697712
# a) adjust the input samples to skip over before producing and output (integer part of τ)
698713
# b) set the ϕ index of the PFB (fractional part of τ)
699-
setphase!(self, τ)
700-
701-
# Calculate the number of 0's required
702-
outLen = ceil(Int, length(x) * rate)
703-
reqInlen = inputlength(self, outLen, RoundUp)
704-
reqZerosLen = reqInlen - length(x)
705-
xPadded = [x; zeros(eltype(x), reqZerosLen)]
714+
setphase!(sf, τ)
715+
end
706716

707-
y = filt(self, xPadded)
708-
@assert length(y) >= outLen
709-
length(y) > outLen && resize!(y, outLen)
717+
function checked_resample_output!(y::AbstractVector, outLen, samplesWritten, ::FIRFilter{Tk}) where Tk<:FIRKernel
718+
if !(Tk <: FIRArbitrary)
719+
samplesWritten == length(y) || throw(AssertionError("Length of resampled output different from expectation."))
720+
end
721+
# outLen: the desired output length ceil(Int, rate * length(input)), but we can overshoot
722+
# samplesWritten: number of samples actually written to y; if longer, y[samplesWritten+1:end] contains invalid data
723+
samplesWritten >= outLen || throw(AssertionError("Resample output shorter than expected."))
724+
length(y) == outLen || resize!(y, outLen)
710725
return y
711726
end
712727

@@ -742,11 +757,23 @@ end
742757
resample(x::AbstractArray, rate::Real, args::Real...; dims) =
743758
_resample!(x, rate, FIRFilter(rate, args...); dims)
744759

745-
_resample!(x::AbstractArray, rate::Real, sf::FIRFilter; dims) =
746-
mapslices(x; dims) do v
747-
reset!(sf)
748-
_resample!(v, rate, sf)
760+
function _resample!(x::AbstractArray{T}, rate::Real, sf::FIRFilter; dims::Int) where T
761+
undelay!(sf)
762+
size_v = size(x, dims)
763+
outLen = ceil(Int, size_v * rate)
764+
xPadded = Vector{T}(undef, inputlength(sf, outLen, RoundUp))
765+
xPadded[size_v+1:end] .= zero(T)
766+
buffer = allocate_output(sf, xPadded)
767+
bufLen = length(buffer)
768+
769+
mapslices(x; dims) do v::AbstractVector
770+
undelay!(reset!(sf))
771+
length(buffer) == bufLen || resize!(buffer, bufLen)
772+
copyto!(xPadded, v)
773+
samplesWritten = filt!(buffer, sf, xPadded)
774+
return checked_resample_output!(buffer, outLen, samplesWritten, sf)
749775
end
776+
end
750777

751778
#
752779
# References

test/FilterTestHelpers.jl

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
module FilterTestHelpers
2-
using DSP, Test
2+
using DSP, Test, DelimitedFiles
33

44
export tffilter_eq, zpkfilter_eq, tffilter_accuracy, zpkfilter_accuracy,
5-
matrix_to_sosfilter, sosfilter_to_matrix
5+
matrix_to_sosfilter, sosfilter_to_matrix,
6+
read_reference_data
7+
8+
read_reference_data(s, delim='\t') = readdlm(joinpath(@__DIR__, "data", s), delim)
69

710
function lt(a, b)
811
if abs(real(a) - real(b)) > 1e-10

test/filt.jl

+12-12
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
!(dirname(@__FILE__) in LOAD_PATH) && push!(LOAD_PATH, dirname(@__FILE__))
1+
!(@__DIR__() in LOAD_PATH) && push!(LOAD_PATH, @__DIR__)
22
using DSP, Test, Random, FilterTestHelpers
33
#
44
# filt with different filter forms
@@ -181,17 +181,17 @@ end
181181
#
182182
##############
183183
@testset "filt! with init. cond." begin
184-
matlab_filt = readdlm(joinpath(dirname(@__FILE__), "data", "filt_check.txt"),'\t')
184+
matlab_filt = read_reference_data("filt_check.txt")
185185

186186
a = [0.9, 0.6]
187187
b = [0.4, 1]
188188
z = [0.4750]
189-
x = vec(readdlm(joinpath(dirname(@__FILE__), "data", "spectrogram_x.txt"),'\t'))
189+
x = vec(read_reference_data("spectrogram_x.txt"))
190190
@test_deprecated(filt!(x, b, a, x, z))
191191

192192
@test matlab_filt x
193193

194-
x = vec(readdlm(joinpath(dirname(@__FILE__), "data", "spectrogram_x.txt"),'\t'))
194+
x = vec(read_reference_data("spectrogram_x.txt"))
195195
filt!(x, DF2TFilter(PolynomialRatio(b, a), z), x)
196196

197197
@test matlab_filt x
@@ -215,22 +215,22 @@ end
215215
#
216216
#######################################
217217
@testset "filtfilt 1D" begin
218-
x2_matlab = readdlm(joinpath(dirname(@__FILE__), "data", "filtfilt_output.txt"),'\t')
218+
x2_matlab = read_reference_data("filtfilt_output.txt")
219219

220220
b = [ 0.00327922, 0.01639608, 0.03279216, 0.03279216, 0.01639608, 0.00327922]
221221
a = [ 1. , -2.47441617, 2.81100631, -1.70377224, 0.54443269, -0.07231567]
222-
x = readdlm(joinpath(dirname(@__FILE__), "data", "spectrogram_x.txt"),'\t')
222+
x = read_reference_data("spectrogram_x.txt")
223223

224224
@test x2_matlab filtfilt(b, a, x)
225225
end
226226

227227
# Make sure above doesn't crash for real coeffs & complex data.
228228
@testset "filtfilt 1D Complex" begin
229-
x2_matlab = readdlm(joinpath(dirname(@__FILE__), "data", "filtfilt_output.txt"),'\t')
229+
x2_matlab = read_reference_data("filtfilt_output.txt")
230230

231231
b = [ 0.00327922, 0.01639608, 0.03279216, 0.03279216, 0.01639608, 0.00327922]
232232
a = [ 1. , -2.47441617, 2.81100631, -1.70377224, 0.54443269, -0.07231567]
233-
x = readdlm(joinpath(dirname(@__FILE__), "data", "spectrogram_x.txt"),'\t')
233+
x = read_reference_data("spectrogram_x.txt")
234234

235235
y = x .+ 1im .* randn(size(x, 1))
236236

@@ -260,10 +260,10 @@ end
260260
b = [ 0.00327922, 0.01639608, 0.03279216, 0.03279216, 0.01639608, 0.00327922]
261261
a = [ 1. , -2.47441617, 2.81100631, -1.70377224, 0.54443269, -0.07231567]
262262

263-
x2_output = readdlm(joinpath(dirname(@__FILE__), "data", "filtfilt_output_2d.txt"),'\t')
263+
x2_output = read_reference_data("filtfilt_output_2d.txt")
264264

265-
x = readdlm(joinpath(dirname(@__FILE__), "data", "spectrogram_x.txt"),'\t')
266-
x = repeat(x, outer=(1, 3))
265+
x = read_reference_data("spectrogram_x.txt")
266+
x = repeat(x, outer=(1, 3))
267267
x[:,2] = circshift(x[:,2], 64)
268268
x[:,3] = circshift(x[:,3], 128)
269269

@@ -291,7 +291,7 @@ end
291291
# the extrapolation will differ slightly.)
292292
#######################################
293293
@testset "filtfilt SOS" begin
294-
x = readdlm(joinpath(dirname(@__FILE__), "data", "spectrogram_x.txt"),'\t')
294+
x = read_reference_data("spectrogram_x.txt")
295295

296296
f = digitalfilter(Lowpass(0.2), Butterworth(4))
297297
@test filtfilt(convert(SecondOrderSections, f), x) filtfilt(convert(PolynomialRatio, f), x)

test/filter_conversion.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
!(dirname(@__FILE__) in LOAD_PATH) && push!(LOAD_PATH, dirname(@__FILE__))
1+
!(@__DIR__() in LOAD_PATH) && push!(LOAD_PATH, @__DIR__)
22
using DSP, Test, FilterTestHelpers, Polynomials
33
using Polynomials.PolyCompat
44

test/filter_design.jl

+14-14
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
!(dirname(@__FILE__) in LOAD_PATH) && push!(LOAD_PATH, dirname(@__FILE__))
1+
!(@__DIR__() in LOAD_PATH) && push!(LOAD_PATH, @__DIR__)
22
using DSP, Test, FilterTestHelpers
33
using LinearAlgebra: norm
4-
using DelimitedFiles: readdlm
4+
using FFTW: fft
55

66
#
77
# Butterworth filter prototype
@@ -987,29 +987,29 @@ end
987987
@testset "window FIR" begin
988988
winfirtaps_jl = digitalfilter(Lowpass(0.25),FIRWindow(hamming(128), scale=false); fs=1)
989989
# firwin(128, 0.25, nyq=.5, scale=False)
990-
winfirtaps_scipy = readdlm(joinpath(dirname(@__FILE__), "data", "digitalfilter_hamming_128_lowpass_fc0.25_fs1.0.txt"),'\t')
990+
winfirtaps_scipy = read_reference_data("digitalfilter_hamming_128_lowpass_fc0.25_fs1.0.txt")
991991
@test winfirtaps_jl winfirtaps_scipy
992992

993993
winfirtaps_jl = digitalfilter(Lowpass(0.25),FIRWindow(hamming(129), scale=false); fs=1)
994994
# firwin(129, 0.25, nyq=.5, scale=False)
995-
winfirtaps_scipy = readdlm(joinpath(dirname(@__FILE__), "data", "digitalfilter_hamming_129_lowpass_fc0.25_fs1.0.txt"),'\t')
995+
winfirtaps_scipy = read_reference_data("digitalfilter_hamming_129_lowpass_fc0.25_fs1.0.txt")
996996
@test winfirtaps_jl winfirtaps_scipy
997997

998998
@test_throws ArgumentError digitalfilter(Highpass(0.25),FIRWindow(hamming(128), scale=false))
999999

10001000
winfirtaps_jl = digitalfilter(Highpass(0.25),FIRWindow(hamming(129), scale=false); fs=1)
10011001
# firwin(129, 0.25, nyq=.5, scale=False, pass_zero=False)
1002-
winfirtaps_scipy = readdlm(joinpath(dirname(@__FILE__), "data", "digitalfilter_hamming_129_highpass_fc0.25_fs1.0.txt"),'\t')
1002+
winfirtaps_scipy = read_reference_data("digitalfilter_hamming_129_highpass_fc0.25_fs1.0.txt")
10031003
@test winfirtaps_jl winfirtaps_scipy
10041004

10051005
winfirtaps_jl = digitalfilter(Bandpass(0.1, 0.2),FIRWindow(hamming(128), scale=false); fs=1)
10061006
# firwin(128, [0.1, 0.2], nyq=.5, scale=False, pass_zero=False)
1007-
winfirtaps_scipy = readdlm(joinpath(dirname(@__FILE__), "data", "digitalfilter_hamming_128_bandpass_fc0.1_0.2_fs1.0.txt"),'\t')
1007+
winfirtaps_scipy = read_reference_data("digitalfilter_hamming_128_bandpass_fc0.1_0.2_fs1.0.txt")
10081008
@test winfirtaps_jl winfirtaps_scipy
10091009

10101010
winfirtaps_jl = digitalfilter(Bandpass(0.1, 0.2),FIRWindow(hamming(129), scale=false); fs=1)
10111011
# firwin(129, [0.1, 0.2], nyq=.5, scale=False, pass_zero=False)
1012-
winfirtaps_scipy = readdlm(joinpath(dirname(@__FILE__), "data", "digitalfilter_hamming_129_bandpass_fc0.1_0.2_fs1.0.txt"),'\t')
1012+
winfirtaps_scipy = read_reference_data("digitalfilter_hamming_129_bandpass_fc0.1_0.2_fs1.0.txt")
10131013
@test winfirtaps_jl winfirtaps_scipy
10141014

10151015
@test_throws ArgumentError ComplexBandpass(2, 1)
@@ -1046,37 +1046,37 @@ end
10461046

10471047
winfirtaps_jl = digitalfilter(Bandstop(0.1, 0.2),FIRWindow(hamming(129), scale=false); fs=1)
10481048
# firwin(129, [0.1, 0.2], nyq=.5, scale=False)
1049-
winfirtaps_scipy = readdlm(joinpath(dirname(@__FILE__), "data", "digitalfilter_hamming_129_bandstop_fc0.1_0.2_fs1.0.txt"),'\t')
1049+
winfirtaps_scipy = read_reference_data("digitalfilter_hamming_129_bandstop_fc0.1_0.2_fs1.0.txt")
10501050
@test winfirtaps_jl vec(winfirtaps_scipy)
10511051

10521052
winfirtaps_jl = digitalfilter(Lowpass(0.25),FIRWindow(hamming(128), scale=true); fs=1)
10531053
# firwin(128, 0.25, nyq=.5)
1054-
winfirtaps_scipy = readdlm(joinpath(dirname(@__FILE__), "data", "digitalfilter_hamming_128_lowpass_scaled_fc0.25_fs1.0.txt"),'\t')
1054+
winfirtaps_scipy = read_reference_data("digitalfilter_hamming_128_lowpass_scaled_fc0.25_fs1.0.txt")
10551055
@test winfirtaps_jl winfirtaps_scipy
10561056

10571057
winfirtaps_jl = digitalfilter(Lowpass(0.25),FIRWindow(hamming(129), scale=true); fs=1)
10581058
# firwin(129, 0.25, nyq=.5)
1059-
winfirtaps_scipy = readdlm(joinpath(dirname(@__FILE__), "data", "digitalfilter_hamming_129_lowpass_scaled_fc0.25_fs1.0.txt"),'\t')
1059+
winfirtaps_scipy = read_reference_data("digitalfilter_hamming_129_lowpass_scaled_fc0.25_fs1.0.txt")
10601060
@test winfirtaps_jl winfirtaps_scipy
10611061

10621062
winfirtaps_jl = digitalfilter(Highpass(0.25),FIRWindow(hamming(129), scale=true); fs=1)
10631063
# firwin(129, 0.25, nyq=.5, pass_zero=False)
1064-
winfirtaps_scipy = readdlm(joinpath(dirname(@__FILE__), "data", "digitalfilter_hamming_129_highpass_scaled_fc0.25_fs1.0.txt"),'\t')
1064+
winfirtaps_scipy = read_reference_data("digitalfilter_hamming_129_highpass_scaled_fc0.25_fs1.0.txt")
10651065
@test winfirtaps_jl winfirtaps_scipy
10661066

10671067
winfirtaps_jl = digitalfilter(Bandpass(0.1, 0.2),FIRWindow(hamming(128), scale=true); fs=1)
10681068
# firwin(128, [0.1, 0.2], nyq=.5, pass_zero=False)
1069-
winfirtaps_scipy = readdlm(joinpath(dirname(@__FILE__), "data", "digitalfilter_hamming_128_bandpass_scaled_fc0.1_0.2_fs1.0.txt"),'\t')
1069+
winfirtaps_scipy = read_reference_data("digitalfilter_hamming_128_bandpass_scaled_fc0.1_0.2_fs1.0.txt")
10701070
@test winfirtaps_jl winfirtaps_scipy
10711071

10721072
winfirtaps_jl = digitalfilter(Bandpass(0.1, 0.2),FIRWindow(hamming(129), scale=true); fs=1)
10731073
# firwin(129, [0.1, 0.2], nyq=.5, scale=False, pass_zero=False)
1074-
winfirtaps_scipy = readdlm(joinpath(dirname(@__FILE__), "data", "digitalfilter_hamming_129_bandpass_scaled_fc0.1_0.2_fs1.0.txt"),'\t')
1074+
winfirtaps_scipy = read_reference_data("digitalfilter_hamming_129_bandpass_scaled_fc0.1_0.2_fs1.0.txt")
10751075
@test winfirtaps_jl winfirtaps_scipy
10761076

10771077
winfirtaps_jl = digitalfilter(Bandstop(0.1, 0.2),FIRWindow(hamming(129), scale=true); fs=1)
10781078
# firwin(129, [0.1, 0.2], nyq=.5, scale=False)
1079-
winfirtaps_scipy = readdlm(joinpath(dirname(@__FILE__), "data", "digitalfilter_hamming_129_bandstop_scaled_fc0.1_0.2_fs1.0.txt"),'\t')
1079+
winfirtaps_scipy = read_reference_data("digitalfilter_hamming_129_bandstop_scaled_fc0.1_0.2_fs1.0.txt")
10801080
@test winfirtaps_jl vec(winfirtaps_scipy)
10811081
end
10821082

0 commit comments

Comments
 (0)