mirror of
https://github.com/jspsych/jsPsych.git
synced 2025-05-12 08:38:11 +00:00
225 lines
8.1 KiB
JavaScript
225 lines
8.1 KiB
JavaScript
/**
|
|
* jspsych-webgazer-calibrate
|
|
* Josh de Leeuw
|
|
**/
|
|
|
|
jsPsych.plugins["webgazer-calibrate"] = (function() {
|
|
|
|
var plugin = {};
|
|
|
|
plugin.info = {
|
|
name: 'webgazer-calibrate',
|
|
description: '',
|
|
parameters: {
|
|
calibration_points: {
|
|
type: jsPsych.plugins.parameterType.INT,
|
|
default: [[10,10], [10,50], [10,90], [50,10], [50,50], [50,90], [90,10], [90,50], [90,90]]
|
|
},
|
|
calibration_mode: {
|
|
type: jsPsych.plugins.parameterType.STRING,
|
|
default: 'click', // options: 'click', 'view'
|
|
},
|
|
repetitions_per_point: {
|
|
type: jsPsych.plugins.parameterType.INT,
|
|
default: 1
|
|
},
|
|
randomize_calibration_order: {
|
|
type: jsPsych.plugins.parameterType.BOOL,
|
|
default: false
|
|
},
|
|
time_to_saccade: {
|
|
type: jsPsych.plugins.parameterType.INT,
|
|
default: 1000
|
|
},
|
|
time_per_point: {
|
|
type: jsPsych.plugins.parameterType.STRING,
|
|
default: 2000
|
|
}
|
|
}
|
|
}
|
|
|
|
// provide options for calibration routines?
|
|
// dot clicks?
|
|
// track a dot with mouse?
|
|
|
|
// then a validation phase of staring at the dot in different locations?
|
|
|
|
plugin.trial = function(display_element, trial) {
|
|
|
|
var html = `
|
|
<div id='webgazer-calibrate-container' style='position: relative; width:100vw; height:100vh'>
|
|
</div>`
|
|
|
|
display_element.innerHTML = html;
|
|
|
|
jsPsych.extensions['webgazer'].showVideo();
|
|
jsPsych.extensions['webgazer'].resume();
|
|
|
|
|
|
//jsPsych.extensions['webgazer'].resume();
|
|
|
|
var wg_container = display_element.querySelector('#webgazer-calibrate-container');
|
|
|
|
show_video_detect_message();
|
|
|
|
function show_video_detect_message(){
|
|
wg_container.innerHTML = "<div style='position: absolute; top: 50%; left: calc(50% - 350px); transform: translateY(-50%); width:700px;'>"+
|
|
"<p>To start, you need to position your head so that the webcam has a good view of your eyes.</p>"+
|
|
"<p>Use the video in the upper-left corner as a guide. Center your face in the box.</p>"+
|
|
"<p>When your face is centered in the box and the box turns green, you can click to continue.</p>"+
|
|
"<button id='jspsych-wg-cont' class='jspsych-btn' disabled>Continue</button>"
|
|
"</div>"+
|
|
"</div>"
|
|
|
|
var observer = new MutationObserver(face_detect_event_observer);
|
|
observer.observe(document, {
|
|
attributes: true,
|
|
attributeFilter: ['style'],
|
|
subtree: true
|
|
});
|
|
|
|
document.querySelector('#jspsych-wg-cont').addEventListener('click', function(){
|
|
observer.disconnect();
|
|
show_begin_calibrate_message();
|
|
})
|
|
}
|
|
|
|
function face_detect_event_observer(mutationsList, observer){
|
|
if(mutationsList[0].target == document.querySelector('#webgazerFaceFeedbackBox')){
|
|
if(mutationsList[0].type == 'attributes' && mutationsList[0].target.style.borderColor == "green"){
|
|
document.querySelector('#jspsych-wg-cont').disabled = false;
|
|
}
|
|
if(mutationsList[0].type == 'attributes' && mutationsList[0].target.style.borderColor == "red"){
|
|
document.querySelector('#jspsych-wg-cont').disabled = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
function show_begin_calibrate_message(){
|
|
jsPsych.extensions['webgazer'].hideVideo();
|
|
if(trial.calibration_mode == 'view'){
|
|
wg_container.innerHTML = "<div style='position: absolute; top: 50%; left: calc(50% - 350px); transform: translateY(-50%); width:700px;'>"+
|
|
"<p>Great! Now the eye tracker will be calibrated to translate the image of your eyes from the webcam to a location on your screen.</p>"+
|
|
"<p>To do this, you need to look at a series of dots.</p>"+
|
|
"<p>Keep your head still, and focus on each dot as quickly as possible. Keep your gaze fixed on the dot for as long as it is on the screen.</p>"+
|
|
"<button id='begin-calibrate-btn' class='jspsych-btn'>Click to begin.</button>"+
|
|
"</div>"
|
|
}
|
|
if(trial.calibration_mode == 'click'){
|
|
wg_container.innerHTML = "<div style='position: absolute; top: 50%; left: calc(50% - 350px); transform: translateY(-50%); width:700px;'>"+
|
|
"<p>Great! Now the eye tracker will be calibrated to translate the image of your eyes from the webcam to a location on your screen.</p>"+
|
|
"<p>To do this, you need to click a series of dots.</p>"+
|
|
"<p>Keep your head still, and click on each dot as it appears. Look at the dot as you click it.</p>"+
|
|
"<button id='begin-calibrate-btn' class='jspsych-btn'>Click to begin.</button>"+
|
|
"</div>"
|
|
}
|
|
document.querySelector('#begin-calibrate-btn').addEventListener('click', function(){
|
|
calibrate();
|
|
});
|
|
}
|
|
|
|
var reps_completed = 0;
|
|
var points_completed = -1;
|
|
var cal_points = null;
|
|
|
|
function calibrate(){
|
|
jsPsych.extensions['webgazer'].resume();
|
|
if(trial.calibration_mode == 'click'){
|
|
jsPsych.extensions['webgazer'].startMouseCalibration();
|
|
}
|
|
next_calibration_round();
|
|
}
|
|
|
|
function next_calibration_round(){
|
|
if(trial.randomize_calibration_order){
|
|
cal_points = jsPsych.randomization.shuffle(trial.calibration_points);
|
|
} else {
|
|
cal_points = trial.calibration_points;
|
|
}
|
|
points_completed = -1;
|
|
next_calibration_point();
|
|
}
|
|
|
|
function next_calibration_point(){
|
|
points_completed++;
|
|
if(points_completed == cal_points.length){
|
|
reps_completed++;
|
|
if(reps_completed == trial.repetitions_per_point){
|
|
calibration_done();
|
|
} else {
|
|
next_calibration_round();
|
|
}
|
|
} else {
|
|
var pt = cal_points[points_completed];
|
|
calibration_display_gaze_only(pt);
|
|
}
|
|
}
|
|
|
|
function calibration_display_gaze_only(pt){
|
|
var pt_html = '<div id="calibration-point" style="width:10px; height:10px; border-radius:10px; border: 1px solid #000; background-color: #333; position: absolute; left:'+pt[0]+'%; top:'+pt[1]+'%;"></div>'
|
|
wg_container.innerHTML = pt_html;
|
|
|
|
var pt_dom = wg_container.querySelector('#calibration-point');
|
|
|
|
if(trial.calibration_mode == 'click'){
|
|
pt_dom.style.cursor = 'pointer';
|
|
pt_dom.addEventListener('click', function(){
|
|
next_calibration_point();
|
|
})
|
|
}
|
|
|
|
if(trial.calibration_mode == 'view'){
|
|
var br = pt_dom.getBoundingClientRect();
|
|
var x = br.left + br.width / 2;
|
|
var y = br.top + br.height / 2;
|
|
|
|
var pt_start_cal = performance.now() + trial.time_to_saccade;
|
|
var pt_finish = performance.now() + trial.time_to_saccade + trial.time_per_point;
|
|
|
|
requestAnimationFrame(function watch_dot(){
|
|
|
|
if(performance.now() > pt_start_cal){
|
|
jsPsych.extensions['webgazer'].calibratePoint(x,y,'click');
|
|
}
|
|
if(performance.now() < pt_finish){
|
|
requestAnimationFrame(watch_dot);
|
|
} else {
|
|
next_calibration_point();
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
function calibration_done(){
|
|
if(trial.calibration_mode == 'click'){
|
|
jsPsych.extensions['webgazer'].stopMouseCalibration();
|
|
}
|
|
wg_container.innerHTML = "";
|
|
end_trial();
|
|
}
|
|
|
|
// function to end trial when it is time
|
|
function end_trial() {
|
|
jsPsych.extensions['webgazer'].pause();
|
|
jsPsych.extensions['webgazer'].hidePredictions();
|
|
jsPsych.extensions['webgazer'].hideVideo();
|
|
|
|
// kill any remaining setTimeout handlers
|
|
jsPsych.pluginAPI.clearAllTimeouts();
|
|
|
|
// gather the data to store for the trial
|
|
var trial_data = {
|
|
|
|
};
|
|
|
|
// clear the display
|
|
display_element.innerHTML = '';
|
|
|
|
// move on to the next trial
|
|
jsPsych.finishTrial(trial_data);
|
|
};
|
|
|
|
};
|
|
|
|
return plugin;
|
|
})(); |