Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion docs/CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ Runtime Changes:
is generally a bad idea, and should be implemented on a case-by-case basis
with care to properly end the trial and log any desired trial data before
starting calibration.
* The :meth:`~klibs.KLEyeTracking.EyeTracker.get_event_queue` method for the
``EyeTracker`` class now returns :obj:`~klibs.KLEyeTracking.EyeEvent`
objects instead of tracker-specific event types.


API Changes:
Expand Down Expand Up @@ -96,7 +99,12 @@ API Changes:
used and complicated the API considerably.
* Removed the unused ``unit`` parameter from the ``now()`` method in the
:class:`~klibs.KLEyeTracking.EyeTracker` class.

* Added a new API :mod:`klibs.KLEyeTracking.events`, defining a clean and
tracker-agnostic way of parsing and extracting information from eye events
from the :class:`~klibs.KLEyeTracking.EyeTracker` event queue.
* Added a new method :meth:`~klibs.KLEyeTracking.EyeTracker.get_sample` to
retrieve the most recent :obj:`~klibs.KLEyeTracking.GazeSample` event from
the tracker.


Fixed Bugs:
Expand Down
11 changes: 11 additions & 0 deletions docs/source/api/KLEyeTracking/events.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Eye Events
==========

During eye tracking, a number of different eye events can occur. A participant
might make a saccade from one location to another, fixate a stimulus for a given
duration, or blink in a critical period. To facilitate working with these events
in your code, klibs converts different eye events into Pythonic objects that can
be easily processed and inspected.

.. automodule:: klibs.KLEyeTracking.events
:members:
25 changes: 20 additions & 5 deletions docs/source/api/KLEyeTracking/eyelink.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
KLEyeLink
=========
EyeLink
=======

.. automodule:: klibs.KLEyeTracking.KLEyeLink
:undoc-members:
:members:
The EyeLink module allows for the use of SR Research EyeLink eye trackers to
be used with the klibs runtime.

If pylink (``sr-research-pylink``) is installed in your project's Python
environment and the ``eye_tracker_availiable`` parameter is set to True, an
instance of this class will be provided as the ``self.el`` attribute of the
Experiment object.

EyeLink trackers record eye movement and gaze data for each session in EDF data
files. The EDF recording for each session is automatically transferred to the
project's ``ExpAssets/EDF`` subfolder when the task exits (unless the task
crashes with an exception).

To avoid duplication with the :class:`~klibs.KLEyeTracking.EyeTracker` docs,
only methods with notable EyeLink-specific details are documented below:

.. autoclass:: klibs.KLEyeTracking.KLEyeLink.EyeLink
:members: calibrate, write
27 changes: 22 additions & 5 deletions docs/source/api/KLEyeTracking/trylink.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
KLTryLink
=========
TryLink
=======

.. automodule:: klibs.KLEyeTracking.KLTryLink
:undoc-members:
:members:
The TryLink module provides an :class:`~klibs.KLEyeTracking.EyeTracker`
implementation using mouse movements to simulate eye movements. For testing and
developing eye tracking experiments without access to real hardware.

In addition to simulating gaze position using the cursor position, TryLink also
generates simulated fixation and saccade events. Blink events are also simulated
when the mouse button is held down if ``dm_click_to_blink`` is enabled in the
project's params file.

TryLink simulation is used by default unless the ``sr-research-pylink`` package
is installed, or if ``eye_tracking`` is True but ``eye_tracker_availiable`` is
False in the project's params file. In addition, TryLink simulation can also be
forced by adding the flag ``-ELx`` to the ``klibs run`` command when launching
an experiment.

To avoid duplication with the :class:`~klibs.KLEyeTracking.EyeTracker` docs,
only methods with notable TryLink-specific details are documented below:

.. autoclass:: klibs.KLEyeTracking.TryLink
:members: calibrate, drift_correct, gaze, write
77 changes: 73 additions & 4 deletions docs/source/api/eyetracking.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
`klibs.KLEyeTracking` - Working with eye trackers
**************************************************
*************************************************

The KLEyeTracking module provides a simple unified interface for working with different kinds of
eye trackers in your experiments. All supported eye trackers make use of the same common interface
Expand All @@ -9,7 +9,7 @@ with the interface should work on any of the different types of eye trackers tha
Currently, KLibs has built-in support for two kinds of eye tracker: SR Research EyeLink trackers,
and a simulated eye tracker that uses mouse movements as a stand-in for gaze and gaze events,
allowing you to write and run eye-tracking experiments without a physical eye tracker
present. You can view more detailed documentation for each of these tracker types here:
present. Relevant details about each tracker type are provided here:

.. toctree::
:maxdepth: 1
Expand Down Expand Up @@ -50,10 +50,79 @@ Additionally, if you want to disable automatic starting and stopping of the eye
recording manually, you can set the parameter ``manual_eyelink_recording`` to True in your
project's ``params.py`` file.

Understanding The Event Queue
-----------------------------

During eye tracking, a number of different eye events can occur. A participant
might make a saccade from one location to another, fixate a stimulus for a given
duration, or blink in a critical period. When these events occur, they are
logged and placed in a queue so that they can be retrieved and inspected for
events of interest (e.g. a saccade away from fixation).

The eye event queue is retrieved using the EyeTracker's
:meth:`~klibs.KLEyeTracking.EyeTracker.get_event_queue` method, which returns a
list of :obj:`~klibs.KLEyeTracking.events.EyeEvent` objects. A table of the
different event types and their corresponding type constants is provided below:

+---------------------+---------------------------------------------------+
| Event Type | Event Class |
+=====================+===================================================+
| ``GAZE_SAMPLE`` | :obj:`~klibs.KLEyeTracking.events.GazeSample` |
+---------------------+---------------------------------------------------+
| ``FIXATION_START`` | :obj:`~klibs.KLEyeTracking.events.FixationStart` |
+---------------------+---------------------------------------------------+
| ``FIXATION_END`` | :obj:`~klibs.KLEyeTracking.events.FixationEnd` |
+---------------------+---------------------------------------------------+
| ``FIXATION_UPDATE`` | :obj:`~klibs.KLEyeTracking.events.FixationUpdate` |
+---------------------+---------------------------------------------------+
| ``SACCADE_START`` | :obj:`~klibs.KLEyeTracking.events.SaccadeStart` |
+---------------------+---------------------------------------------------+
| ``SACCADE_END`` | :obj:`~klibs.KLEyeTracking.events.SaccadeEnd` |
+---------------------+---------------------------------------------------+
| ``BLINK_START`` | :obj:`~klibs.KLEyeTracking.events.BlinkStart` |
+---------------------+---------------------------------------------------+
| ``BLINK_END`` | :obj:`~klibs.KLEyeTracking.events.BlinkEnd` |
+---------------------+---------------------------------------------------+

To avoid problems with event processing when performing multiple tests in a
short interval (e.g. checking for fixations in multiple boundaries every
iteration of a loop), you should use this method to retrieve the eye tracker
event queue once at the start and then pass it to every function that inspects
eye events.

For example, the following code will not reliably detect saccades to the second
boundary:

.. code-block:: python

while self.evm.before('cue_on'):
draw_stimuli()
if self.el.saccade_to_boundary('boundary1'):
print('saccade to boundary 1!')
if self.el.saccade_to_boundary('boundary2'):
print('saccade to boundary 2!')
flip() # refresh screen

This is because the first call to :meth:`saccade_to_boundary` fetches and clears
the eye event queue, meaning that the second call is only processing the
saccades that have happened in the tiny interval since the first call. To fix
this, you should fetch the queue once per loop:

.. code-block:: python

while self.evm.before('cue_on'):
draw_stimuli()
el_q = self.el.get_event_queue() # fetch queue once per loop
if self.el.saccade_to_boundary('boundary1', event_queue=el_q):
print('saccade to boundary 1!')
if self.el.saccade_to_boundary('boundary2', event_queue=el_q):
print('saccade to boundary 2!')
flip() # refresh screen


The EyeTracker API
===================

.. automodule:: klibs.KLEyeTracking.KLEyeTracker
:undoc-members:
.. autoclass:: klibs.KLEyeTracking.EyeTracker
:members:

2 changes: 1 addition & 1 deletion klibs/KLEnvironment.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def db(self):

@property
def el(self):
""":obj:`~klibs.KLEyeTracking.KLEyeTracker`: If 'P.eye_tracking' is True, this is the
""":obj:`~klibs.KLEyeTracking.EyeTracker`: If 'P.eye_tracking' is True, this is the
EyeTracker instance for the KLibs runtime environment. Otherwise, None.

"""
Expand Down
17 changes: 17 additions & 0 deletions klibs/KLExperiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ def __init__(self):
from klibs.KLEventInterface import EventManager
from klibs.KLResponseCollectors import ResponseCollector
from klibs.KLTrialFactory import TrialFactory
from klibs.KLEyeTracking import EyeTracker
from klibs.KLEnvironment import el

super(Experiment, self).__init__()

Expand All @@ -33,6 +35,7 @@ def __init__(self):
self.rc = ResponseCollector() # add default response collector
self.database = self.db # use database from env
self._evm = EventManager()
self._el = el if el else EyeTracker()

self._exp_factors, self._exp_structure = self._get_exp_structure()
self.trial_factory = TrialFactory(self._exp_factors)
Expand Down Expand Up @@ -505,6 +508,20 @@ def participant_info(self):
return out


@property
def el(self):
""":obj:`~klibs.KLEyeTracking.EyeTracker`: The eye tracker instance for the task.

Raises:
RuntimeError: If ``eye_tracking`` is False in the project's params.py file.

"""
if not P.eye_tracking:
e = "Eye tracking must be enabled in params.py before accessing self.el."
raise RuntimeError(e)
return self._el


@property
def evm(self):
""":obj:`~klibs.KLEventInterface.EventManager`: The trial event sequencer for
Expand Down
Loading
Loading