+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +
+

Tutorial: Processing Eye-Tracking Data with PyNeon#

+
+

Step 1: Loading Sample Data#

+

First, we’ll load sample eye-tracking data provided by PyNeon.

+
+
[1]:
+
+
+
import numpy as np
+from pyneon import NeonRecording, get_sample_data
+from pyneon.preprocess import *
+
+# Load the sample recording
+recording_dir = get_sample_data("OfficeWalk") / "Timeseries Data" / "walk1-e116e606"
+recording = NeonRecording(recording_dir)
+
+
+
+
+
+

Step 2: Constructing Event Times#

+

We’ll create a list of event times from 0 to 100 seconds at intervals of 0.1 seconds. These events will serve as reference points for creating epochs.

+
+
[2]:
+
+
+
# Create a list of event times from 0 to 100 seconds at 0.1-second intervals
+tlist = np.arange(0, 100, 0.1)
+global_ref_time = recording.start_time
+
+# Construct event times DataFrame
+event_times = construct_event_times(
+    t_refs=tlist,
+    t_before=0.1,  # 0.1 seconds before the event
+    t_after=0.5,  # 0.5 seconds after the event
+    description="test_event",
+    global_t_ref=global_ref_time,
+    time_unit="s",  # Specify that t_refs are in seconds
+)
+
+# Display the first few event times
+print(event_times.head())
+
+
+
+
+
+
+
+
+          t_ref     t_before      t_after description
+0  1.725032e+18  100000000.0  500000000.0  test_event
+1  1.725032e+18  100000000.0  500000000.0  test_event
+2  1.725032e+18  100000000.0  500000000.0  test_event
+3  1.725032e+18  100000000.0  500000000.0  test_event
+4  1.725032e+18  100000000.0  500000000.0  test_event
+
+
+
+
+

Step 3: Verifying Event Intervals#

+

Check the average interval between events to confirm they are correctly spaced.

+
+
[3]:
+
+
+
# Calculate the average difference between subsequent event times
+average_interval = np.mean(np.diff(event_times["t_ref"]))
+
+print(f"Average interval between events: {average_interval} nanoseconds")
+
+
+
+
+
+
+
+
+Average interval between events: 100000000.0 nanoseconds
+
+
+

This confirms that events are spaced 0.1 seconds (100,000,000 nanoseconds) apart.

+
+
+

Step 4: Creating Epochs from the Data#

+

Use the create_epoch function to create epochs from the gaze data based on the event times.

+
+
[4]:
+
+
+
# Create epochs from the gaze data
+epochs_df, annotated_data = create_epoch(recording.gaze.data, event_times)
+
+
+
+
+
[5]:
+
+
+
print(epochs_df.iloc[0])
+
+
+
+
+
+
+
+
+epoch id                                                      29
+t_ref                                        1725032224427000064
+t_before                                             100000000.0
+t_after                                              500000000.0
+description                                           test_event
+epoch data             timestamp [ns]  gaze x [px]  gaze y [p...
+Name: 0, dtype: object
+
+
+

Every row in the epochs_df file has information about the epoch as well as the data assigned to this epoch.

+
+
[26]:
+
+
+
print(annotated_data.iloc[0])
+
+
+
+
+
+
+
+
+timestamp [ns]     1725032224852161732
+gaze x [px]                   1067.486
+gaze y [px]                    620.856
+worn                              True
+fixation id                          1
+blink id                          <NA>
+azimuth [deg]                 16.21303
+elevation [deg]              -0.748998
+time [s]                           0.0
+epoch id                            34
+description                 test_event
+t_rel                      -74838272.0
+Name: 0, dtype: object
+
+
+

On the other hand, annotated data has the information about the current epoch appended at the end.

+

Alternatively, an epoch object can also be created without an event_times object but rather the info needed to create one

+
+
[29]:
+
+
+
epochs_df, annotated_data = create_epoch(
+    recording.gaze.data,
+    times_df=None,
+    t_refs=tlist,
+    t_before=0.1,
+    t_after=0.5,
+    global_t_ref=global_ref_time,
+    time_unit="s",
+    description="test_event",
+)
+
+
+
+

This object is equivalent to the ones created before

+
+
+

Step 5: Initializing the Epoch Class#

+

Initialize the Epoch class with the gaze data and event times.

+
+
[14]:
+
+
+
# Initialize the Epoch object
+
+epochs = Epoch(recording.gaze.data, event_times)
+
+
+
+
+
+

Step 6: Converting Epochs to NumPy Array#

+

Convert the epochs into a NumPy array, specifying the columns of interest

+
+
[15]:
+
+
+
# Convert epochs to NumPy array, selecting specific columns
+ep_np, info = epochs.to_numpy(columns=["gaze x [px]", "gaze y [px]"])
+
+# Display information about the epochs
+print("Column IDs:", info["column_ids"])
+print("Time relative to reference (s):", info["t_rel"] * 1e-9)
+print("Shape of epochs array:", ep_np.shape)
+
+
+
+
+
+
+
+
+NaN values were found in the data.
+Column IDs: ['gaze x [px]', 'gaze y [px]']
+Time relative to reference (s): [-0.1  -0.09 -0.08 -0.07 -0.06 -0.05 -0.04 -0.03 -0.02 -0.01  0.    0.01
+  0.02  0.03  0.04  0.05  0.06  0.07  0.08  0.09  0.1   0.11  0.12  0.13
+  0.14  0.15  0.16  0.17  0.18  0.19  0.2   0.21  0.22  0.23  0.24  0.25
+  0.26  0.27  0.28  0.29  0.3   0.31  0.32  0.33  0.34  0.35  0.36  0.37
+  0.38  0.39  0.4   0.41  0.42  0.43  0.44  0.45  0.46  0.47  0.48  0.49
+  0.5 ]
+Shape of epochs array: (955, 61, 2)
+
+
+

Column IDs: The data channels extracted. Times: The common time grid for all epochs. Shape: Indicates there are 1000 epochs, each with 61 time points and 2 data channels.

+
+
+

Step 7: Averaging Across Epochs#

+

Compute the average across all epochs to get the mean time-series.

+
+
[16]:
+
+
+
# Average across epochs (resulting in a 2D array)
+integrated_epochs = np.nanmean(ep_np, axis=0)
+print("Shape after averaging across epochs:", integrated_epochs.shape)
+
+
+
+
+
+
+
+
+Shape after averaging across epochs: (61, 2)
+
+
+
+
+
+
+
+C:\Users\jan-gabriel.hartel\AppData\Local\Temp\ipykernel_12180\2544218633.py:2: RuntimeWarning: Mean of empty slice
+  integrated_epochs = np.nanmean(ep_np, axis=0)
+
+
+

The resulting array has 61 time points and 2 channels.

+
+
+

Step 8: Averaging Over Time#

+

Further, average over time to get a single value per channel.

+
+
[17]:
+
+
+
# Average over time (resulting in a 1D array)
+integrate_in_time = np.nanmean(integrated_epochs, axis=0)
+print("Averaged values over time:", integrate_in_time)
+
+
+
+
+
+
+
+
+Averaged values over time: [784.02903257 539.72164082]
+
+
+

his gives the overall mean for each data channel.

+
+
+

Conclusion#

+

In this tutorial, we’ve demonstrated how to:

+
    +
  • Load sample eye-tracking data using PyNeon.

  • +
  • Construct event times for epoching the data.

  • +
  • Create epochs from continuous gaze data.

  • +
  • Convert epochs into a NumPy array for analysis.

  • +
  • Perform averaging across epochs and over time.

  • +
+

These steps are essential for preprocessing eye-tracking data and can be extended for more advanced analyses.

+
+
+ + +
+ + + + + +
+ +
+
+
+ +
+ + + + + + +
+
+ +
+ +