# Implement High Pass Filter Using NumPy

In this tutorial, you’ll learn about different methods to create high-pass filters, including Finite Impulse Response (FIR), Infinite Impulse Response (IIR), and the Fast Fourier Transform (FFT) using NumPy.

We’ll start by designing a FIR high-pass filter using a windowed sinc function.

Then, we’ll focus on IIR high-pass filters, using popular design techniques like the Butterworth and Chebyshev filters.

These filters are known for their smooth frequency response and sharp cutoff characteristics, respectively.

Finally, we explore the use of FFT to create a high-pass filter.

## FIR High Pass Filter

The concept involves creating a low-pass filter first and then transforming it into a high-pass filter.

This transformation is done by subtracting the low-pass response from an appropriately scaled delta function.

First, ensure you have NumPy installed in your Python environment.

If you don’t have it installed, you can install it via pip:

```!pip install numpy
```

Now, let’s import NumPy:

```import numpy as np
```

Here, we define the parameters for our filter. This includes the filter length, cutoff frequency, and the sampling rate.

```filter_length = 101  # Length of the filter (number of coefficients)
cutoff_frequency = 0.3  # Normalized cutoff frequency (0 to 0.5)
sampling_rate = 1000  # Sampling rate in Hz
```

We’ll design the low-pass filter using a windowed sinc function. The sinc function is the ideal response of a low-pass filter, and we window it to truncate the infinite series.

```# Time vector for filter
t = np.arange(-filter_length // 2 + 1, filter_length // 2 + 1)

# Sinc function
h_low_pass = np.sinc(2 * cutoff_frequency * (t / sampling_rate))

# Apply a window function (e.g., Hamming window)
window = np.hamming(filter_length)
h_low_pass_windowed = h_low_pass * window
print(h_low_pass_windowed)
```

Output:

```[0.07988162 0.08079272 0.08351319 0.0880326  0.09433344 0.10239112
0.11217411 0.12364407 0.13675597 0.15145828 0.16769319 0.18539682
0.20449946 0.22492588 0.24659561 0.26942324 0.29331878 0.31818802
0.34393288 0.3704518  0.39764018 0.42539072 0.45359394 0.48213851
0.51091178 0.53980016 0.56868962 0.59746608 0.62601594 0.65422647
0.68198625 0.70918566 0.7357173  0.76147636 0.7863611  0.81027325
0.83311833 0.85480612 0.87525093 0.894372   0.9120938  0.92834631
0.94306533 0.9561927  0.96767655 0.97747153 0.98553892 0.99184685
0.9963704  0.9990917  1.         0.9990917  0.9963704  0.99184685
0.98553892 0.97747153 0.96767655 0.9561927  0.94306533 0.92834631
0.9120938  0.894372   0.87525093 0.85480612 0.83311833 0.81027325
0.7863611  0.76147636 0.7357173  0.70918566 0.68198625 0.65422647
0.62601594 0.59746608 0.56868962 0.53980016 0.51091178 0.48213851
0.45359394 0.42539072 0.39764018 0.3704518  0.34393288 0.31818802
0.29331878 0.26942324 0.24659561 0.22492588 0.20449946 0.18539682
0.16769319 0.15145828 0.13675597 0.12364407 0.11217411 0.10239112
0.09433344 0.0880326  0.08351319 0.08079272 0.07988162]```

This output represents the coefficients of the windowed low-pass filter. These coefficients are applied to the input signal to achieve low-pass filtering.

To create the high-pass filter, we subtract the low-pass filter response from a delta function.

This delta function is represented by an array with a one at its center and zeros elsewhere.

```delta_function = np.zeros(filter_length)
delta_function[filter_length // 2] = 1

# Subtract the low-pass response from the delta function
h_high_pass = delta_function - h_low_pass_windowed
print(h_high_pass)
```

Output:

```[-0.07988162 -0.08079272 -0.08351319 -0.0880326  -0.09433344 -0.10239112
-0.11217411 -0.12364407 -0.13675597 -0.15145828 -0.16769319 -0.18539682
-0.20449946 -0.22492588 -0.24659561 -0.26942324 -0.29331878 -0.31818802
-0.34393288 -0.3704518  -0.39764018 -0.42539072 -0.45359394 -0.48213851
-0.51091178 -0.53980016 -0.56868962 -0.59746608 -0.62601594 -0.65422647
-0.68198625 -0.70918566 -0.7357173  -0.76147636 -0.7863611  -0.81027325
-0.83311833 -0.85480612 -0.87525093 -0.894372   -0.9120938  -0.92834631
-0.94306533 -0.9561927  -0.96767655 -0.97747153 -0.98553892 -0.99184685
-0.9963704  -0.9990917   0.         -0.9990917  -0.9963704  -0.99184685
-0.98553892 -0.97747153 -0.96767655 -0.9561927  -0.94306533 -0.92834631
-0.9120938  -0.894372   -0.87525093 -0.85480612 -0.83311833 -0.81027325
-0.7863611  -0.76147636 -0.7357173  -0.70918566 -0.68198625 -0.65422647
-0.62601594 -0.59746608 -0.56868962 -0.53980016 -0.51091178 -0.48213851
-0.45359394 -0.42539072 -0.39764018 -0.3704518  -0.34393288 -0.31818802
-0.29331878 -0.26942324 -0.24659561 -0.22492588 -0.20449946 -0.18539682
-0.16769319 -0.15145828 -0.13675597 -0.12364407 -0.11217411 -0.10239112
-0.09433344 -0.0880326  -0.08351319 -0.08079272 -0.07988162]```

The resulting array is the coefficients of the high-pass filter. When applied to an input signal, it allows frequencies higher than the cutoff frequency to pass through, effectively blocking lower frequencies.

## IIR High Pass Filter

First, ensure you have both NumPy and SciPy installed in your Python environment.

```!pip install numpy scipy
```

After installing, import the required modules from NumPy and SciPy:

```import numpy as np
from scipy.signal import butter, cheby1, freqz
```

Define the specifications for your high-pass filter, including the order of the filter, the cutoff frequency, and the type of filter (Butterworth or Chebyshev).

```filter_order = 5  # Order of the filter
cutoff_frequency = 0.3  # Normalized cutoff frequency (0 to 0.5)
ripple_db = 5  # Ripple for Chebyshev filter in decibels
```

For the Butterworth filter, known for its maximally flat frequency response in the passband:

```b_butter, a_butter = butter(filter_order, cutoff_frequency, btype='high', analog=False)
```

For the Chebyshev filter, which allows for a sharper cutoff but introduces ripples in the passband:

```b_cheby, a_cheby = cheby1(filter_order, ripple_db, cutoff_frequency, btype='high', analog=False)
```

Display the coefficients for both filters:

```print("Butterworth Filter Coefficients:\n", b_butter, a_butter)
print("Chebyshev Filter Coefficients:\n", b_cheby, a_cheby)
```

Output:

```Butterworth Filter Coefficients:
[ 0.20188501 -1.00942507  2.01885014 -2.01885014  1.00942507 -0.20188501] [ 1.         -1.97590162  2.01347303 -1.10261798  0.32761833 -0.04070949]
Chebyshev Filter Coefficients:
[ 0.07522326 -0.37611631  0.75223263 -0.75223263  0.37611631 -0.07522326] [ 1.         -0.76509924  1.13123641  0.13615021  0.04480816  0.39784919]
```

These coefficients are crucial for implementing the filters in digital signal processing systems.

Finally, analyze the frequency response of both filters to understand their performance characteristics:

```w, h_butter = freqz(b_butter, a_butter)
w, h_cheby = freqz(b_cheby, a_cheby)
```

In this step, we compare the frequency responses of the Butterworth and Chebyshev filters, focusing on their ripple and roll-off characteristics.

## FFT High Pass Filter

This method involves converting a signal to the frequency domain, applying a high-pass filter, and then transforming it back to the time domain using the inverse FFT.

First, import NumPy:

```import numpy as np
```

Create a sample signal by combining sine waves of different frequencies. This will help demonstrate the effect of the high-pass filter.

```sample_rate = 1000
t = np.arange(0, 1.0, 1.0 / sample_rate)
# Signal with multiple frequencies
signal = np.sin(2 * np.pi * 5 * t) + np.sin(2 * np.pi * 20 * t)
```

Next, apply FFT to transform this signal into the frequency domain.

```fft_signal = np.fft.fft(signal)
frequency = np.fft.fftfreq(t.shape[-1], d=1.0/sample_rate)
```

Design the high-pass filter using a simple frequency domain window that blocks low frequencies and allows high frequencies to pass.

```high_pass_filter = frequency > 10  # 10 Hz cutoff frequency
filtered_fft_signal = fft_signal * high_pass_filter
```

Finally, apply the inverse FFT to convert the signal back to the time domain.

```filtered_signal = np.fft.ifft(filtered_fft_signal)
print(filtered_signal.real)
```

The output of this process is the real part of the filtered time-domain signal, demonstrating the effectiveness of FFT-based high-pass filtering.