Home
Course Guidelines
About the course Prerequite Material References
Python
Jupyter Notebooks Python overview
Exercises
Before the semester start: Installation and exercise setup Week 1: Introduction to Python and libraries Week 2: Vector representations Week 3: Linear Algebra Week 4: Linear Transformations Week 5: Models and least squares Week 6: Assignment 1 - Gaze Estimation Week 7: Model selection and descriptive statistics Week 8: Filtering Week 9: Classification Week 10: Evaluation Week 11: Dimensionality reduction Week 12: Clustering and refresh on gradients Week 13: Neural Networks Week 14: Convolutional Neural Networks (CNN's)
Tutorials
Week 1: Data analysis, manipulation and plotting Week 2: Linear algebra Week 3: Transformations tutorial Week 4: Projection and Least Squares tutorial Week 7: Cross-validation and descriptive statistics tutorial Week 8: Filtering tutorial Week 11: Gradient Descent / Ascent
In-class Exercises
In-class 1 In-class 2 In-class 10 In-class 3 In-class 4 In-class 8
Explorer

Document

  • Overview
  • 1. Filtering gaze data
  • 2. Constructing filters

Content

  • Task 1 Load the data
  • Task 2 Gaussian filter
  • Task 3 Implementing gaussian filter
  • Task 4 Reflect on applying gaussian filter
  • Partials derivatives
    • Task 5 Partial derivatives
    • Task 6 Calculate the derivatives
    • Task 7 Derivatives of a signal
    • Task 8 Saccade detection
    • Task 9 Saccade detection
    • Task 10 Fixation detection
    • Task 11 Visualization of signals
    • Task 12 Noise handling during fixations
    • Task 13 Frame grouping
  • Analyzing results
    • Task 14 Analyse results
    • Task 15 Reflect
  • Inspecting own data
    • Task 16 Combined signal
  • Event detection using gradients
    • Task 17 Combined signal
    • Task 18 Reflect
import filtering_util import numpy as np import matplotlib.pyplot as plt from scipy.ndimage import gaussian_filter1d
import filtering_util
import numpy as np
import matplotlib.pyplot as plt
from scipy.ndimage import gaussian_filter1d

Filtering gaze data

This exercise introduces different techniques for processing and analyzing gaze data using filtering. In particular the exercise removing noise and identifying fixations and saccades in the signal. A fixation is defined as an instance with little or no eye movement. A saccade is an eye movement between two fixations as illustrated in Figure 1 for an example (image from researchGate ).

Figure 1:

Fixation and saccades in gaze data.

The visualizations presented throughout the exercise are based on data collected from test_subject_3 using the grid pattern.

The exercise contains functions in the filtering_util.py file to create plots.

List of individual tasks
  • Task 1: Load the data
  • Task 2: Gaussian filter
  • Task 3: Implementing gaussian filter
  • Task 4: Reflect on applying gaussian filter
  • Task 5: Partial derivatives
  • Task 6: Calculate the derivatives
  • Task 7: Derivatives of a signal
  • Task 8: Saccade detection
  • Task 9: Saccade detection
  • Task 10: Fixation detection
  • Task 11: Visualization of signals
  • Task 12: Noise handling during fixations
  • Task 13: Frame grouping
  • Task 14: Analyse results
  • Task 15: Reflect
  • Task 16: Combined signal
  • Task 17: Combined signal
  • Task 18: Reflect

Although the number of tasks is substantial, each requires minimal programming effort, with many tasks primarily necessitating reflection on the outcomes.

Task 1: Load the data
  1. Run the cell to visualize the grid pattern of test_subject_3 .

    The code is loading the data from the folder of week 6. Alternatively you can copy the data to the folder of this exercise and change the file path accordingly.

test_subject = 'test_subject_3' csv_file =f'../W06/data/output/{test_subject}/grid/pupil_coordinates.csv' pupil_coor= filtering_util.load_csv1(csv_file) filtering_util.plot_pupil_coor(pupil_coor['px'], pupil_coor['py'], 'Original') plt.show()
test_subject = 'test_subject_3'

csv_file =f'../W06/data/output/{test_subject}/grid/pupil_coordinates.csv'

pupil_coor= filtering_util.load_csv1(csv_file)

filtering_util.plot_pupil_coor(pupil_coor['px'], pupil_coor['py'], 'Original')
plt.show()

The next task involves visualizing the pupil coordinates signals px and py for the gaze grid pattern. In this task, a $1D$ Gaussian filter will be applied to smooth the signals individually. Figure 2 illustrates the Gaussian $1D$ filters and the corresponding filtered signal. The cell below convolves a Gaussian filter to gaze data.

Figure 2:

Left: Shows the shape of the Gaussian filter for each value of sigma. Right: Demonstrates the effect of smoothing the noisy signal with each Gaussian filter.

Task 2: Gaussian filter
  1. Reflect on the effect of applying a Gaussian filter to the data and how this can affect the subsequent proccessing of the gaze signal.
#Write your reflections here...
#Write your reflections here...
Task 3: Implementing gaussian filter
  1. Study the function apply_gauss in the cell below.
  2. Apply a Gaussian filter to the gaze data individually for px and py signals. Use the function plot_x_and_y from filtering_util.py to visualize the result.
    • The function takes two arrays of data as parameters.
def apply_gauss(data, sigma = 3): filtered = gaussian_filter1d(data, sigma=sigma, order = 0) return filtered px = np.asarray(pupil_coor['px']) py = np.asarray(pupil_coor['py']) # Write your implementation filtering_util.plot_x_and_y(filtered_x, filtered_y)
def apply_gauss(data, sigma = 3):
    filtered = gaussian_filter1d(data, sigma=sigma, order = 0)
    return filtered



px = np.asarray(pupil_coor['px'])
py = np.asarray(pupil_coor['py'])

# Write your implementation

filtering_util.plot_x_and_y(filtered_x, filtered_y)
Task 4: Reflect on applying gaussian filter
  1. Experiment with the sigma parameter, which value of sigma removes the noise, but maintains the signal?
  2. For which value of sigma does the important features of the signal start to disappear?
#Write your reflections here...
#Write your reflections here...

Partials derivatives

Task 5: Partial derivatives
  1. Recall from the lecture the partial derivatives of a signal calculated by convolving using the filter [-1, 1] . What would be the result of applying the derivative filter to data smoothed by a Gaussian filter? Use the following questions to guide your answers:
    • Why does this filter find the derivative of the signal?
    • Which information is gained by applying such filter to gaze data?
    • How can the output be used for further processing?
    • What is the influence of the sigma value in the Gaussian the filter when finding the partial derivatives?
#Write your reflections here...
#Write your reflections here...

The following task is about implementing a method to find derivatives a signal using the filter $[-1, 1]$.

The function plot_x_and_y_complete from filter_util.py takes two dictionaries one for each coordinates and plots the signals. The dictionaries should contain keys value pairs of the original and processed signal.

Task 6: Calculate the derivatives
  1. Complete the function get_partial_derivatives to apply the derivative filter to a signal and return the absolute value of the result. Use np.convolve for convolution. The absolute value of the derivative is used as only the rate of change is of interest.
def get_partial_derivatives(data): """ Convolve 1D data with a [-1, 1] filter. Args: data (numpy array): 1D array of data. Returns: numpy array: Convolved result. """ # Write your implementation der_x = get_partial_derivatives(filtered_x) der_y = get_partial_derivatives(filtered_y) x_collected = {'underlaying': px, 'filtered': filtered_x, 'derivative': der_x} y_collected = {'underlaying': py, 'filtered': filtered_y, 'derivative': der_y} filtering_util.plot_x_and_y_complete(x_collected, y_collected)
def get_partial_derivatives(data):
    """
    Convolve 1D data with a [-1, 1] filter.

    Args:
        data (numpy array): 1D array of data.

    Returns:
        numpy array: Convolved result.
    """
    # Write your implementation

der_x = get_partial_derivatives(filtered_x)
der_y = get_partial_derivatives(filtered_y)

x_collected = {'underlaying': px, 'filtered': filtered_x, 'derivative': der_x}
y_collected = {'underlaying': py, 'filtered': filtered_y, 'derivative': der_y}

filtering_util.plot_x_and_y_complete(x_collected, y_collected)
Task 7: Derivatives of a signal
  1. Inspect the plots above and reflect on the relation between the original signals, the filtered and the derivative of the filtered signals. In your reflections you may include:
    • What charateristics in the gaze data do the peaks and plateaus encapsulate, use the data collection session to elaborate.
    • What behaviour is expected for the x coordinate of the pupil?
    • What behaviour is expected for the y coordinate of the pupil?
    • What would happen with these if if the pattern was changed?
#Write your reflections here...
#Write your reflections here...

The following task are about detecting events (fixations and saccades) in eye signals using derivative filters.

Task 8: Saccade detection
  1. Complete the function saccade_detection .
    • The function should identify indices for large eye movement changes called saccades using the derivatives of the smoothed signal.
  2. Use the function saccade_detection to detect events in the derivative signal of the filtered signal of both px and py
def saccade_detection(der): """ Detects saccades (non-zero changes) in the derivative data. Parameters: der (1 x N numpy array): Representing the derivative of the signal. Returns: saccades (list): A list of indices where there are non-zero changes (saccades) in `x_dev`. """ saccades = [] # Write your implementation return saccades x_saccades = ... y_saccades = ...
def saccade_detection(der):
    """
    Detects saccades (non-zero changes) in the derivative data.
    Parameters:
    der (1 x N numpy array): Representing the derivative of the signal.
    Returns:
    saccades (list): A list of indices where there are non-zero changes (saccades) in `x_dev`.
    """
    saccades = []
    # Write your implementation
return saccades


x_saccades = ...
y_saccades = ...
Task 9: Saccade detection
  1. Run the cell below to visualize the detected saccades.
x_saccade = {'underlaying': px, 'filtered': filtered_x, 'derivative filtered': der_x, 'saccade': x_saccades} y_saccade = {'underlaying': py, 'filtered': filtered_y, 'derivative filtered': der_y, 'saccade': y_saccades} filtering_util.plot_x_and_y_complete(x_saccade, y_saccade)
x_saccade = {'underlaying': px, 'filtered': filtered_x, 'derivative filtered': der_x, 'saccade': x_saccades}
y_saccade = {'underlaying': py, 'filtered': filtered_y, 'derivative filtered': der_y, 'saccade': y_saccades}

filtering_util.plot_x_and_y_complete(x_saccade, y_saccade)
Task 10: Fixation detection
  1. Complete the function fixation_filtering to handle noise during fixations. Select an appropriate method discussed in the lectures or draw inspiration from the experimental setup to handle noise during fixations.
  2. Use the function fixation_filtering to remove the noise in the filtered signals and return cleaned signals for both px and py .
  3. Use the function get_partial_derivatives to calculate the partial derivatives of the cleaned signals.
  4. Use the function saccade_detection to detect events in the derivative signal of the cleaned signal of both px and py .
def fixation_filtering(px, py, x_saccades, y_saccades): """ Removes noise from the input data based on detected saccades. If a spike is detected at a given index, the previous valid value is used to replace the current value. Parameters: px (1 x N nupy array): Representing the x coordinates (input signal). py (1 x N nupy array): Representing the y coordinates (input signal). x_saccades (list): A list of indices where saccades are detected in the x direction. y_saccades (list): A list of indices where saccades are detected in the y direction. Returns: px_new (list): A list of the cleaned x coordinates. py_new (list): A list of the cleaned y coordinates. """ px_cleaned = [] py_cleaned = [] # Write your implementation return px_cleaned, py_cleaned cleaned_px, cleaned_py = ... der_x_cleaned = ... der_y_cleaned = ... x_saccades_cleaned = ... y_saccades_cleaned = ...
def fixation_filtering(px, py, x_saccades, y_saccades):
    """
    Removes noise from the input data based on detected saccades. If a spike is detected
    at a given index, the previous valid value is used to replace the current value.
    Parameters:
        px (1 x N nupy array): Representing the x coordinates (input signal).
        py (1 x N nupy array): Representing the y coordinates (input signal).
        x_saccades (list): A list of indices where saccades are detected in the x direction.
        y_saccades (list): A list of indices where saccades are detected in the y direction.
    Returns:
    px_new (list): A list of the cleaned x coordinates.
    py_new (list): A list of the cleaned y coordinates.
    """
    px_cleaned = []
    py_cleaned = []
    # Write your implementation
return px_cleaned, py_cleaned



cleaned_px, cleaned_py = ...
der_x_cleaned = ...
der_y_cleaned = ...
x_saccades_cleaned = ...
y_saccades_cleaned = ...
Task 11: Visualization of signals
  1. Run the cell below to visualize the fixations and saccades.
x_collected['cleaned'] = cleaned_px y_collected['cleaned'] = cleaned_py x_collected['derivative cleaned'] = der_x_cleaned y_collected['derivative cleaned'] = der_y_cleaned x_collected['saccade detected'] = x_saccades_cleaned y_collected['saccade detected'] = y_saccades_cleaned filtering_util.plot_x_and_y_complete(x_collected, y_collected, 5) #5 is a scaling factor on the values of the derivative for display purposes filtering_util.plot_pupil_coor(px, py, 'Original') filtering_util.plot_pupil_coor(cleaned_px, cleaned_py, 'Cleaned') plt.show()
x_collected['cleaned'] = cleaned_px
y_collected['cleaned'] = cleaned_py

x_collected['derivative cleaned'] = der_x_cleaned
y_collected['derivative cleaned'] = der_y_cleaned

x_collected['saccade detected'] = x_saccades_cleaned
y_collected['saccade detected'] = y_saccades_cleaned

filtering_util.plot_x_and_y_complete(x_collected, y_collected, 5) #5 is a scaling factor on the values of the derivative for display purposes
filtering_util.plot_pupil_coor(px, py, 'Original')
filtering_util.plot_pupil_coor(cleaned_px, cleaned_py, 'Cleaned')
plt.show()
Task 12: Noise handling during fixations
  1. Reflect on the assumptions of your method for handling noise during fixations in Task 10. What are the advantages and limitations of your approach if any?
  2. Reflect on other strategies that you could use for handling the noise and outliers in the gaze data.
#Write your reflections here...
#Write your reflections here...

The next step is to utilize the charateristics of the derivative of the smoothed signal to detect the frames (pupil coordinates) corresponding to each screen calibration point, so the data can be sorted in corresponding input and label pairs.

Task 13: Frame grouping
  1. Complete the function get_frames_pupil_movement . The function should:
    • Construct a dictionary where the keys represent the calibration point numbers (e.g., '0', '1', ..., '8'), and the associated values are tuples consisting of the start and end frame numbers.
  2. Use the function get_frames_pupil_movement to determine the sections pupil corrdinate sections belonging to each calibration coordinate.
def get_frames_pupil_movement(cleaned_der, data): """ Identifies segments of pupil movement based on changes in the cleaned derivative. Parameters: cleaned_der (list or numpy array): A 1D array of the derivative values of the cleaned data, indicating changes in pupil position over time. data (list or numpy array): The original data corresponding to the pupil movement. Returns: dict ( {string: tuple(int, int)} ): A dictionary where the keys are calibration point numbers (as strings) and the values are tuples containing the start and end frame numbers for each segment. """ frames = {} # Write your implementation filtering_util.plot_pupil_coordinates(frames, px, py, cleaned_px, cleaned_py)
def get_frames_pupil_movement(cleaned_der, data):
    """
    Identifies segments of pupil movement based on changes in the cleaned derivative.
    Parameters:
        cleaned_der (list or numpy array): A 1D array of the derivative values of the cleaned data, indicating changes in pupil position over time.
        data (list or numpy array): The original data corresponding to the pupil movement.

    Returns:
        dict ( {string: tuple(int, int)} ): A dictionary where the keys are calibration point numbers (as strings) and the values are tuples
              containing the start and end frame numbers for each segment.
    """
    frames = {}
# Write your implementation

filtering_util.plot_pupil_coordinates(frames, px, py, cleaned_px, cleaned_py)

Analyzing results

In the following tasks the results of the filtering will be analyzed using mean, variance and covariance at each calibration target for both raw data and filtered data.

Task 14: Analyse results
  1. Run the cell below to visualize the mean, variance and covariance of the data before and after processing.
filtering_util.plot_statistics_comparison(px, py, cleaned_px, cleaned_py, frames)
filtering_util.plot_statistics_comparison(px, py, cleaned_px, cleaned_py, frames)
Task 15: Reflect
  1. Reflect on the results visualized in the plots.
    • What effect does cleaning the data have on the mean and variance?
      • Use your reflection from Task 12 and reevaluate your choice of strategy, has it changes the underlaying signal in terms of mean and variance? Why? Why not?
    • If any target points stands out, why? Relate to the experiment setup and the pupil detection method.

Inspecting own data

Task 16: Combined signal
  1. Go back to Task 1 and update the folder path to target the grid pattern of your own data.
  2. Rerun the steps above.
  3. Reflect on how well does the method generalizes to this dataset. You may use the following questions to guide your answers?
    • How do the results differ from the ones of test_subject_3 ?
    • Is it possible to reuse the same parameters or do they need to be updated?
    • Has the experimental setup any influence on the methods ability to generalizable? Why/why not?
# Write your reflections here...
# Write your reflections here...

Event detection using gradients

In this task you will explore the combined signal (changes in x and y direction simultainously) and use the gradient for event detection. Recall the definition for the gradient of the function $f(x,y)$: $$ \nabla f(x, y) = \left( \frac{\partial f(x,y)}{\partial x}, \frac{\partial f(x,y)}{\partial y} \right) $$ You will use the gradient magnitude, the length of the gradient defined by:

$$ ||\nabla f(x, y)|| = \sqrt{\Big(\frac{\partial f(x,y)}{\partial x}\Big)^2 + \Big(\frac{\partial f(x,y)}{\partial y} \Big)^2} $$
Task 17: Combined signal
  1. Complete the functions:
    • gradient_mangitude : The function should calculate the gradient magnitude of a $2D$ signal given x and y .
def gradient_mangitude(x,y): """ Calculates the gradient mangitude in a 2D space. Args: x (N x 1 numpy array): Array of x coordinates. y (N x 1 numpy array): Array of y coordinates. Returns: (N x 1 numpy array): The Euclidean distance from the origin to the point (x, y). """ # Write your implementation magnitude = gradient_mangitude(der_x, der_y) magnitude_cleaned = gradient_mangitude(der_x_cleaned, der_y_cleaned) collected_signal = {} collected_magnitude = {} collected_signal['x'] = px collected_signal['y'] = py collected_signal['x cleaned'] = cleaned_px collected_signal['y cleaned'] = cleaned_py collected_magnitude['magnitude of gradient'] = magnitude collected_magnitude['magnitude of gradient cleaned'] = magnitude_cleaned filtering_util.plot_x_and_y_complete(collected_signal, collected_magnitude, 5) #5 is a scaling factor on the values of the derivative for display purposes
def gradient_mangitude(x,y):
    """
    Calculates the gradient mangitude in a 2D space.
    Args:
        x (N x 1 numpy array): Array of x coordinates.
        y (N x 1 numpy array): Array of y coordinates.
    
    Returns:
        (N x 1 numpy array): The Euclidean distance from the origin to the point (x, y).
    """
    # Write your implementation

magnitude = gradient_mangitude(der_x, der_y)
magnitude_cleaned = gradient_mangitude(der_x_cleaned, der_y_cleaned)

collected_signal = {}
collected_magnitude = {}
collected_signal['x'] = px
collected_signal['y'] = py
collected_signal['x cleaned'] = cleaned_px
collected_signal['y cleaned'] = cleaned_py
collected_magnitude['magnitude of gradient'] = magnitude
collected_magnitude['magnitude of gradient cleaned'] = magnitude_cleaned



filtering_util.plot_x_and_y_complete(collected_signal, collected_magnitude, 5) #5 is a scaling factor on the values of the derivative for display purposes
Task 18: Reflect
  1. Reflect on the benefit of using the combined signal compared to using the two individual signals.
# Write your reflection here...
# Write your reflection here...