-
Notifications
You must be signed in to change notification settings - Fork 113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
aliasing issue with floating-point resampling ratio #620
Comments
Could you test this with 0.7.9 to see if this might be a regression with the changes in 0.8.0? |
This actually is on 0.7.9, I will try to run it on 0.8.0 but I need to update my code for changes (no |
Sorry, didn't quite notice. Fingers crossed it magically disappears in 0.8... |
Unfortunately not, same results on 0.8 DSP v0.8 codeusing DSP, CairoMakie
function hpf(x, freq; fs)
return filtfilt(digitalfilter(Highpass(freq), Butterworth(4); fs), x)
end
function lpf(x, freq; fs)
return filtfilt(digitalfilter(Lowpass(freq), Butterworth(4); fs), x)
end
function middle_third(x)
third = div(length(x), 3)
return x[third:(2 * third + 1)]
end
function plt_filters(x; fs)
x = lpf(x, 35.0; fs)
x = hpf(x, 0.5; fs)
return x
end
sine_wave(freq_hz) = sin.(2π .* freq_hz .* ts)
n = 45 # seconds
fs = 250
ts = range(0, n; length=fs * n)
data = 0.05 * sine_wave(0.75) + 0.01 * sine_wave(5.0) + 0.025 * sine_wave(10.0) +
# lots of high frequency noise
sum(100 * sine_wave(f) for f in 90:125)
resampling_ratio = 1 / 1.00592
resampled = resample(data, resampling_ratio)
ts_resampled = resample(ts, resampling_ratio)
rational_resampled = resample(data, rationalize(resampling_ratio))
rational_ts_resampled = resample(ts, rationalize(resampling_ratio))
# individual axes
fig = let
fig = Figure()
colors = Makie.wong_colors()
ax_kwargs = (; ylabel="Data", limits=(nothing, nothing, -1, 1))
ax1 = Axis(fig[1, 1]; ax_kwargs...)
l1 = lines!(ax1, middle_third(ts), middle_third(plt_filters(data; fs)); color=colors[1])
ax2 = Axis(fig[2, 1]; ax_kwargs...)
l2 = lines!(ax2, middle_third(ts_resampled), middle_third(plt_filters(resampled; fs));
color=colors[2])
ax3 = Axis(fig[3, 1]; xlabel="Time (s)", ax_kwargs...)
l3 = lines!(ax3, middle_third(rational_ts_resampled),
middle_third(plt_filters(rational_resampled; fs)); color=colors[3])
Legend(fig[4, 1], [l1, l2, l3], ["original", "resampled", "rational resampled"];
orientation=:horizontal)
fig
end
save("mwe.png", fig)
# combined
fig = let
fig = Figure()
colors = Makie.wong_colors()
ax_kwargs = (; ylabel="Data", limits=(nothing, nothing, -1, 1))
ax = Axis(fig[1, 1]; xlabel="Time (s)", ax_kwargs...)
lines!(ax, middle_third(ts_resampled), middle_third(plt_filters(resampled; fs));
color=colors[2], label="resampled")
lines!(ax, middle_third(rational_ts_resampled),
middle_third(plt_filters(rational_resampled; fs)); color=colors[3],
label="rational resampled")
lines!(ax, middle_third(ts), middle_third(plt_filters(data; fs)); color=colors[1],
label="original")
axislegend(ax)
fig
end
save("mwe-combined.png", fig)
fig |
I tried to resample using a DSP.jl/src/Filters/stream_filt.jl Lines 198 to 201 in 06f8776
On L200, FIRFilter looks like it should have an additional argument of Nϕ , but upon fixing it, the smoothing effect previously observed disappears, so that's really not the problem here.
|
Ok, saw wrongly. Indeed, choosing a higher value of There are still going to be artifacts, but less, if you use something like resample_phases(s, rate, Nϕ=32) = filt(FIRFilter(resample_filter(rate, Nϕ), rate, Nϕ), s)
resampled = resample_phases(data, resampling_ratio, 128)
ts_resampled = resample_phases(ts, resampling_ratio, 128) |
You could also play around with |
When working on #596, I din't take a closer look at the algorithm, but from what I recollect, it does upsampling by an integer ( |
Just to add on, the |
Yeah, fwiw, that's why I was describing it as an aliasing issue; the original signal has a lot of high frequency noise, but it is filtered out cleanly by the bandpass. The resampled version has these unexpected spikes when filtered the same way. It seems this arbitrary sampling rate resampling is a tricky business. Searching, I came across https://www.mathworks.com/help/dsp/ug/efficient-sample-rate-conversion-between-arbitrary-factors.html and https://www.mathworks.com/help/dsp/ref/designrateconverter.html which seem to be MATLAB's solution here. I don't have a MATLAB license or I'd check how it does on this signal. But maybe a multi-stage approach like that would do better here. |
Ah, right, I hadn't looked at how those plots are generated carefully enough. So the (unfiltered) data contains those spikes at 1 s intervals due to the "high frequency noise", which really is a series of sines 1 Hz apart (90 Hz to 125 Hz). Then the highpass filter should eliminate those, but for the (irrational-)resampled signal, a significant amount remains due to aliasing components below (or just slightly above) the filter's cutoff. It should be noted, though, that the spikes are still attenuated by some 80 dB or so, which isn't great, but not catastrophic either. A way to more directly visualize the aliasing effects is by looking at the spectrogram of a linear sine sweep. Consider a long sweep from 0 to pi: sweep = let N=2^24
[sin(0.5*pi*n^2/N) for n in 1:N]
end As expected, it's spectrogram consists of a single diagonal and some windowing artifacts: OTOH, the spectrogram for
For comparison, rational resampling ( For completeness, I've created the spectrograms with: function sgramplot(x)
sgram = spectrogram(x, 4096; window=hanning)
fig = Figure()
ax = Axis(fig[1,1]; xlabel="ω", ylabel="n")
hm = heatmap!(ax, freq(sgram), time(sgram), pow2db.(power(sgram)); colorrange=(-130, -30))
Colorbar(fig[:, end+1], hm)
return fig
end So contrary to my first impression, I no longer think this issue exposes a bug. Rather, the choice of algorithm and default parameters leads to a performance/quality trade-off that delivers insufficient quality in this case. So what @wheeheee has proposed might indeed be the solution here, not merely a work-around. The main take-way here is probably that we should improve the documentation on how to achieve this. From a look at the Mathworks documents, I get the feeling that it might also be worthwhile to replace the linear interpolation we're doing with cubic interpolation, but that would require a deep dive into how |
When calling
resample
with a floating-point value, it takes a different codepath than with a rational value. The floating-point version seems more susceptible to aliasing issues.Here is an example, minimized from a real-world issue, that demonstrates the problem:
The text was updated successfully, but these errors were encountered: