pythonnumpyfftlowpass-filter

Python: Lowpass Filter with only numpy


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:

The 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)

Solution

  • 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