{ "cells": [ { "cell_type": "markdown", "id": "17b91b54", "metadata": {}, "source": [ "# Working with Raw Gaze Samples" ] }, { "cell_type": "markdown", "id": "7abb9eed", "metadata": {}, "source": [ "Once gaze data have been loaded, they are available as time-ordered raw samples in `gaze.samples`. \n", "\n", "The table below shows a basic example of raw gaze samples after import into `pymovemnts`. Each row corresponds to one time-ordered gaze sample and is stored in the ``samples`` attribute of the {py:class}`~pymovements.Gaze` object. Timestamps are listed in the ``time`` column, and horizontal and vertical gaze positions in pixel coordinates can be found in the `pixel` column. Depending on the loader and input format, additional channels such as binocular coordinates or quality measures may also be present." ] }, { "cell_type": "code", "execution_count": null, "id": "e4e823cc", "metadata": { "tags": [ "hide-input", "remove-stderr" ] }, "outputs": [], "source": [ "import pymovements as pm\n", "from pymovements.gaze.experiment import Experiment\n", "\n", "experiment = Experiment(\n", " screen_width_px=1280,\n", " screen_height_px=1024,\n", " screen_width_cm=38,\n", " screen_height_cm=30.2,\n", " distance_cm=68,\n", " origin='upper left',\n", " sampling_rate=250.0,\n", ")\n", "\n", "gaze = pm.gaze.from_csv(\n", " '../examples/gaze-toy-example.csv',\n", " experiment=experiment,\n", " time_column='time',\n", " pixel_columns=['x', 'y']\n", ")\n", "\n", "gaze.samples.head(5)" ] }, { "cell_type": "markdown", "id": "8410aa2f", "metadata": {}, "source": [ "## Inspecting Raw Samples with Plots" ] }, { "cell_type": "markdown", "id": "f2eae4df", "metadata": {}, "source": [ "Visual inspection is an essential first step when working with newly loaded gaze data. Time-series plots help reveal signal loss, noise, blinks, sampling irregularities, or calibration problems before any preprocessing is applied. Using the {py:func}`~pymovements.plotting.traceplot` function, we can visualize raw gaze samples from a {py:class}`~pymovements.Gaze` object. The plot shows the continuous trajectory of gaze positions across the stimulus, allowing inspection of spatial gaze behavior over time." ] }, { "cell_type": "code", "execution_count": null, "id": "1c5fe9dc", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "pm.plotting.traceplot(gaze)" ] }, { "cell_type": "markdown", "id": "af42a408", "metadata": {}, "source": [ "We can examine how each recorded signal changes over time by using the {py:func}`~pymovements.plotting.tsplot` function. It produces a time-series plot with one line per selected channel (e.g., horizontal and vertical gaze position). The x-axis represents time, as defined by the gaze sample timestamps. In this example, we plot the `x` and `y` channels. " ] }, { "cell_type": "code", "execution_count": null, "id": "334630bc", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "gaze_unnested = gaze.clone()\n", "gaze_unnested.unnest('pixel')\n", "\n", "pm.plotting.tsplot(\n", " gaze_unnested,\n", " xlabel='time [ms]',\n", " channels=['pixel_x', 'pixel_y'],\n", " share_y=False,\n", " line_color='darkblue',\n", " n_rows=2, n_cols=1,\n", " zero_centered_yaxis=False\n", ")" ] }, { "cell_type": "markdown", "id": "b507134e", "metadata": {}, "source": [ "## Transforming Raw Samples\n", "\n", "Raw pixel coordinates are tied to a specific screen setup and viewing distance. For meaningful interpretation and cross-experiment comparison, gaze samples are often transformed into alternative representations. These transformations operate directly on the raw samples and rely on the experimental metadata defined earlier." ] }, { "cell_type": "markdown", "id": "b454bc49", "metadata": {}, "source": [ "### `pix2deg()`: From Pixels to Degrees of Visual Angle\n", "\n", "Eye trackers typically record gaze positions in screen pixels. While useful for display-based inspection, pixel units depend on screen size and viewing distance and are therefore not comparable across setups. The {py:func}`~pymovements.gaze.transforms.pix2deg` function converts pixel coordinates into degrees of visual angle (dva) using the experiment's screen geometry and viewing distance.\n", "\n", "Requirements:\n", "- A pixel-based gaze column must be available (by default named \"pixel\")\n", "- An {py:class}`~pymovements.Experiment` must be attached to the gaze data, because screen size and distance are needed for the conversion" ] }, { "cell_type": "code", "execution_count": null, "id": "53adbc90", "metadata": {}, "outputs": [], "source": [ "gaze.pix2deg()\n", "gaze" ] }, { "cell_type": "markdown", "id": "6550514e", "metadata": {}, "source": [ "Unlike pixels, degrees of visual angle reflect the actual angular displacement of the eye relative to the observer's viewpoint and are therefore comparable across different screen setups and viewing distances. In the plot below, the overall shape of the signal remains the same as in pixel space, since only the unit of measurement has changed. However, the scale of the y-axes differs, reflecting the conversion from screen-dependent coordinates to angular units." ] }, { "cell_type": "code", "execution_count": null, "id": "b9b9137f", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "gaze_unnested = gaze.clone()\n", "gaze_unnested.unnest('position')\n", "\n", "pm.plotting.tsplot(\n", " gaze_unnested,\n", " xlabel='time [ms]',\n", " channels=['position_x', 'position_y'],\n", " share_y=False,\n", " line_color=\"darkblue\",\n", " n_rows=2, n_cols=1,\n", " zero_centered_yaxis=False\n", ")" ] }, { "cell_type": "markdown", "id": "73e04e36", "metadata": {}, "source": [ "### `pos2vel()`: From Position to Velocity\n", "\n", "Many eye-movement measures are derived not from position directly but from its temporal\n", "derivatives. Velocity is computed from changes in gaze position over time and is central to event detection algorithms for saccades and fixations. In pymovements, velocity is computed explicitly from position data with the {py:func}`~pymovements.gaze.transforms.pos2vel` function, using the sampling rate stored in the eye tracker definition." ] }, { "cell_type": "code", "execution_count": null, "id": "51a8e663", "metadata": {}, "outputs": [], "source": [ "gaze.pos2vel()\n", "gaze" ] }, { "cell_type": "markdown", "id": "ad4b3763", "metadata": {}, "source": [ "The following plot illustrates velocity, i.e. how quickly the eye moves at each time point. Periods of relative stability (low velocity) typically correspond to fixations, whereas sharp peaks in the signal indicate rapid eye movements such as saccades." ] }, { "cell_type": "code", "execution_count": null, "id": "1845b738", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "gaze_unnested = gaze.clone()\n", "gaze_unnested.unnest('velocity')\n", "\n", "pm.plotting.tsplot(\n", " gaze_unnested,\n", " channels=['velocity_x', 'velocity_y'],\n", " share_y=False,\n", " line_color=\"darkblue\",\n", " n_rows=2, n_cols=1,\n", " zero_centered_yaxis=False\n", ")" ] }, { "cell_type": "markdown", "id": "203904c6", "metadata": {}, "source": [ "For more information on these preprocessing steps, please see the {doc}`Preprocessing Raw Gaze Data <../tutorials/preprocessing-raw-data>`" ] } ], "metadata": {}, "nbformat": 4, "nbformat_minor": 5 }