{ "cells": [ { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ ".. _eyelink_demo:\n", "\n", ".. title:: Eyelink Demo" ] }, { "cell_type": "markdown", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "# Example setup for Eyelink 1000 Plus, using PsychoPy 3.0." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Sample code to run SR Research Eyelink eyetracking system. Code is optimized for the Eyelink 1000 Plus (5.0), but should be compatiable with earlier systems." ] }, { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ ".. cssclass:: table-of-contents eyelink\n", "\n", ".. contents::\n", " :local:\n", " :depth: 3\n" ] }, { "cell_type": "markdown", "metadata": { "raw_mimetype": "text/restructuredtext", "toc-hr-collapsed": false }, "source": [ "## Before Running the Participant" ] }, { "cell_type": "markdown", "metadata": { "raw_mimetype": "text/restructuredtext", "toc-hr-collapsed": true }, "source": [ "### Import package" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "nbsphinx": "hidden", "tags": [ "removecell" ] }, "outputs": [], "source": [ "import os\n", "from pathlib import Path\n", "os.chdir('C:\\\\Users\\\\mdl-admin\\\\Desktop\\\\mdl')\n", "import imhr\n", "root = Path(imhr.__file__).parent" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import imhr" ] }, { "cell_type": "markdown", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "Set task parameters, either directly from PsychoPy or created manually." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from psychopy import visual, core\n", "expInfo = {'condition': 'even', u'participant': '001', 'dominant eye': 'left', 'corrective': 'False'}\n", "subject = expInfo['participant']\n", "dominant_eye = expInfo['dominant eye']\n", "# `psychopy.core.Clock.CountdownTimer` instance\n", "routineTimer = core.CountdownTimer()\n", "# `psychopy.visual.window.Window` instance\n", "window = visual.Window(size=[1920, 1080], fullscr=False, allowGUI=True, units='pix', winType='pyglet', color=[110,110,110], colorSpace='rgb255')\n", "window.flip()" ] }, { "cell_type": "markdown", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "### Initialize ``imhr.eyetracking.Eyelink``" ] }, { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ ".. note:: Before initializing :func:`imhr.eyetracking.Eyelink.start_recording`, make sure code is placed after PsychoPy window instance has been created in the experiment file. This window will be used in the calibration function." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Initialize imhr.eyetracking.run()\n", "eyetracking = imhr.eyetracking.Eyelink(window=window, libraries=True, subject=subject, timer=routineTimer, demo=True)" ] }, { "cell_type": "markdown", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "### Connect to the Eyelink Host" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This controls the parameters to be used when running the eyetracker." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "param = eyetracking.connect(calibration_type=13)" ] }, { "cell_type": "markdown", "metadata": { "raw_mimetype": "text/restructuredtext", "toc-hr-collapsed": false }, "source": [ "## Preparing the Participant" ] }, { "cell_type": "markdown", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "### Set the dominant eye" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This step is required for recieving gaze coordinates from Eyelink->PsychoPy." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "eye_used = eyetracking.set_eye_used(eye=dominant_eye)" ] }, { "cell_type": "markdown", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "### Start calibration" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When calibration has been completed, it returns **True**." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# start calibration\n", "calibration = eyetracking.calibration()" ] }, { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ ".. note:: \n", "\n", " # Enter the key \"o\" on the calibration instance. This will begin the task. \n", " # The Calibration, Validation, 'task-start' events are controlled by the keyboard.\n", " # Calibration (\"c\"), Validation (\"v\"), task-start (\"o\") respectively.\n" ] }, { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ ".. cssclass: figure\n", "\n", "**Figure:** Sample calibration.\n", "\n", ".. plot::\n", "\n", " # create image\n", " import matplotlib.pyplot as plt\n", " import matplotlib.image as image\n", " from pathlib import Path\n", " # path\n", " # draw plot\n", " #plt.figure(figsize=(20,6), dpi=400, facecolor='#ffffff')\n", " fig, ax = plt.subplots()\n", " ## load roi\n", " im = image.imread('/Users/mdl-admin/Desktop/mdl/docs/source/examples/eyetracking/demo/img/calibration.png')\n", " ax.imshow(im)\n", " # labels\n", " ax.get_xaxis().set_ticks([])\n", " ax.get_yaxis().set_ticks([])\n", " # save\n", " plt.tight_layout()\n", " plt.subplots_adjust(wspace=0.1)\n", " ## remove frame\n", " ax.axis('off')\n", " #plt.gca().axes.get_yaxis().set_visible(False)\n", " #plt.gca().axes.get_xaxis().set_visible(False)\n", " plt.tick_params(top='off', bottom='off', left='off', right='off', labelleft='off', labelbottom='off')\n", " plt.box(False)\n", " plt.show()\n" ] }, { "cell_type": "markdown", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "### (Optional) Print message to console/terminal" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Allows printing color coded messages to console/terminal/cmd. This may be useful for debugging issues." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "eyetracking.console(\"eyetracking.calibration() started\", \"blue\")" ] }, { "cell_type": "markdown", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "### (Optional) Drift correction" ] }, { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "This can be done at any point after calibration, including before and after :func:`imhr.eyetracking.Eyelink.start_recording` has started. When drift correction has been completed, it returns **True**." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "drift = eyetracking.drift_correction()" ] }, { "cell_type": "markdown", "metadata": { "raw_mimetype": "text/restructuredtext", "toc-hr-collapsed": false }, "source": [ "## Task Starting" ] }, { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ ".. cssclass: figure\n", "\n", "**Figure:** Sample trial.\n", "\n", ".. plot::\n", "\n", " # create image\n", " import matplotlib.pyplot as plt\n", " import matplotlib.image as image\n", " from pathlib import Path\n", " # path\n", " # draw plot\n", " #plt.figure(figsize=(20,6), dpi=400, facecolor='#ffffff')\n", " fig, ax = plt.subplots()\n", " ## load roi\n", " im = image.imread('/Users/mdl-admin/Desktop/mdl/docs/source/examples/eyetracking/demo/img/stimulus.png')\n", " ax.imshow(im)\n", " # labels\n", " ax.get_xaxis().set_ticks([])\n", " ax.get_yaxis().set_ticks([])\n", " # save\n", " plt.tight_layout()\n", " plt.subplots_adjust(wspace=0.1)\n", " ## remove frame\n", " ax.axis('off')\n", " #plt.gca().axes.get_yaxis().set_visible(False)\n", " #plt.gca().axes.get_xaxis().set_visible(False)\n", " plt.tick_params(top='off', bottom='off', left='off', right='off', labelleft='off', labelbottom='off')\n", " plt.box(False)\n", " plt.show()\n" ] }, { "cell_type": "markdown", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "### Start recording" ] }, { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ ".. note:: This should be run at the start of the trial. Also, there is an intentional delay of 150 msec to allow the Eyelink to buffer gaze samples that will show up in your data." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Create stimulus (demonstration purposes only).\n", "filename = \"8380.bmp\"\n", "path = '%s/dist/stimuli/%s'%(root, filename)\n", "size = (1024, 768) #image size\n", "pos = (window.size[0]/2, window.size[1]/2) #positioning image at center of screen\n", "stimulus = visual.ImageStim(win=window, name=\"stimulus\", units='pix', image=path, \n", " pos=(0,0), size=size, flipHoriz=False, flipVert=False, texRes=128, \n", " interpolate=True, depth=-1.0)\n", "stimulus.setAutoDraw(True)\n", "# Note: PsychoPy requires two filps when displaying a new image. The first to clear the screen and the second to update it.\n", "window.flip()\n", "window.flip()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# start recording\n", "eyetracking.start_recording(trial=1, block=1)" ] }, { "cell_type": "markdown", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "### (Optional) Initiate gaze contigent event" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is used for realtime data collection from Eyelink->PsychoPy." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# In the example, a participant is required to look at the bounding cross for a duration\n", "# of 2000 msec before continuing the task. If this doesn't happen and a maxinum duration of \n", "# 10000 msec has occured, drift correction will be initiated.\n", "bound = dict(left=448, top=156, right=1472, bottom=924)\n", "eyetracking.gc(bound=bound, t_min=5000, t_max=20000)" ] }, { "cell_type": "markdown", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "### (Optional) Collect real-time gaze coordinates from Eyelink" ] }, { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ ".. note:: Samples need to be collected at an interval of 1000/(sampling rate) msec to prevent oversampling." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "gxy, ps, sample = eyetracking.sample() # get gaze coordinates, pupil size, and sample" ] }, { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ ".. section:: Example use of eyetracking.sample()\n", "\n", "##### Example use of :obj:`eyetracking.sample`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# In our example, the sampling rate of our device (Eyelink 1000 Plus) is 500Hz.\n", "s1 = 0 # set current time to 0\n", "lgxy = [] # create list of gaze coordinates (demonstration purposes only)\n", "s0 = time.clock() # initial timestamp\n", "# repeat\n", "while True:\n", " # if difference between starting and current time is greater than > 2.01 msec, collect new sample\n", " diff = (s1 - s0)\n", " if diff >= .00201:\n", " gxy, ps, sample = eyetracking.sample() # get gaze coordinates, pupil size, and sample\n", " lgxy.append(gxy) # store in list (not required; demonstration purposes only)\n", " s0 = time.clock() # update starting time\n", " #else set current time\n", " else: \n", " s1 = time.clock()\n", "\n", " #break `while` statement if list of gaze coordiantes >= 20 (not required; demonstration purposes only)\n", " if len(lgxy) >= 200: break" ] }, { "cell_type": "markdown", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "### (Optional) Send messages to Eyelink" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This allows post-hoc processing of event markers (i.e. \"stimulus onset\")." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Sending message \"stimulus onset\".\n", "eyetracking.send_message(msg=\"stimulus onset\")" ] }, { "cell_type": "markdown", "metadata": { "raw_mimetype": "text/restructuredtext", "toc-hr-collapsed": false }, "source": [ "## Task Ending" ] }, { "cell_type": "markdown", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "### Stop recording" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Also (optional) provides trial-level variables to Eyelink." ] }, { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ ".. note:: Variables sent are optional. If they being included, they must be in :obj:`dict` format." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Prepare variables to be sent to Eyelink\n", "variables = dict(stimulus=filename, trial_type='encoding', race=\"black\")\n", "# Stop recording\n", "eyetracking.stop_recording(trial=1, block=1, variables=variables)" ] }, { "cell_type": "markdown", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "### Finish recording" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "eyetracking.finish_recording()" ] }, { "cell_type": "markdown", "metadata": { "raw_mimetype": "text/restructuredtext", "toc-hr-collapsed": true }, "source": [ "### Close PsychoPy" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "window.close()" ] }, { "cell_type": "markdown", "metadata": { "raw_mimetype": "text/restructuredtext", "toc-hr-collapsed": false }, "source": [ "### Other Resources" ] }, { "cell_type": "markdown", "metadata": { "raw_mimetype": "text/restructuredtext", "toc-hr-collapsed": true }, "source": [ "#### Calculate Visual Angle" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "file_extension": ".py", "kernelspec": { "display_name": "Python 3", "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.7.3" }, "mimetype": "text/x-python", "name": "python", "npconvert_exporter": "python", "pygments_lexer": "ipython3", "toc-autonumbering": false, "toc-showcode": false, "toc-showmarkdowntxt": false, "toc-showtags": false, "version": 3 }, "nbformat": 4, "nbformat_minor": 2 }