Fourier Transform#

Hide code cell source
import numpy as np
import matplotlib.pyplot as plt
plt.style.use("seaborn-v0_8-whitegrid")
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[1], line 1
----> 1 import numpy as np
      2 import matplotlib.pyplot as plt
      3 plt.style.use("seaborn-v0_8-whitegrid")

ModuleNotFoundError: No module named 'numpy'
N = 32  # length of the signal x
k0 = 7.5  # frequency of the complex exponential
x = np.cos(2 * np.pi * k0 / N * np.arange(N))

fig, ax = plt.subplots(1, 1, figsize=(8, 2))
ax.plot(np.arange(0, N), x, "o-")
ax.set_title("Signal in time domain")
ax.set_xlabel("Time n")
ax.set_ylabel("x[n]")

plt.tight_layout()
plt.show()
../_images/d5f4636e1914feee10071668d1a23d1db8475edfc350083600c2333f22f16216.png

Discrete Fourier Transform DFT#

In the signal processing literature the DFT is expressed as:

\[ X[k] = \sum_{n=0}^{N-1} x[n] e^{-j2 \pi kn/N} \hspace{1cm} k=0,1,2,...,N-1 \]

Where:

\(x[n]\) is the discrete input signal at time [sample] \(n\)

\(n\) is the sample number (integer)

\(X[k]\) is the \(k\) th spectral sample

\(w_k = k\Omega\) is the \(k\) th frequency sample (rad/sec)

\(\Omega = \dfrac{2\pi}{NT}\)

\(f_s = \dfrac{1}{T}\)

\(N\) is the number of samples in both time and frequency

\(e^{-j2 \pi kn/N}\) is the complex exponential

\(j = \sqrt{-1}\) or \(j = -1^2\)

\(e = \lim_{n\to\inf} (1 + \dfrac{1}{n})^n = 2.71828182845905...\)

# "Manually" compute the DFT of a signal x

nv = np.arange(-N/2, N/2)   # time index
kv = np.arange(-N/2, N/2)   # frequency index

X = np.array([])    # placeholder for the DFT of x
for k in kv:
    s = np.exp(1j * 2 * np.pi * k / N * nv)
    X = np.append(X, sum(x * np.conjugate(s)))


fig, ax = plt.subplots(2, 1, figsize=(8, 5))
plt.title('DFT of a signal x')
ax[0].stem(np.arange(0, N), x, basefmt='b-')
ax[0].set_title('Signal x in time domain')
ax[0].set_xlabel('Time Index')
ax[0].set_ylabel('Amplitude')

ax[1].stem(kv, np.abs(X), basefmt='b-')
ax[1].set_title('Magnitude of the DFT of x')
ax[1].set_xlabel('Frequency Index (bins)')
ax[1].set_ylabel('Magnitude')

plt.tight_layout()
plt.show()
../_images/791a70b471f6e5b002ab76b07276d4e57138f82549dbcb36104785ec7778932a.png

Inverse Discrete Fourier Transform IDFT#

To recover the original signal from the DFT we use the inverse DFT (IDFT) which is expressed as:

\[ x[n] = \frac{1}{N}\sum_{k=0}^{N-1} X[k] e^{j2 \pi nk/N} \hspace{1cm} n=0,1,2,...,N-1 \]

Where: x[n] : discrete input signal at time [sample] n

x_reconstructed = np.array([])
for n in nv:
    s = np.exp(1j * 2 * np.pi * n / N * kv)
    x_reconstructed = np.append(x_reconstructed, 1.0 / N * sum(X * s))

# Plotting for comparison
fig, ax = plt.subplots(2, 1, figsize=(8, 5))
plt.suptitle('Comparison of Original and Reconstructed Signal using IDFT')

ax[0].set_title('Original Signal x in Time Domain')
ax[0].stem(np.arange(0, N), x, label='x[n]')
ax[0].set_xlabel('Time Index')
ax[0].set_ylabel('Amplitude')

ax[1].set_title('Manually Reconstructed Signal from IDFT')
ax[1].stem(np.arange(0, N), x_reconstructed, label='idft(X[k])')
ax[1].set_xlabel('Time Index')
ax[1].set_ylabel('Amplitude')

plt.tight_layout()
plt.show()
/usr/local/lib/python3.10/site-packages/matplotlib/cbook.py:1699: ComplexWarning: Casting complex values to real discards the imaginary part
  return math.isfinite(val)
/usr/local/lib/python3.10/site-packages/numpy/ma/core.py:3387: ComplexWarning: Casting complex values to real discards the imaginary part
  _data[indx] = dval
/usr/local/lib/python3.10/site-packages/matplotlib/cbook.py:1345: ComplexWarning: Casting complex values to real discards the imaginary part
  return np.asarray(x, float)
../_images/dc04c4cc21306ef85496c6c20f7d206191f4c9feadee9c2686210c7d9cd7796e.png
# Function to compute DFT
def compute_dft(signal):
    N = len(signal)
    X = np.zeros(N, dtype=np.complex128)

    for k in range(N):
        X[k] = np.sum(signal * np.exp(-1j * 2 * np.pi * k * np.arange(N) / N))

    return X

# Parameters
fs = 1000  # Sampling frequency in Hz
T = 1/fs   # Sampling period
duration = 1  # Signal duration in seconds
t = np.arange(0, duration, T)  # Time array

# Create a signal composed of two sinusoids
frequencies = [50, 150]
signal = np.sin(2 * np.pi * frequencies[0] * t) + 0.5 * np.sin(2 * np.pi * frequencies[1] * t)

# Compute DFT of the signal
dft_result = compute_dft(signal)

plt.figure(figsize=(8, 5))
plt.subplot(2, 1, 1)
plt.plot(t, signal)
plt.title('Original Signal')
plt.xlabel('Time (s)')
plt.ylabel('Amplitude')

plt.subplot(2, 1, 2)
frequencies_dft = np.fft.fftfreq(len(dft_result), T)
plt.stem(frequencies_dft, np.abs(dft_result))
plt.title('Discrete Fourier Transform (DFT)')
plt.xlabel('Frequency (Hz)')
plt.ylabel('Magnitude')
plt.grid(True)

plt.tight_layout()
plt.show()
../_images/ccd1daab3ed62bdc9b7924be50bac057ab567307c9860fae901f64b605b18a78.png