var jsPsychWebgazerValidate = (function (jspsych) {
'use strict';
const info = {
name: "webgazer-validate",
parameters: {
/** Array of points in [x,y] coordinates */
validation_points: {
type: jspsych.ParameterType.INT,
default: [
[10, 10],
[10, 50],
[10, 90],
[50, 10],
[50, 50],
[50, 90],
[90, 10],
[90, 50],
[90, 90],
],
array: true,
},
/**
* Are the validation_points specified as percentages of screen width and height, or the distance in pixels from the center of the screen?
* Options are 'percent' and 'center-offset-pixels'
*/
validation_point_coordinates: {
type: jspsych.ParameterType.SELECT,
default: "percent",
options: ["percent", "center-offset-pixels"],
},
/** Tolerance around validation point in pixels */
roi_radius: {
type: jspsych.ParameterType.INT,
default: 200,
},
/** Whether or not to randomize the order of validation points */
randomize_validation_order: {
type: jspsych.ParameterType.BOOL,
default: false,
},
/** Delay before validating after showing a point */
time_to_saccade: {
type: jspsych.ParameterType.INT,
default: 1000,
},
/** Length of time to show each point */
validation_duration: {
type: jspsych.ParameterType.INT,
default: 2000,
},
/** Validation point size in pixels */
point_size: {
type: jspsych.ParameterType.INT,
default: 20,
},
/** If true, then validation data will be shown on the screen after validation is complete */
show_validation_data: {
type: jspsych.ParameterType.BOOL,
default: false,
},
},
};
/**
* **webgazer-validate**
*
* jsPsych plugin for measuring the accuracy and precision of eye gaze predictions.
* Intended for use with the Webgazer eye-tracking extension, after the webcam has been initialized with the
* `webgazer-init-camera` plugin and calibrated with the `webgazer-calibrate` plugin.
*
* @author Josh de Leeuw
* @see {@link https://www.jspsych.org/plugins/jspsych-webgazer-validate/ webgazer-validate plugin} and
* {@link https://www.jspsych.org/overview/eye-tracking/ eye-tracking overview} documentation on jspsych.org
*/
class WebgazerValidatePlugin {
constructor(jsPsych) {
this.jsPsych = jsPsych;
}
trial(display_element, trial) {
var trial_data = {};
trial_data.raw_gaze = [];
trial_data.percent_in_roi = [];
trial_data.average_offset = [];
trial_data.validation_points = null;
var html = `
`;
display_element.innerHTML = html;
var wg_container = display_element.querySelector("#webgazer-validate-container");
var points_completed = -1;
var val_points = null;
var start = performance.now();
// function to end trial when it is time
const end_trial = () => {
this.jsPsych.extensions.webgazer.stopSampleInterval();
// kill any remaining setTimeout handlers
this.jsPsych.pluginAPI.clearAllTimeouts();
// clear the display
display_element.innerHTML = "";
// move on to the next trial
this.jsPsych.finishTrial(trial_data);
};
const validation_display = (pt) => {
var pt_html = drawValidationPoint(pt[0], pt[1]);
wg_container.innerHTML = pt_html;
var pt_dom = wg_container.querySelector(".validation-point");
var br = pt_dom.getBoundingClientRect();
var x = br.left + br.width / 2;
var y = br.top + br.height / 2;
var pt_start_val = performance.now() + trial.time_to_saccade;
var pt_finish = pt_start_val + trial.validation_duration;
var pt_data = [];
var cancelGazeUpdate = this.jsPsych.extensions["webgazer"].onGazeUpdate((prediction) => {
if (performance.now() > pt_start_val) {
pt_data.push({
x: prediction.x,
y: prediction.y,
dx: prediction.x - x,
dy: prediction.y - y,
t: Math.round(prediction.t - start),
});
}
});
requestAnimationFrame(function watch_dot() {
if (performance.now() < pt_finish) {
requestAnimationFrame(watch_dot);
}
else {
trial_data.raw_gaze.push(pt_data);
cancelGazeUpdate();
next_validation_point();
}
});
};
const next_validation_point = () => {
points_completed++;
if (points_completed == val_points.length) {
validation_done();
}
else {
var pt = val_points[points_completed];
validation_display(pt);
}
};
const validate = () => {
if (trial.randomize_validation_order) {
val_points = this.jsPsych.randomization.shuffle(trial.validation_points);
}
else {
val_points = trial.validation_points;
}
trial_data.validation_points = val_points;
points_completed = -1;
//jsPsych.extensions['webgazer'].resume();
this.jsPsych.extensions.webgazer.startSampleInterval();
//jsPsych.extensions.webgazer.showPredictions();
next_validation_point();
};
const show_validation_data = () => {
var html = "";
for (var i = 0; i < trial.validation_points.length; i++) {
html += drawValidationPoint(trial.validation_points[i][0], trial.validation_points[i][1]);
html += drawCircle(trial.validation_points[i][0], trial.validation_points[i][1], 0, 0, trial.roi_radius);
for (var j = 0; j < trial_data.raw_gaze[i].length; j++) {
html += drawRawDataPoint(trial.validation_points[i][0], trial.validation_points[i][1], trial_data.raw_gaze[i][j].dx, trial_data.raw_gaze[i][j].dy);
}
}
html +=
'