-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
20 changed files
with
1,895 additions
and
148 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,389 @@ | ||
{ | ||
"cells": [ | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"# Tutorial: Processing Eye-Tracking Data with PyNeon\n", | ||
"\n", | ||
"## Step 1: Loading Sample Data\n", | ||
"\n", | ||
"First, we'll load sample eye-tracking data provided by PyNeon." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 1, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"import numpy as np\n", | ||
"from pyneon import NeonRecording, get_sample_data\n", | ||
"from pyneon.preprocess import *\n", | ||
"\n", | ||
"# Load the sample recording\n", | ||
"recording_dir = get_sample_data(\"OfficeWalk\") / \"Timeseries Data\" / \"walk1-e116e606\"\n", | ||
"recording = NeonRecording(recording_dir)" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"## Step 2: Constructing Event Times\n", | ||
"\n", | ||
"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." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 2, | ||
"metadata": {}, | ||
"outputs": [ | ||
{ | ||
"name": "stdout", | ||
"output_type": "stream", | ||
"text": [ | ||
" t_ref t_before t_after description\n", | ||
"0 1.725032e+18 100000000.0 500000000.0 test_event\n", | ||
"1 1.725032e+18 100000000.0 500000000.0 test_event\n", | ||
"2 1.725032e+18 100000000.0 500000000.0 test_event\n", | ||
"3 1.725032e+18 100000000.0 500000000.0 test_event\n", | ||
"4 1.725032e+18 100000000.0 500000000.0 test_event\n" | ||
] | ||
} | ||
], | ||
"source": [ | ||
"# Create a list of event times from 0 to 100 seconds at 0.1-second intervals\n", | ||
"tlist = np.arange(0, 100, 0.1)\n", | ||
"global_ref_time = recording.start_time\n", | ||
"\n", | ||
"# Construct event times DataFrame\n", | ||
"event_times = construct_event_times(\n", | ||
" t_refs=tlist,\n", | ||
" t_before=0.1, # 0.1 seconds before the event\n", | ||
" t_after=0.5, # 0.5 seconds after the event\n", | ||
" description=\"test_event\",\n", | ||
" global_t_ref=global_ref_time,\n", | ||
" time_unit=\"s\", # Specify that t_refs are in seconds\n", | ||
")\n", | ||
"\n", | ||
"# Display the first few event times\n", | ||
"print(event_times.head())" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"## Step 3: Verifying Event Intervals\n", | ||
"\n", | ||
"Check the average interval between events to confirm they are correctly spaced." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 3, | ||
"metadata": {}, | ||
"outputs": [ | ||
{ | ||
"name": "stdout", | ||
"output_type": "stream", | ||
"text": [ | ||
"Average interval between events: 100000000.0 nanoseconds\n" | ||
] | ||
} | ||
], | ||
"source": [ | ||
"# Calculate the average difference between subsequent event times\n", | ||
"average_interval = np.mean(np.diff(event_times[\"t_ref\"]))\n", | ||
"\n", | ||
"print(f\"Average interval between events: {average_interval} nanoseconds\")" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"This confirms that events are spaced 0.1 seconds (100,000,000 nanoseconds) apart.\n", | ||
"\n", | ||
"## Step 4: Creating Epochs from the Data\n", | ||
"\n", | ||
"Use the create_epoch function to create epochs from the gaze data based on the event times." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 4, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"# Create epochs from the gaze data\n", | ||
"epochs_df, annotated_data = create_epoch(recording.gaze.data, event_times)" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 5, | ||
"metadata": {}, | ||
"outputs": [ | ||
{ | ||
"name": "stdout", | ||
"output_type": "stream", | ||
"text": [ | ||
"epoch id 29\n", | ||
"t_ref 1725032224427000064\n", | ||
"t_before 100000000.0\n", | ||
"t_after 500000000.0\n", | ||
"description test_event\n", | ||
"epoch data timestamp [ns] gaze x [px] gaze y [p...\n", | ||
"Name: 0, dtype: object\n" | ||
] | ||
} | ||
], | ||
"source": [ | ||
"print(epochs_df.iloc[0])" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"Every row in the epochs_df file has information about the epoch as well as the data assigned to this epoch. " | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 26, | ||
"metadata": {}, | ||
"outputs": [ | ||
{ | ||
"name": "stdout", | ||
"output_type": "stream", | ||
"text": [ | ||
"timestamp [ns] 1725032224852161732\n", | ||
"gaze x [px] 1067.486\n", | ||
"gaze y [px] 620.856\n", | ||
"worn True\n", | ||
"fixation id 1\n", | ||
"blink id <NA>\n", | ||
"azimuth [deg] 16.21303\n", | ||
"elevation [deg] -0.748998\n", | ||
"time [s] 0.0\n", | ||
"epoch id 34\n", | ||
"description test_event\n", | ||
"t_rel -74838272.0\n", | ||
"Name: 0, dtype: object\n" | ||
] | ||
} | ||
], | ||
"source": [ | ||
"print(annotated_data.iloc[0])" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"On the other hand, annotated data has the information about the current epoch appended at the end.\n", | ||
"\n", | ||
"Alternatively, an epoch object can also be created without an event_times object but rather the info needed to create one" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 29, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"epochs_df, annotated_data = create_epoch(\n", | ||
" recording.gaze.data,\n", | ||
" times_df=None,\n", | ||
" t_refs=tlist,\n", | ||
" t_before=0.1,\n", | ||
" t_after=0.5,\n", | ||
" global_t_ref=global_ref_time,\n", | ||
" time_unit=\"s\",\n", | ||
" description=\"test_event\",\n", | ||
")" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"This object is equivalent to the ones created before" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"## Step 5: Initializing the Epoch Class\n", | ||
"\n", | ||
"Initialize the Epoch class with the gaze data and event times." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 14, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"# Initialize the Epoch object\n", | ||
"\n", | ||
"epochs = Epoch(recording.gaze.data, event_times)" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"## Step 6: Converting Epochs to NumPy Array\n", | ||
"\n", | ||
"Convert the epochs into a NumPy array, specifying the columns of interest" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 15, | ||
"metadata": {}, | ||
"outputs": [ | ||
{ | ||
"name": "stdout", | ||
"output_type": "stream", | ||
"text": [ | ||
"NaN values were found in the data.\n", | ||
"Column IDs: ['gaze x [px]', 'gaze y [px]']\n", | ||
"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\n", | ||
" 0.02 0.03 0.04 0.05 0.06 0.07 0.08 0.09 0.1 0.11 0.12 0.13\n", | ||
" 0.14 0.15 0.16 0.17 0.18 0.19 0.2 0.21 0.22 0.23 0.24 0.25\n", | ||
" 0.26 0.27 0.28 0.29 0.3 0.31 0.32 0.33 0.34 0.35 0.36 0.37\n", | ||
" 0.38 0.39 0.4 0.41 0.42 0.43 0.44 0.45 0.46 0.47 0.48 0.49\n", | ||
" 0.5 ]\n", | ||
"Shape of epochs array: (955, 61, 2)\n" | ||
] | ||
} | ||
], | ||
"source": [ | ||
"# Convert epochs to NumPy array, selecting specific columns\n", | ||
"ep_np, info = epochs.to_numpy(columns=[\"gaze x [px]\", \"gaze y [px]\"])\n", | ||
"\n", | ||
"# Display information about the epochs\n", | ||
"print(\"Column IDs:\", info[\"column_ids\"])\n", | ||
"print(\"Time relative to reference (s):\", info[\"t_rel\"] * 1e-9)\n", | ||
"print(\"Shape of epochs array:\", ep_np.shape)" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"Column IDs: The data channels extracted.\n", | ||
"Times: The common time grid for all epochs.\n", | ||
"Shape: Indicates there are 1000 epochs, each with 61 time points and 2 data channels.\n", | ||
"\n", | ||
"## Step 7: Averaging Across Epochs\n", | ||
"\n", | ||
"Compute the average across all epochs to get the mean time-series." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 16, | ||
"metadata": {}, | ||
"outputs": [ | ||
{ | ||
"name": "stdout", | ||
"output_type": "stream", | ||
"text": [ | ||
"Shape after averaging across epochs: (61, 2)\n" | ||
] | ||
}, | ||
{ | ||
"name": "stderr", | ||
"output_type": "stream", | ||
"text": [ | ||
"C:\\Users\\jan-gabriel.hartel\\AppData\\Local\\Temp\\ipykernel_12180\\2544218633.py:2: RuntimeWarning: Mean of empty slice\n", | ||
" integrated_epochs = np.nanmean(ep_np, axis=0)\n" | ||
] | ||
} | ||
], | ||
"source": [ | ||
"# Average across epochs (resulting in a 2D array)\n", | ||
"integrated_epochs = np.nanmean(ep_np, axis=0)\n", | ||
"print(\"Shape after averaging across epochs:\", integrated_epochs.shape)" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"The resulting array has 61 time points and 2 channels.\n", | ||
"\n", | ||
"## Step 8: Averaging Over Time\n", | ||
"\n", | ||
"Further, average over time to get a single value per channel." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 17, | ||
"metadata": {}, | ||
"outputs": [ | ||
{ | ||
"name": "stdout", | ||
"output_type": "stream", | ||
"text": [ | ||
"Averaged values over time: [784.02903257 539.72164082]\n" | ||
] | ||
} | ||
], | ||
"source": [ | ||
"# Average over time (resulting in a 1D array)\n", | ||
"integrate_in_time = np.nanmean(integrated_epochs, axis=0)\n", | ||
"print(\"Averaged values over time:\", integrate_in_time)" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"his gives the overall mean for each data channel.\n", | ||
"\n", | ||
"## Conclusion\n", | ||
"\n", | ||
"In this tutorial, we've demonstrated how to:\n", | ||
"\n", | ||
"- Load sample eye-tracking data using PyNeon.\n", | ||
"- Construct event times for epoching the data.\n", | ||
"- Create epochs from continuous gaze data.\n", | ||
"- Convert epochs into a NumPy array for analysis.\n", | ||
"- Perform averaging across epochs and over time.\n", | ||
"\n", | ||
"These steps are essential for preprocessing eye-tracking data and can be extended for more advanced analyses." | ||
] | ||
} | ||
], | ||
"metadata": { | ||
"kernelspec": { | ||
"display_name": "pyneon", | ||
"language": "python", | ||
"name": "python3" | ||
}, | ||
"language_info": { | ||
"codemirror_mode": { | ||
"name": "ipython", | ||
"version": 3 | ||
}, | ||
"file_extension": ".py", | ||
"mimetype": "text/x-python", | ||
"name": "python", | ||
"nbconvert_exporter": "python", | ||
"pygments_lexer": "ipython3", | ||
"version": "3.12.5" | ||
} | ||
}, | ||
"nbformat": 4, | ||
"nbformat_minor": 2 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.