EyeTracker element

Failing calibration with a score of 0? Upload this file to your project to overwrite the EyeTracker element (don’t mind the corresponding warning message in the Debug window)

Eye-tracking requires access to the webcam, which itself requires hosting your experiment on a secure domain (https). You can set up your project on either the PCIbex Farm or the original Ibex Farm, but you will need your own server to save the collected data.

Introduced in PennController 1.8, EyeTracker elements let you track which elements from a designated set are being looked at by the participant. It uses the javascript library WebGazer.js developed by Alexandra Papoutsaki’s team.

Creation:

newEyeTracker("tracker", 100, 80)

The number parameters are optional and are used to determine when an element is considered looked-at. In this example, if 80% of the (estimated) looks fall on an element over 100 cycles, then it will be considered looked-at. If these parameters are omitted, an element is considered looked-at as long as one (estimated) look falls on it.

See the page Collecting eye-tracking data to learn how to set up your experiment and your server to collect eye-tracking data.

Commands

eyetracker.add

getEyeTracker(id).add( element1 , element2, ... ) (since PennController 1.8)

Adds elements to be tracked.

Any commands passed to callback will be executed on every cycle (or span of cycles) where the estimated looks fall on one of the specified elements.

Once you add elements to the tracker and use start, the tracker will report 1 (looked-at) or 0 (not looked-at) for every element on every cycle, which will be sent to your PHP script at the end of the trial.

Looked at element also receive the CSS class eyetracked.

Example:

newTrial( newButton("Start").print().wait(newEyeTracker().test.ready()) )

newTrial(
    newEyeTracker("tracker")
        .calibrate(70)
        .train(true)
    ,
    newButton("Click here first").print("20vw","40vh").wait().remove(), 
    newButton("Now Click here").print("60vw","40vh").wait().remove()
    ,
    newText("Left").css("padding","20vw").print("20vw", "40vh"),
    newText("Right").css("padding","20vw").print("60vw", "40vh")
    ,
    getEyeTracker("tracker")
        .stopTraining()
        .add( getText("Left") , getText("Right") )
    ,
    newSelector().add( getText("Left") , getText("Right") ).wait()
)

Will add Left and Right as tracked elements.

eyetracker.calibrate

getEyeTracker(id).calibrate(threshold) (since PennController 1.8)

or getEyeTracker(id).calibrate(threshold,attempts)

Calibrates the eye-tracker so that threshold% of the estimated looks fall on the target.

If the eye-tracker was calibrated before, this command will display a button at the center of the page: after clicking it, participants must fix it for 3s. If fewer than threshold% of the estimated looks fall on the button during those 3s, or if the eye-tracker was not calibrated before, participants will be asked to click on 9 buttons placed along the edges of the page, to help calibrate the eye-tracker.

Execution of the trial’s script is paused until a calibration phase is successful, or after attempts failed calibration phases if a second parameter was provided.

It is recommended that you use this command at the beginning of each eye-tracking trial to ensure that the eye-tracker has a uniformly good calibration level throughout the experiment.

Example:

newTrial( newButton("Start").print().wait(newEyeTracker().test.ready()) )

newTrial(
    newEyeTracker("tracker").calibrate(70)
    ,
    newText("Left").css("padding","20vw").print("20vw", "40vh"),
    newText("Right").css("padding","20vw").print("60vw", "40vh")
    ,
    getEyeTracker("tracker").add( getText("Left") , getText("Right") ).start()
    ,
    newSelector().add( getText("Left") , getText("Right") ).wait()
)

newTrial(
    newEyeTracker("tracker").calibrate(70)
    ,
    newText("Right").css("padding","20vw").print("20vw", "40vh"),
    newText("Left").css("padding","20vw").print("60vw", "40vh")
    ,
    getEyeTracker("tracker").add( getText("Left") , getText("Right") ).start()
    ,
    newSelector().add( getText("Left") , getText("Right") ).wait()
)

Will go through a calibration phase at the beginning of the first eye-tracking trial. The second eye-tracking trial will begin with a central button: if fewer than 70% of the estimated looks fall on the button during the 3s fixation time-window, it will go through a calibration phase again.

eyetracker.callback

getEyeTracker(id).callback( function ) (since PennController 1.8)

Tell the script to continuously execute the specified javascript function as long as an element is being looked at.

The function takes is passed two arguments, the estimated X and Y coordinates. The keyword this in the callback function points to the PennElement instance corresponding to the looked-at element.

Example:

newTrial( newButton("Start").print().wait(newEyeTracker().test.ready()) )

newTrial(
    newEyeTracker("tracker").calibrate(70)
    ,
    newText("lookedAt", "").print()
    ,
    newText("Left").css("padding","20vw").print("20vw", "40vh"),
    newText("Right").css("padding","20vw").print("60vw", "40vh")
    ,
    getEyeTracker("tracker")
        .add( getText("Left") , getText("Right") )
        .callback(function(x,y){ getText("lookedAt").text(`Looking at ${this.id} (${x},${y})`)._runPromises(); })
        .start()
    ,
    newSelector().add( getText("Left") , getText("Right") ).wait()
)

Will display the name of the element being looked-at, along with the X and Y coordinates.

eyetracker.hideFeedback

getEyeTracker(id).hideFeedback() (since PennController 1.8)

Hides the red dot estimating your looks.

The red dot is hidden by default after calibration, but you can turn it back on using showFeedback.

Example:

newTrial( newButton("Start").print().wait(newEyeTracker().test.ready()) )

newTrial(
    newEyeTracker("tracker").calibrate(70)
    ,
    newText("Left").css("padding","20vw").print("20vw", "40vh"),
    newText("Right").css("padding","20vw").print("60vw", "40vh")
    ,
    getEyeTracker("tracker").add( getText("Left") , getText("Right") ).showFeedback().start()
    ,
    newTimer(1000).callback( getEyeTracker("tracker").hideFeedback() ).start()
    ,
    newSelector().add( getText("Left") , getText("Right") ).wait()
)

Will still show the red dot after calibration for 1 second, and hide it after that delay (assuming no selection has been made in the meantime).

eyetracker.log

getEyeTracker(id).log() (since PennController 1.8)

Tell the EyeTracker element to send the collected data points to the PHP script provided by EyeTrackerURL at the end of the trial.

If you do not call log on the EyeTracker element, your server will never receive the eye-tracking data.

eyetracker.showFeedback

getEyeTracker(id).showFeedback() (since PennController 1.8)

Shows the red dot estimating your looks, which is hidden by default after calibration.

Example:

newTrial( newButton("Start").print().wait(newEyeTracker().test.ready()) )

newTrial(
    newEyeTracker("tracker").calibrate(70)
    ,
    newText("Left").css("padding","20vw").print("20vw", "40vh"),
    newText("Right").css("padding","20vw").print("60vw", "40vh")
    ,
    getEyeTracker("tracker")
        .add( getText("Left") , getText("Right") )
        .showFeedback()
        .start()
    ,
    newTimer(1000).callback( getEyeTracker("tracker").hideFeedback() ).start()
    ,
    newSelector().add( getText("Left") , getText("Right") ).wait()
)

Will still show the red dot after calibration for 1 second, and hide it after that delay (assuming no selection has been made in the meantime).

eyetracker.start

getEyeTracker(id).start() (since PennController 1.8)

Starts tracking which element is being looked at.

You should use add first to tell the eye tracker which elements are to be tracked.

The EyeTracker element will not collect any data points until start is executed. Use stop to stop tracking elements and sending data to your PHP script.

Example:

newTrial( newButton("Start").print().wait(newEyeTracker().test.ready()) )

newTrial(
    newEyeTracker("tracker").calibrate(70)
    ,
    newText("Left").css("padding","20vw").print("20vw", "40vh"),
    newText("Right").css("padding","20vw").print("60vw", "40vh")
    ,
    getEyeTracker("tracker").add( getText("Left") , getText("Right") ).showFeedback().start()
    ,
    newTimer(1000).callback( getEyeTracker("tracker").hideFeedback() ).start()
    ,
    newSelector().add( getText("Left") , getText("Right") ).wait()
)

Will start tracking looks to the Left and Right elements after they are printed.

eyetracker.stop

getEyeTracker(id).stop() (since PennController 1.8)

Stops tracking which element is being looked at.

The EyeTracker element will stop collecting and sending data points to your PHP script after stop is executed.

Example:

newTrial( newButton("Start").print().wait(newEyeTracker().test.ready()) )

newTrial(
    newEyeTracker("tracker").calibrate(70)
    ,
    newText("Left").css("padding","20vw").print("20vw", "40vh"),
    newText("Right").css("padding","20vw").print("60vw", "40vh")
    ,
    getEyeTracker("tracker").add( getText("Left") , getText("Right") ).showFeedback().start()
    ,
    newTimer(1000).callback( getEyeTracker("tracker").stop() ).start()
    ,
    newSelector().add( getText("Left") , getText("Right") ).wait()
)

Will stop tracking looks after 1 second.

eyetracker.stopTraining

getEyeTracker(id).stopTraining() (since PennController 1.8)

Stops using mouse movements and clicks to train the model that estimates looks.

By default the model is only trained during the calibration phase, but you can train it further with the command train.

Example:

newTrial( newButton("Start").print().wait(newEyeTracker().test.ready()) )

newTrial(
    newEyeTracker("tracker")
        .calibrate(70)
        .train(true)
    ,
    newButton("Click here first").print("20vw","40vh").wait().remove(), 
    newButton("Now Click here").print("60vw","40vh").wait().remove()
    ,
    newText("Left").css("padding","20vw").print("20vw", "40vh"),
    newText("Right").css("padding","20vw").print("60vw", "40vh")
    ,
    getEyeTracker("tracker")
        .stopTraining()
        .add( getText("Left") , getText("Right") )
    ,
    newSelector().add( getText("Left") , getText("Right") ).wait()
)

Will keep training the model after calibration, so that the participant’s successive clicks on the buttons will be used to refine the model, and will stop using mouse events to train the model once Left and Right show up on the page.

eyetracker.train

getEyeTracker(id).train() (since PennController 1.8)

or getEyeTracker(id).train(true)

Starts using mouse movements and clicks to train the model that estimates looks. Pass true as a parameter to make the red dot visible.

By default the model is only trained during the calibration phase, but you can train it further with this command.

Use stopTraining to stop training the model.

Example:

newTrial( newButton("Start").print().wait(newEyeTracker().test.ready()) )

newTrial(
    newEyeTracker("tracker")
        .calibrate(70)
        .train(true)
    ,
    newButton("Click here first").print("20vw","40vh").wait().remove(), 
    newButton("Now Click here").print("60vw","40vh").wait().remove()
    ,
    newText("Left").css("padding","20vw").print("20vw", "40vh"),
    newText("Right").css("padding","20vw").print("60vw", "40vh")
    ,
    getEyeTracker("tracker")
        .stopTraining()
        .add( getText("Left") , getText("Right") )
    ,
    newSelector().add( getText("Left") , getText("Right") ).wait()
)

Will keep training the model after calibration with visual feedback (the red dot) so that the participant’s successive clicks on the buttons will be used to refine the model, and will stop using mouse events to train the model once Left and Right show up on the page.

eyetracker.trainOnMouseMove

getEyeTracker(id).trainOnMouseMove() (since PennController 1.8)

or getEyeTracker(id).trainOnMouseMove(false) (since PennController 1.8)

Tells the model to use mouse movements for its estimations or, if you pass false, to NOT use them.

Use this command after calling train.

Example:

newTrial( newButton("Start").print().wait(newEyeTracker().test.ready()) )

newTrial(
    newEyeTracker("tracker")
        .calibrate(70)
        .train(true)
        .trainOnMouseMove(false)
    ,
    newButton("Click here first").print("20vw","40vh").wait().remove(), 
    newButton("Now Click here").print("60vw","40vh").wait().remove()
    ,
    newText("Left").css("padding","20vw").print("20vw", "40vh"),
    newText("Right").css("padding","20vw").print("60vw", "40vh")
    ,
    getEyeTracker("tracker")
        .stopTraining()
        .add( getText("Left") , getText("Right") )
    ,
    newSelector().add( getText("Left") , getText("Right") ).wait()
)

Will keep training the model after calibration but will only use the mouse clicks on the buttons, and NOT the mouse movements toward them, to refine the model.

Tests

eyetracker.test.calibrated

getEyeTracker(id).test.calibrated() (since PennController 1.8)

Tests whether the EyeTracker went through a calibration procedure (regardless of whether it was successful).

You probably won’t make use of this, unless you don’t want to calibrate the tracker before your trials nor on every trial, but still need to calibrate it at least on the first trial, and your trials are presented in a random order. So yeah, you most likely won’t have to use this.

See test.ready if you want to test whether the participant has granted webGazer access to their webcam, as required by the EyeTracker.

eyetracker.test.ready

getEyeTracker(id).test.ready() (since PennController 1.8)

Tests whether the participant has granted webGazer access to their webcam, as required by the EyeTracker.

Use this early in your experiment, and most usefully in the wait command of a Button to proceed only after access has been granted.

Example:

newButton("Start")
    .print()
    .wait( newEyeTracker().test.ready()
        .failure( newText("Please grant access to your webcam").print() )
    )

Adds a button onto the page and wait for the participant to click it. The presence of the EyeTracker element inside wait also triggers the webcam-access request. The test will only be successful after access has been granted: only then will a click on the button be validated.

If the participant clicks the button before granting access to the webcam, they will see a message inviting them to do so.


Published by Jeremy

Researcher in semantics and pragmatics; Programmer of PennController for IBEX