I need to implement a lowpass filter in Python, but the only module I can use is numpy (not scipy). I tried using np.fft.fft()
on the signal, then setting all frequencies which are higher than the cutoff frequency to 0 and then using np.fft.ifft()
.
Howerver this didn't work and I'm not shure how to apply the filter at all.
EDIT:
after changing np.abs()
to np.real()
the result was almost correct.
But in the spectrogram the amplitudes are smaller then in the original and the filterd refernce (difference of 6dB). So it looks like it's not completely right. Any Ideas what could be done to fix that?
my Lowpass Function should take the following arguments:
signal
: audio signal to be filteredcutoff_freq
: cut off frequency in Hz above which to cut off frequenciessampling_rate
: sampling rate in samples/secondThe filtered signal should be returned.
my current function
def low_pass_filter(adata: np.ndarray, bandlimit: int = 1000, sampling_rate: int = 44100) -> np.ndarray:
# translate bandlimit from Hz to dataindex according to sampling rate and data size
bandlimit_index = int(bandlimit * adata.size / sampling_rate)
fsig = np.fft.fft(adata)
for i in range(bandlimit_index + 1, len(fsig)):
fsig[i] = 0
adata_filtered = np.fft.ifft(fsig)
return np.real(adata_filtered)
I see that the comments of @Cris Luengo have already developed your solution into the right direction. The last thing you're missing now is that the spectrum you obtain from np.fft.fft
is composed of the positive frequency components in the first half and the 'mirrored' negative frequency components in the second half.
If you now set all components beyond your bandlimit_index
to zero, you're erradicating these negative frequency components. That explains the drop in signal amplitude of 6dB, you're eliminating half the signal power (+ as you already noticed every real signal has to have conjugate symmetric frequency spectrum). The np.fft.ifft
function documentation (ifft documentation) explains the expected format quite nicely. It states:
"The input should be ordered in the same way as is returned by fft, i.e.,"
That's essentially the symmetry you have to preserve. So in order to preserve these components just set the components between bandlimit_index + 1 -> (len(fsig) - bandlimit_index)
to zero.
def low_pass_filter(adata: np.ndarray, bandlimit: int = 1000, sampling_rate: int = 44100) -> np.ndarray:
# translate bandlimit from Hz to dataindex according to sampling rate and data size
bandlimit_index = int(bandlimit * adata.size / sampling_rate)
fsig = np.fft.fft(adata)
for i in range(bandlimit_index + 1, len(fsig) - bandlimit_index ):
fsig[i] = 0
adata_filtered = np.fft.ifft(fsig)
return np.real(adata_filtered)
or maybe if you want to be slightly more 'pythonic', you can also set the components to zero like this:
fsig[bandlimit_index+1 : -bandlimit_index] = 0
Here's a colab to walk through: https://colab.research.google.com/drive/1RR_9EYlApDMg4jAS2HuJIpSqwg5RLzGW?usp=sharing