Signal Processing Basics

Written by Luke Chang In this lab, we will cover the basics of convolution, sine waves, and fourier transforms. This lab is largely based on exercises from Mike X Cohen's excellent book, Analyzing Neural Data Analysis: Theory and Practice. If you are interested in learning in more detail about the basics of EEG and time-series analyses I highly recommend his accessible introduction. I also encourage you to watch his accompanying freely available lecturelets to learn more about each topic introduced in this notebook.

Time Domain

First we will work on signals in the time domain. This requires measuring a signal at a constant interval over time. The frequency with which we measure a signal is referred to as the sampling frequency. The units of this are typically described in ||(Hz||) - or the number of cycles per second. It is critical that the sampling frequency is consistent over the entire measurement of the time series.

Dot Product

To understand convolution, we first need to familiarize ourselves with the dot product. The dot product is simply the sum of the elements of a vector weighted by the elements of another vector. This method is commonly used in signal processing, and also in statistics as a measure of similarity between two vectors. Finally, there is also a geometric interpretation which is a mapping between vectors (i.e., the product of the magnitudes of the two vectors scaled by the cosine of the angle between them). For a more in depth overview of the dot product and its relation to convolution, you can watch this optional video. ||[dotproduct_{ab}=\sum\limits_{i=1}^n a_i b_i||]Let's create some vectors of random numbers and see how the dot product works. First, the two vectors need to be of the same length.
Dot Product: 446
What happens when we make the two variables more similar? In the next example we add gaussian noise on top of one of the vectors. What happens to the dot product?
Dot Product: 666.01

Convolution

Convolution in the time domain is an extension of the dot product in which the dot product is computed iteratively over time. One way to think about it is that one signal weights each time point of the other signal and then slides forward over time. Let's call the timeseries variable signal and the other vector the kernel. To gain an intuition of how convolution works, let's play with some data. First, let's create a time series of spikes. Then let's convolve this signal with a boxcar kernel.
Notice how the kernel is only 10 samples long and the boxcar width is about 6 seconds, while the signal is 100 samples long with 5 single pulses. Now let's convolve the signal with the kernel by taking the dot product of the kernel with each time point of the signal. This can be illustrated by creating a matrix of the kernel shifted each time point of the signal. Now, let's take the dot product of the signal with this matrix. Matrix multiplication consists of taking the dot product of the signal vector with each row of this expanded kernel matrix.
You can see that after convolution, each spike has now become the shape of the kernel. Signal: 100, Kernel: 10, Convolved: 109 samples

Step Through Convolution

Use the slider to step through the convolution one timepoint at a time. Watch the kernel (red) slide across the signal and see the output build up.
This process can be performed using np.convolve: What happens if the spikes have different intensities? Now what happens if we switch out the boxcar kernel for a hemodynamic response function (HRF)? Here we will use a double gamma hemodynamic function (HRF) developed by Gary Glover. Use the sliders to explore how the TR and oversampling affect the HRF shape. Oversampling the function will help make it look more smooth. In practice we will want to make sure that the kernel is the correct shape given our sampling resolution. Be sure to set the oversampling to 1. Notice how the function looks more jagged now?
Now let's convolve our event pulses with this HRF kernel. If you are interested in a more detailed overview of convolution in the time domain, I encourage you to watch this video by Mike X Cohen. For more details about convolution and the HRF function, see this overview using python examples.

Oscillations

Ok, now let’s move on to studying time-varying signals that have the shape of oscillating waves. Let’s watch a short video by Mike X Cohen to get some more background on sine waves. Don’t worry too much about the matlab code as we will work through similar Python examples in this notebook.
Oscillations can be described mathematically as: ||(A\sin(2 \pi ft + \theta)||) where ||(f||) is frequency or the speed of the oscillation described in the number of cycles per second (||(Hz||)), Amplitude ||(A||) refers to the height of the waves, which is half the distance of the peak to the trough. Finally, ||(\theta||) describes the phase angle offset, which is in radians. Here we will plot a simple sine wave. Try playing with the different parameters (i.e., amplitude, frequency, & theta) to gain an intuition of how they each impact the shape of the wave. Try the sliders:
Next we will generate a simulation combining multiple sine waves. Try dropping the sampling frequency below 70 Hz to see aliasing. Add noise to make it more realistic. Let’s add all of those signals together to get a more interesting signal. What is the effect of changing the sampling frequency on our ability to measure these oscillations? Try dropping it to be very low (e.g., less than 70 hz.) Notice that signals will alias when the sampling frequency is below the nyquist frequency of a signal. To observe the oscillations, we need to be sampling at least two times for each oscillation cycle. This will result in a jagged view of the data, but we can still theoretically observe the frequency. Practically, higher sampling rates allow us to better observe the underlying signals.

Time & Frequency Domains

We have seen above how to represent signals in the time domain. However, these signals can also be represented in the frequency domain. Let’s get started by watching a short video by Mike X Cohen to get an overview of how a signal can be represented in both of these different domains.

Frequency Domain

In the previous example, we generated a complex signal composed of multiple sine waves oscillating at different frequencies. Typically in data analysis, we only observe the signal and are trying to uncover the generative processes that gave rise to the signal. In this section, we will introduce the frequency domain and how we can identify if there are any frequencies oscillating at a consistent frequency in our signal using the fourier transform. The fourier transform convolves different frequencies of sine waves with our data to identify oscillatory components. One important assumption: stationarity — the generative processes don't vary over time. See this video or a more in depth discussion on stationarity. In practice, this assumption is rarely true. Often it can be useful to use other techniques such as wavelets to look at time x frequency representations. We will not be covering wavelets here, but see this series of videos for more information.

Discrete Time Fourier Transform

We will gain an intution of how the fourier transform works by building our own discrete time fourier transform. Let’s watch this short video about the fourier transform by Mike X Cohen. Don’t worry too much about the details of the discussion on the matlab code as we will be exploring these concepts in python below.
The discrete Fourier transform of variable ||(x||) at frequency ||(f||) can be defined as: ||[X_f = \sum\limits_{k=0}^{n-1} x_k \cdot e^{\frac{-i2\pi fk}{n}}||]where ||(n||) refers to the number of data points in vector ||(x||), and the capital letter ||(X_f||) is the fourier coefficient of time series variable ||(x||) at frequency ||(f||). Essentially, we create a bank of complex sine waves at different frequencies that are linearly spaced. The zero frequency component reflects the mean offset over the entire signal and will simply be zero in our example.

Complex Sine Waves

You may have noticed that we are computing complex sine waves using the np.exp function instead of the np.sin function. ||[e^{i(2\pi ft + \theta)}||]We will not spend too much time on the details, but basically complex sine waves have three components: time, a real part of the sine wave, and the imaginary part of the sine wave, which are basically phase shifted by ||(\frac{\pi}{2}||). ||(1j||) is how we can specify a complex number in python. We can extract the real components using np.real or the imaginary using np.imag. We can visualize complex sine waves in three dimensions. For more information, watch this video. If you need a refresher on complex numbers, you may want to watch this video. In this plot, we show this complex signal in 3 dimensions and also project on two dimensional planes to show that the real and imaginary create a unit circle, and are phase offset by ||(\frac{\pi}{2}||) with respect to time.

Create a filter bank

Ok, now let’s create a bank of n-1 linearly spaced complex sine waves and the plot first 5 waves to see their frequencies. Remember the first basis function is zero frequency (DC) component and reflects the mean offset over the entire signal.
We can visualize all of the sine waves simultaneously using a heatmap representation. Each row is a different sine wave, and columns reflect time. The intensity of the value is like if the sine wave was coming towards and away rather than up and down. Notice how it looks like that the second half of the sine waves appear to be a mirror image of the first half. This is because the first half contain the positive frequencies, while the second half contains the negative frequencies. Negative frequencies capture sine waves that travel in reverse order around the complex plane compared to that travel forward. This becomes more relevant with the hilbert transform, but for the purposes of this tutorial we will be ignoring the negative frequencies.

Estimate Fourier Coefficients

Now let’s take the dot product of each of the sine wave basis set with our signal to get the fourier coefficients. We can scale the coefficients to be more interpretable by dividing by the number of time points and multiplying by 2. Watch this video if you’re interested in a more detailed explanation. Basically, this only needs to be done if you want the amplitude to be in the same units as the original data. In practice, this scaling factor will not change your interpretation of the spectrum.
Recall: freq = [3, 10, 5, 15, 35], amplitude = [5, 15, 10, 5, 7]
Let's learn a few more important details about the DFT:

Inverse Fourier Transform

The fourier transform allows you to represent a time series in the frequency domain. This is a lossless operation, meaning that no information in the original signal is lost by the transform. This means that we can reconstruct the original signal by inverting the operation. Thus, we can create a time series with only the frequency domain information using the inverse fourier transform. Watch this video if you would like a more in depth explanation. ||[x_k = \sum\limits_{k=0}^{n-1} X_f \cdot e^\frac{i2\pi fk}{n}||]Notice that we are computing the dot product between the complex sine wave and the fourier coefficients ||(X||) instead of the time series data ||(x||).

Phase Spectrum

The FFT provides frequency, amplitude, AND phase. Below we show both spectra. Phase carries timing/structural info.

Phase Scrambling

Keep the power spectrum but randomize phases. The signal looks completely different — phase carries the structure!

Fast Fourier Transform

In practice, np.fft.fft is used. Scale by dividing by N.

Convolution Theorem

Convolution in the time domain is the same as multiplication in the frequency domain. This means that time domain convolution computations can be performed much more efficiently in the frequency domain via simple multiplication. (The opposite is also true that multiplication in the time domain is the same as convolution in the frequency domain. Watch this Video for an overview of the convolution theorem and convolution in the frequency domain.
Let's prove it:

Filters

Filters can be classified as finite impulse response (FIR) or infinite impulse response (IIR). These terms describe how a filter responds to a single input impulse. FIR filters have a response that ends at a discrete point in time, while IIR filters have a response that continues indefinitely. Filters are constructed in the frequency domain and have several properties that need to be considered.
  • ripple in the pass-band
  • attenuation in the stop-band
  • steepness of roll-off
  • filter order (i.e., length for FIR filters)
  • time-domain ringing
In general, there is a frequency by time tradeoff. The sharper something is in frequency, the broader it is in time, and vice versa. Here we will use IIR butterworth filters as an example.

High Pass

High pass filters only allow high frequency signals to remain, effectively removing any low frequency information. Here we will construct a high pass butterworth filter and plot it in frequency space.
Notice how the gain scales from [0,1]? Filters can be multiplied by the FFT of a signal to apply the filter in the frequency domain. When the resulting signal is transformed back in the time domain using the inverse FFT, the new signal will be filtered. This can be much faster than applying filters in the time domain. The filter_order parameter adjusts the sharpness of the cutoff in the frequency domain. Try playing with different values to see how it changes the filter plot. What does the filter look like in the temporal domain? Let’s take the inverse FFT and plot it to see what it looks like as a kernel in the temporal domain. Notice how changing the filter order adds more ripples in the time domain.

Low Pass

Low pass filters only retain low frequency signals, which removes any high frequency information. We use filtfilt for zero-phase distortion.

Bandpass

Bandpass filters permit retaining only a specific frequency. Morlet wavelets are an example of a bandpass filter. or example a Morlet wavelet is a gaussian with the peak frequency at the center of a bandpass filter. Let’s try selecting removing specific frequencies

Band-Stop

Bandstop filters remove a specific frequency from the signal

Interactive Filter Explorer

Explore all filter types interactively:
Upper cutoff only used for bandpass/bandstop

Exercises

Exercise 1: Create a simulated time series with 7 different frequencies with noise

Ellipsis

Exercise 2: Show that you can identify each signal using a FFT

Ellipsis

Exercise 3: Remove one frequency with a bandstop filter

Ellipsis

Exercise 4: Remove frequency with a bandstop filter in the frequency domain and reconstruct the signal in the time domain with the frequency removed and compare it to the original

Ellipsis

Graded assignment

The graded version of these exercises is the Signal Processing assignment at the end of this page, which opens in a drawer from the Assignment button in the header. Open it from that page (in molab or by download), sign in with your Dartmouth account inside the notebook, and submit each question when you are ready.

Assignment: Signal Processing

signal-processing · v6

  1. Setup
  2. Q1. Simulate a time series with 7 frequencies plus noise
  3. Q2. Identify each frequency with an FFT
  4. Q3. Remove one frequency with a bandstop filter
  5. Q4. Remove the same frequency in the frequency domain
  6. Q5. Interpretation
Open in molab Opens in a drawer at the bottom of the page, so you can keep reading while you work. Autosaves in this browser.