Skip to content

Sample Experiment 1

Felix Engelmann edited this page Feb 7, 2014 · 7 revisions

This sample program can be used to setup a simple eyetracking experiment to do the following task:

  1. Present sentence based stimuli to record eyetracking patterns of subjects.
  2. Present a response task after each stimulus. Subjects are presented with a question and response to that question is recorded.

The experiment script can be found here: clementine.py

The program can handle multiple trials for individual subjects. All the information for the above task (eyetracking information, response to the question) for each subject is logged in a file. In addition, one can also log information about individual subject at the start of a session.

Here is an example of a single trial in a session.

  • Prompt user to give personal information at the start of a session ~ - e.g. ID, Age, Gender, Mother tongue, etc.
  • Show a welcome screen, provide some instructions about the trial
  • Display a sentence for which eyetracking information needs to be recorded
  • Display a question related to the previous sentence. Record the response.
  • Go to the next trial
  • ...

Changing the sample script for your experiment

Before you run the script, make sure to tweek the code so that it behaves correctly for your experiment. Here are some of the things that you might want to change:

Practice trial file (if any)

In the sample code, look for the following line in def session():

    practStimList = es.StimList("clementine.practice.txt", order='sequential')

You must specify your practice file name instead of clementine.practice.txt above. Make sure that the file is in the correct format (Data Format). If your experiment does not have a practice trial, then all the statements related to practice trials ([2], [4], [13]) should be commented out in def session():

    def session():
       #practStimList = es.StimList("clementine.practice.txt", order='sequential')
       stimList = es.LatinSquareList("clementine.items.txt")
       #practTrialList = [PracticeTrial(stim) for stim in practStimList]
       trialList = [ReadingTrial(stim) for stim in stimList]
       es.calibrateTracker()
       es.ContinueDisplay(u"Willkommen zum Experiment.", response_device=es.Keyboard).run()

       es.ContinueDisplay(u"Lies die Sätze und drücke die Daumentaste, wenn du fertig bist. "
                            u"\nDann beantworte die Frage mit den Zeigefingertasten."
                            u"\nZuerst wirst du ein paar Übungssätze lesen.",
                            vertical_spacing=10, response_device=es.Keyboard).run()
       #for tr in practTrialList: tr.record()
       es.ContinueDisplay(u"Nun beginnen die Experimentalsätze.", response_device=es.Keyboard).run()
       for tr in trialList: tr.record()
       es.TextDisplay(u"Vielen Dank für die Teilnahme. "
                      u"\nLeertaste zum beenden.", response_device=es.Keyboard).run()

Note that you can easily change the welcome message at the start of session by changing the text in es.ContinueDisplay at [7] and [9].

Experiment trial file

In the sample code, look for the following line in def session():

    stimList = es.LatinSquareList("clementine.items.txt")

You must speciify your experiment trial file name instead of clementine.items.txt above. Make sure that the file has the correct format (Data Format).

Response device

At various steps through the experiment, a subject is required to respond. This is of course required when answering the question, but also to move from one trial to the next. As an experiment designer, you can choose the device that will be used by the user to interact with the program. And based on what device is chosen, you will have to specify the response type. The sample code specifies keyboard as the response device. In the experiment a user is required to provide three kinds of reponse

  1. To move from Welcome screen/instruction screen to the next screen, a user can press any key on the keyboard.
  2. To move from a sentence stimulus screen to the question screen, a user can only press a 'spacebar'.
  3. To provide the answer to the question, a user can press 'f' (for 'no' as a response to the question) or 'j' (for 'yes' as a response). This will also initiate the next trial of the session.

To change the response device for the above tasks (to say, a button-pad), you will have to change the value for all the instances of response_device to es.EyeLinkButtons. Note that once you choose es.EyeLinkButtons as your response_device, you have to subsequently provide the appropriate response options for this input type. In particular, you will have to specify the responses for (2) and (3) above.

Notice the changes in possible_resp, cresp, continue_text and of course response_device. Also note the all the es.ContinueDisplay functions in def session() have no response_device, this is because when absent, response_device defaults to es.EyeLinkButtons.

Experiment initialization

When you run your experiment you can change certain aspect of the sample code that specify things like font type [6], font size [5], screen-resolution [4], etc.:

    es.Experiment(session_info = ['session','time','age','gender','study','glasses','nativelang','origin','hsleep','alc24'],
                                  align=('left','center'),
                                  margins=[10,0,10,0],
                                  screen_size=(1600,900),
                                  font_size=18,
                                  font_name='arial.ttf'
                 )

If you don't provide the values to these variable, they will default to values mentioned in defaults.py

The sample code prompts a user at the start of a session to provide certain information such as gender, age, study, etc. This can be seen at [1] above. If you intend to modify these, you will have to change def __init__() of the Experiment class:

    while 1:
       subjectString=self['subject']=raw_input("Enter subject ID (0 for no data logging):").decode('437')
       if self['subject'].isdigit():
          self['subject'] = int(self['subject'])
          subjectString = "%03d"%self['subject']
       for attribute in self['session_info']:
          longattr = attribute
          if attribute == "study": longattr = "field of study"
          if attribute == "gender": longattr = "gender (m/w)"
          if attribute == "glasses": longattr = "glasses/lenses?"
          if attribute == "alc24": longattr = "alcohol in last 24h? (y/n)"
          if attribute == "hsleep": longattr = "hours of sleep last night"
          if attribute == "nativelang": longattr = "mother tongue"
          if attribute == "origin": longattr = "origin (Bundesland)"
          if attribute == "time": longattr = "time (morning/noon/afternoon/evening)"         
          self[attribute] = raw_input("Enter %s: "%longattr).decode('437')
          if self[attribute].isdigit(): self[attribute] = int(self[attribute])
          if self[attribute] == "": self[attribute] = "."

Data Format

The sample code expects a tab-delimited stimuli text file. Each row (except for the 1st) corresponds to a stimulus in a trial. The first row provides the column header.

experiment itemnumber condition sentence question cresp
clementine 01 a Es ist Maria, die das Klavier spielen kann und außerdem noch die Geige, sagte der Lehrer. Kann Maria Klavier spielen? Y
clementine 01 b Es ist Maria, die das Klavier spielen kann und außerdem noch Luise und Jana, sagte der Lehrer. Kann Maria Gitarre spielen? N
clementine 01 c Nur Maria kann das Klavier spielen und außerdem noch die Geige, sagte der Lehrer. Kann Maria Klavier und Geige spielen? Y
clementine 01 d Nur Maria kann das Klavier spielen und außerdem noch Luise und Jana, sagte der Lehrer. Können die Mädchen Flöte spielen? N
clementine 02 a ... ... ...
clementine 02 b ... ... ...
clementine 02 c ... ... ...
clementine 02 d ... ... ...

Running the experiment

To run the program open your terminal (or command line interface) and type:

python clementine.py

Output directory

Once the experiment is over, separate log info. for the subject can be obtained from the data\ directory in your experiment folder. You will also find some more directories in the data directory that contain images shown during each trial (screenimages) and area of interest for each trial (interestareas).

Code documentation

Experiment initialization

The sample code starts with instantiating an Experiment (Experiment) object.:

    es.Experiment(session_info = ['session','time','age','gender','study','glasses','nativelang','origin','hsleep','alc24'],
                                  align=('left','center'),
                                  margins=[10,0,10,0],
                                  screen_size=(1600,900),
                                  font_size=18,
                                  font_name='arial.ttf'
                 )

As mentioned earlier, this is to handle certain global experimental settings such as font type [6], font size [5], screen-resolution [4], etc. One could provide additional (or fewer) parameters while instantiating an object. The complete list (with the default values) can be found in (defaults.py)[../blob/master/EyeScript/defaults.py]. These parameters are related to Screen defaults, Display object defaults, Text data file defaults, Eye tracker data file defaults, Tracker parameters, Drift correction, Cedrus button box. Except for the subject id that a user has to mandatorily enter, the session_info attribute (if provided) enables the experimenter to ask additional question at the start of a session.

Class: (Reading/Practice)Trial

ReadingTrial and PracticeTrial definitions describe the trials in a session. They inherit the Trial class (Trial).

The __init__ method in both these definitions prepares the trial stimulus (that are stored as images in data/screenimages). The image file name comprises of experiment name, item number, and condition. These variables are conjoined by underscores and finally suffixed with '_s1' followed by either a '.jpg' or '.ias' (depending on the type of file) [3]-[6]. s1 stands for 'slide 1'. In case a sequence of sentences is shown in each trial, the corresponding images can be distinguished by s2, s3, etc. The yes/no comprehension question on the other hand is not stored as an image.:

    def __init__(self,stim):
       self.slides = []
       imageName = "%s_%s_%s_s"%(stim['experiment'],
                 stim['itemnumber'],
                 stim['condition'],
                 )

       nr_lines = len(stim['sentence'].split('\n'))
       self.slides.append(es.TextDisplay(unicode(stim['sentence']),
             name='slide_1',
             font_size=14,
             response_device=es.Keyboard,
             logging=['rt','resp'],
             possible_resp = ["space"],
             background_for=self,
             screen_image_file=os.path.join('screenimages', imageName+"1.jpg"),
             interest_area_file=os.path.join('interestareas', imageName+"1.ias"),
             nr_lines = nr_lines
             ))

       self.question = es.ContinueDisplay(stim['question'],
                     name='question',
                     font_size=16,
                     align=('center','center'),
                     vertical_spacing=0,
                     response_device=es.Keyboard,
                     possible_resp = ['f','j'],
                     cresp=stim['cresp'] == "Y" and 'f' or 'j',
                     continue_text = u'f: NEIN            j: JA'
                     )
       self.setMetadata(stim)

The __init__ method takes a StimList object (StimList) (which is basically a list of dictionaries, each dictionary representing a row in the stimuli file) as one of its parameters. The stimulus and the question is instantiated as a TextDisplay [9] and ContinueDisplay [20] object (Display). Note that the parameters used while initializing a TextDisplay and ContinueDisplay object, if not provided, will be obtained from defaults.py. For both the stimulus and the following yes/no comprehension question, the response_device is keyboard (lines [11] and [24] respectively). Notice how the possible_resp differs for them. To move from the stimulus screen one can only use a 'space' [13], whereas one has to use either a 'f' key or a 'j' key to move from a question screen [25]. Additionally, one has to specify the correct response (cresp) for the question slide [26]. This is important so that one can compare subject's response to the correct response. The background_for is initialized to 'self' (instead of 'NONE') so that the screen_image_file and interest_area_file are stored in the appropriate directories.

Finally, we also keep some 'metadata' [31] that will be used for logging purposes later.

The run method of ReadingTrial/PracticeTrial defines the events that comprise a trial:

    def run(self):
       #es.driftCorrect()
       es.startRecording()
       for slide in self.slides:
          x, y = defaultParams['gcTargetCoords']
          if slide.params['nr_lines'] > 1:
             y = y - 0.5*(slide.params['nr_lines']-1)*(slide.params['vertical_spacing']+slide.params['singleline_height'])
          es.gcFixation(target = (x,y))
          slide.run()
       es.stopRecording()
       self.question.run()
       if self.question['acc']:
          correctFeedback.run()
       else:
          incorrectFeedback.run()

The method initiates the eye-tracker (or a stub if tracker is not attached) [2] - [3] (Trials Utility). Each trial begins with displaying a fixation point and waiting until the subject has fixated on it for a minimum duration [8]. We then display the actual slide (a TextDisplay object; Display) [9]. slide.run() not only displays the stimulus slide but also collects the subject response and logs it if necessary. Once all the slides in a trial have been displayed we stop the eyetracker [10] and display the question slide [11] (a ContinueDisplay object; Display). Depending on the response to the question we display the appropriate slide [12]-[15]. correctFeedback and incorrectFeedback are ContinueDisplay objects and have been instantiated as:

    correctFeedback = es.ContinueDisplay("Richtig!",align=('center','center'),duration=800,continue_text="")
    incorrectFeedback = es.ContinueDisplay("Falsch!",align=('center','center'),duration=800,continue_text="")

Session definition

session() defines what a session in your experiment comprises of:

    def session():
       practStimList = es.StimList("clementine.practice.txt", order='sequential')
       stimList = es.LatinSquareList("clementine.items.txt")
       practTrialList = [PracticeTrial(stim) for stim in practStimList]
       trialList = [ReadingTrial(stim) for stim in stimList]
       es.calibrateTracker()
       es.ContinueDisplay(u"Willkommen zum Experiment.", response_device=es.Keyboard).run()
       es.ContinueDisplay(u"Lies die Sätze und drücke die Daumentaste, wenn du fertig bist. "
                      u"\nDann beantworte die Frage mit den Zeigefingertasten."
                  u"\nZuerst wirst du ein paar Übungssätze lesen.",
                  vertical_spacing=10, response_device=es.Keyboard).run()
       for tr in practTrialList: tr.record()
       es.ContinueDisplay(u"Nun beginnen die Experimentalsätze.", response_device=es.Keyboard).run()
       for tr in trialList: tr.record()
       es.TextDisplay(u"Vielen Dank für die Teilnahme. "
                  u"\nLeertaste zum beenden.", response_device=es.Keyboard).run()

A session begins by reading the practice file by instantiating a StimList object (see StimList; a StimList object is basically a list of dictionaries, each dictionary representing a row in the stimulis file). The experiment stimuli file is read as a LatinSquareList object that inherits the StimList class [3]; it ensures that the conditions in the trial file are counterbalanced across subjects. If the experiment stimuli should not be counterbalanced automatically, they can also be read as a StimList with order being either 'sequential' or 'randomized'.

In [4] and [5] these StimList objects are used to instantiate lists of ReadingTrial and PracticeTrial objects (Trial). We then go on to display the welcome screen [7]-[11] followed by presenting the practice [12] and the trial slides [14]. tr.record() is a method in the Trial class (Trial) that initiates each trial (by calling the ReadingTrial/PracticeTrial run() method that we saw earlier), does some logging, etc. We finally end the session by displaying the 'thank you' screen [15].

Running the session

The session() function just dicussed is called by the callback function runSession() (Experiment). runSession() also closes the experiment and writes the log data onto the logfile (textdatafile in Experiment's __init__())

Clone this wiki locally