Time-Frequency Analysis with scipy.signal.spectrogram

Time-Frequency Analysis with scipy.signal.spectrogram

Time-frequency analysis is a technique used to understand how the frequency content of a signal changes over time. This method is essential in various fields such as music, seismology, speech processing, and communications. The basic idea is to decompose a signal into its frequency components and analyze how these components vary with time. Unlike Fourier Transform, which provides the frequency information of the entire signal, time-frequency analysis gives you a more detailed picture by showing the frequency content at each moment in time.

One common approach to time-frequency analysis is the Short-Time Fourier Transform (STFT), where the signal is divided into short segments, and the Fourier Transform is applied to each segment. This results in a two-dimensional representation of the signal, where one axis represents time, and the other represents frequency. However, this method has a limitation known as the time-frequency trade-off, meaning that increasing the resolution in one domain decreases the resolution in the other.

To overcome this limitation, the spectrogram is used. A spectrogram is a visual representation of the spectrum of frequencies in a signal as they vary with time. It’s essentially a collection of STFTs from consecutive time windows, providing a detailed view of the signal’s frequency content over time. The spectrogram function in scipy.signal is a powerful tool for performing time-frequency analysis on signals in Python.

The following Python code example illustrates how to import the necessary module from scipy.signal and generate the spectrogram of a simple sine wave:

import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import spectrogram

# Generate a test signal, a 2 Vrms sine wave at 100 Hz corrupted by 0.001 V**2/Hz of white noise
fs = 10e3
N = 1e5
amp = 2*np.sqrt(2)
freq = 100.0
noise_power = 0.01 * fs / 2
time = np.arange(N) / fs
x = amp * np.sin(2*np.pi*freq*time)
x += np.random.normal(scale=np.sqrt(noise_power), size=time.shape)

# Compute and plot the spectrogram
f, t, Sxx = spectrogram(x, fs)
plt.pcolormesh(t, f, 10 * np.log10(Sxx))
plt.ylabel('Frequency [Hz]')
plt.xlabel('Time [sec]')
plt.title('Spectrogram of a 100Hz Sine Wave')
plt.show()

As seen in the code, the spectrogram function takes in the signal and the sampling frequency as input and outputs the frequencies, time, and the intensity of the frequencies at each time point, which can then be visualized as a heatmap. This visualization provides a wealth of information about the signal’s characteristics and is a foundation for further analysis and processing.

Understanding the Spectrogram Function in scipy.signal

The spectrogram function in scipy.signal has several parameters that can be fine-tuned to adjust the resolution and overlap of the time windows used for the STFT. The primary parameters are:

  • This parameter specifies the type of windowing function to apply to each segment of the signal before computing the Fourier Transform. Common window functions include ‘hann’, ‘hamming’, ‘blackman’, and ‘bartlett’. Each window function has its characteristics and affects the spectrogram’s frequency resolution.
  • This parameter defines the length of each segment. A larger value increases the frequency resolution but decreases the time resolution.
  • This parameter determines the number of points to overlap between segments. A higher overlap will result in a smoother spectrogram with better time resolution.
  • The number of data points used in each segment’s FFT. Increasing this value can improve frequency resolution, but at the cost of computational efficiency.
  • This parameter determines the scaling of the spectrogram. ‘density’ gives the power spectral density (PSD) while ‘spectrum’ gives the power spectrum.
  • This parameter specifies what kind of spectrogram to return; options include ‘psd’, ‘complex’, ‘magnitude’, ‘angle’, ‘phase’.

Below is an example of how to use some of these parameters to generate a spectrogram with a Hamming window and 50% overlap:

from scipy.signal import spectrogram

# Generate a test signal
# ...

# Compute and plot the spectrogram with a Hamming window and 50% overlap
nperseg = int(fs * 0.025)  # 25 ms window
noverlap = int(fs * 0.0125)  # 12.5 ms overlap
f, t, Sxx = spectrogram(x, fs, window='hamming', nperseg=nperseg, noverlap=noverlap)
plt.pcolormesh(t, f, 10 * np.log10(Sxx))
plt.ylabel('Frequency [Hz]')
plt.xlabel('Time [sec]')
plt.title('Enhanced Spectrogram with Hamming Window and 50% Overlap')
plt.show()

By manipulating these parameters, it is possible to optimize the spectrogram for different types of signals and analysis requirements. Understanding and selecting the right parameters is important for effective time-frequency analysis.

Generating and Visualizing Spectrograms

Visualizing the spectrogram very important for analyzing the time-frequency content of the signal effectively. The heatmap produced by the pcolormesh function in matplotlib provides an intuitive way to interpret the results. The intensity of the colors in the heatmap corresponds to the power of the frequency at each time point, with warmer colors indicating higher power and cooler colors indicating lower power. This visual representation can help identify patterns, trends, and anomalies in the signal that may not be apparent from the raw data alone.

Moreover, the spectrogram can be further enhanced by applying a logarithmic scale to the power spectrum, which can help bring out details in signals with a wide dynamic range. The following example demonstrates how to apply a logarithmic scale to the power spectrum:

# Compute and plot the spectrogram with a logarithmic scale
f, t, Sxx = spectrogram(x, fs)
plt.pcolormesh(t, f, 10 * np.log10(Sxx))
plt.yscale('log')  # Apply logarithmic scale to y-axis
plt.ylabel('Frequency [Hz]')
plt.xlabel('Time [sec]')
plt.title('Log-Scaled Spectrogram')
plt.show()

It is also possible to customize the appearance of the spectrogram by adjusting the colormap, setting limits for the frequency and time axes, and adding grid lines for better readability. Here’s an example of customizing the spectrogram’s appearance:

# Customize the appearance of the spectrogram
plt.pcolormesh(t, f, 10 * np.log10(Sxx), cmap='jet')
plt.colorbar(label='Intensity [dB]')
plt.ylim([0, 500])  # Set frequency limits
plt.xlim([0, 10])  # Set time limits
plt.grid(True)  # Add grid lines
plt.ylabel('Frequency [Hz]')
plt.xlabel('Time [sec]')
plt.title('Customized Spectrogram')
plt.show()

Generating and visualizing spectrograms is a powerful technique for conducting time-frequency analysis. By fine-tuning the parameters and customizing the visualization, you can extract meaningful insights from signals that are crucial for various applications in signal processing.

Tuning Parameters for Optimal Spectrogram Analysis

To achieve optimal spectrogram analysis, it’s essential to carefully select and adjust the parameters of the spectrogram function. In addition to the parameters discussed earlier, there are other considerations that can impact the quality and usefulness of the spectrogram.

For example, the choice of the window size (nperseg) has a significant impact on the spectrogram. A larger window size will provide better frequency resolution but poorer time resolution. Conversely, a smaller window size will provide better time resolution but poorer frequency resolution. The following code snippet demonstrates how to experiment with different window sizes:

# Experiment with different window sizes
nperseg_options = [int(fs * 0.01), int(fs * 0.05), int(fs * 0.1)]
for nperseg in nperseg_options:
    f, t, Sxx = spectrogram(x, fs, window='hann', nperseg=nperseg)
    plt.figure()
    plt.pcolormesh(t, f, 10 * np.log10(Sxx))
    plt.ylabel('Frequency [Hz]')
    plt.xlabel('Time [sec]')
    plt.title(f'Spectrogram with Window Size {nperseg/fs} seconds')
    plt.show()

It’s also important to think the trade-offs when selecting the overlap (noverlap) between segments. A higher overlap can provide better time resolution by ensuring that transient events are captured more accurately. However, it also increases the computational load and can lead to redundancy in the data. Here is an example code that illustrates the effect of different overlap values:

# Experiment with different overlap values
noverlap_options = [int(nperseg * 0.25), int(nperseg * 0.5), int(nperseg * 0.75)]
for noverlap in noverlap_options:
    f, t, Sxx = spectrogram(x, fs, window='hann', nperseg=nperseg, noverlap=noverlap)
    plt.figure()
    plt.pcolormesh(t, f, 10 * np.log10(Sxx))
    plt.ylabel('Frequency [Hz]')
    plt.xlabel('Time [sec]')
    plt.title(f'Spectrogram with {noverlap/nperseg*100}% Overlap')
    plt.show()

The choice of the FFT length (nfft) is another parameter that can be adjusted to improve the frequency resolution. A larger FFT length will result in more frequency bins and a finer frequency resolution, but it will also increase the computational cost. Here’s an example that shows how to modify the FFT length:

# Experiment with different FFT lengths
nfft_options = [256, 512, 1024]
for nfft in nfft_options:
    f, t, Sxx = spectrogram(x, fs, window='hann', nperseg=nperseg, noverlap=noverlap, nfft=nfft)
    plt.figure()
    plt.pcolormesh(t, f, 10 * np.log10(Sxx))
    plt.ylabel('Frequency [Hz]')
    plt.xlabel('Time [sec]')
    plt.title(f'Spectrogram with FFT Length {nfft}')
    plt.show()

By understanding the effects of these parameters and how they interact with each other, you can tune the spectrogram to achieve the desired balance between time and frequency resolution. This will enable you to perform more accurate and insightful time-frequency analysis for your specific application.

Applications of Time-Frequency Analysis in Signal Processing

Time-frequency analysis is not just a theoretical concept; it has practical applications in many areas of signal processing. We will now explore some of these applications and see how the spectrogram function in scipy.signal can be used to address real-world problems.

1. Audio Signal Processing: In audio signal processing, time-frequency analysis is used to detect and characterize transient events such as drum beats or speech phonemes. For example, a spectrogram can be used to visualize the harmonic structure of musical instruments over time or to analyze the frequency content of spoken words for speech recognition.

# Analyzing a speech signal
from scipy.io import wavfile

# Load an audio file as a numpy array
sample_rate, audio_signal = wavfile.read('speech.wav')

# Generate the spectrogram
f, t, Sxx = spectrogram(audio_signal, sample_rate)
plt.pcolormesh(t, f, 10 * np.log10(Sxx))
plt.ylabel('Frequency [Hz]')
plt.xlabel('Time [sec]')
plt.title('Spectrogram of Speech Signal')
plt.show()

2. Radar and Sonar: Radar and sonar systems use time-frequency analysis to detect and track moving objects. The Doppler shift in the frequency of the returned signal can provide information about the speed and direction of an object.

# Simulating a Doppler shift in radar signal
object_velocity = 100  # in m/s
doppler_shift = object_velocity / 343.0 * freq
x_doppler = amp * np.sin(2*np.pi*(freq + doppler_shift)*time)

# Generate the spectrogram of Doppler-shifted signal
f, t, Sxx = spectrogram(x_doppler, fs)
plt.pcolormesh(t, f, 10 * np.log10(Sxx))
plt.ylabel('Frequency [Hz]')
plt.xlabel('Time [sec]')
plt.title('Spectrogram of Doppler-shifted Radar Signal')
plt.show()

3. Seismology: In seismology, time-frequency analysis is used to analyze seismic waves to determine the properties of Earth’s interior and to locate and characterize earthquakes. The spectrogram can reveal the frequency content of different wave types and their evolution over time.

4. Communications: In the field of communications, time-frequency analysis is used for signal modulation, demodulation, and to analyze the spectral efficiency of different transmission schemes. It can also help in detecting and mitigating interference and noise in communication signals.

# Modulating a signal for transmission
carrier_freq = 1e3
modulated_signal = x * np.cos(2*np.pi*carrier_freq*time)

# Generate the spectrogram of the modulated signal
f, t, Sxx = spectrogram(modulated_signal, fs)
plt.pcolormesh(t, f, 10 * np.log10(Sxx))
plt.ylabel('Frequency [Hz]')
plt.xlabel('Time [sec]')
plt.title('Spectrogram of Modulated Signal')
plt.show()

These are just a few examples of how time-frequency analysis and the spectrogram function can be applied in signal processing. The ability to analyze how frequencies change over time is invaluable in developing a deeper understanding of signals and improving the performance of various systems.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *