Windowing#

The most common way to remedy the problem of spectral leakage is to apply a window to the input data before calculating the DFT. Windowing can reduce spectral leakage by attenuating the sidelobes of the DFT”s sinc response. A window is simply a mask that truncates and scales the input data across the window. The rectangular window is the simplist form of a window. This is what the truncated sinusoid example in the spectral leakage notebook used to truncate the input data before applying the DFT. The rectangular window only applies a mask to the data, but it does not apply any scaling. In order to reduce spectral leakage, the window must force the amplitude of the endpoints of an input signal towards a common amplitude. The example below illustrates a variety of window functions.

import numpy as np
import matplotlib.pyplot as plt
from scipy.signal.windows import barthann, bartlett, blackman, blackmanharris, \
    bohman, boxcar, cosine, exponential, flattop, hamming, hann, nuttall, parzen, \
    taylor, triang, tukey


# Sample rate and period
Fs = 32e3
Ts = 1/Fs

# DFT parameters
N = 128

# Sinusoid input parameters
A0 = 1
f = 1e3
theta = np.pi/3
n = np.arange(0, N)
x_in = A0 * np.sin(2*np.pi*f*n*Ts + theta)

# Windows
windows = [barthann, bartlett, blackman, blackmanharris,
           bohman, boxcar, cosine, exponential, flattop, hamming, hann, nuttall,
           parzen, taylor, triang, tukey]

# Plot sinusoidal input
fig, ax = plt.subplots()
ax.grid()
ax.plot(n*Ts*1000, x_in)
ax.set_ylim((-1.5, 1.5))
ax.set_xlabel("Time (ms)")
ax.set_ylabel("Amplitude")
ax.set_title("Sinusoidal Input")
plt.show()

for w in windows:
    # Plot windowed input
    fig, ax = plt.subplots()
    ax.grid()
    ax.plot(n*Ts*1000, x_in * w(N))
    ax.plot(n*Ts*1000, w(N), "--k", alpha=0.5)
    ax.set_xlabel("Time (ms)")
    ax.set_ylabel("Amplitude")
    ax.set_title(f"{w.__name__.capitalize()} Window")
    plt.show()
pass
_images/76a20cf3790cfe2cfbae6dcbe549029705d941ae6bc7a94baaa212f3553d48c6.png _images/ffecf10116cbbc64dbf92ce373991da74488285e695a0a928408618bb269898f.png _images/abbb33f3d76cea0a3de4969981942d6a195f573f809fde0dadb005884a1fca9a.png _images/b63a8f41e079f8b63321de06f2cd72c2ec38fdf2d5faba1fb8e0e566f97f2c0a.png _images/1a7890b12920a6f049fed09005b5fac9fbfbe64ccdb63d423f5a81fa967e7778.png _images/1f4d378aecb3a6e1bdc8dcf862b612d84a702a243599b4b7d35a0a9355555c5c.png _images/ef7c0ed290d7e19b3e9acd3c1727154af69384d7f0a5feb1f6485a0a392f517d.png _images/5238ed4a5b0ad2624ce076f889ff86dbb90524a1076f4ed3cf63876de30ca373.png _images/a1dd45caf25d84045128657d99134d68aba0b243b33f3d3d60a07a4584fe31a8.png _images/fbf68c1896f7e61bfc91ce53c23e043489f6b24595da78eeaf399fbf9c0b2b97.png _images/18a486e63ece424da916c4163330009601e52785e6ac4057150f9712a29e95ad.png _images/2532ba8488376f8211fe788d8bcfd54fc1020dae89454f07655c83dc1b58c608.png _images/7c6cfaf9f37f07349f6e289c89ce980abf5283ab39b217aca21b9e853942b175.png _images/bd3d1ebf17fa065c952cbcf9be13d1d974d6c78680b6678837f346ed51d7addc.png _images/3b44089441393d1e59388ed3729faa97e98ad75e875f972a0bf09ab8acc60e7a.png _images/2d3df52e70b50178299b93717f90052428868ce469c7535a8f21545cae9495dc.png _images/ae8cfc48bfe02a624ae7d1641f40cc307692811a9712791034bee9b2e19cb72e.png

Using Windowing to Detect Small Signals#

Windowing is particularly important when we are trying to detect a low level signal in the presence of a near by high level signal. The example below demonstrates the effects that a Hanning window has when taking the 64 point DFT of an input buffer that contains a strong signal with 3.4 cycles per input buffer and a weak signal with 7 cycles per input buffer.

import numpy as np
import matplotlib.pyplot as plt
from scipy.fft import fft
from scipy.signal.windows import hann

# DFT parameters
N = 64
n = np.arange(0, N)

# Strong signal parameters
A_strong = 1
k_strong = 3.4
theta_strong = 0
x_strong = A_strong * np.sin(2*np.pi * k_strong * n / N + theta_strong)

# Weak signal parameters
A_weak = 0.1
k_weak = 7
theta_weak = 0
x_weak = A_weak * np.sin(2*np.pi * k_weak * n / N + theta_weak)

# Composite signal
x_in = x_strong + x_weak

# Window function 
# (you can try swapping this with other windows to see their responses)
w = hann
window_name = w.__name__.capitalize()

# Calculate the FFTs on the input and windowed input data
y_fft = fft(x_in)
y_fft_wind = fft(x_in * w(N))

# Plot the comparison between the rectangular window and the selected window
fig, ax = plt.subplots(figsize=(15,10))
ax.minorticks_on()
ax.grid(True, which="both", alpha=0.25)
ax.stem(n, np.abs(y_fft), basefmt=" ", markerfmt="sC0", linefmt="C0--", label="Rectangular window response")
ax.plot(n, np.abs(y_fft), c="C0", alpha = 0.8)
ax.stem(n, np.abs(y_fft_wind), basefmt=" ", markerfmt="^C3", linefmt="C3--", label=f"{window_name} window response")
ax.plot(n, np.abs(y_fft_wind), c="C3", alpha = 0.8)
ax.set_xlabel("FFT Bin")
ax.set_ylabel("Magnitude")
ax.set_title(f"Rectangular and {window_name} Window Response Comparison")
ax.set_xlim((0, N/2))
ax.set_ylim(bottom=0)
ax.legend()
pass
_images/233991f15d49d4d83e1622e0783b8bff7819c69645c8b1c10669469da963a14a.png