mirror of
https://github.com/jspsych/jsPsych.git
synced 2025-05-11 16:18:11 +00:00
improving validation plugin options
This commit is contained in:
parent
bce3b5576e
commit
9225d27f0d
@ -20,17 +20,20 @@
|
||||
|
||||
var calibration = {
|
||||
type: 'webgazer-calibrate',
|
||||
calibration_points: [[50,50], [25,25], [25,75], [75,25], [75,75]],
|
||||
//calibration_points: [[50,50], [25,25], [25,75], [75,25], [75,75]],
|
||||
calibration_points: [[10,10],[10,30],[10,50],[10,70],[10,90],[30,10],[30,30],[30,50],[30,70],[30,90],[50,10],[50,30],[50,50],[50,70],[50,90],[70,10],[70,30],[70,50],[70,70],[70,90],[90,10],[90,30],[90,50],[90,70],[90,90]],
|
||||
repetitions_per_point: 1,
|
||||
randomize_calibration_order: true,
|
||||
time_per_point: 1000,
|
||||
//calibration_mode: 'view',
|
||||
time_per_point: 500,
|
||||
time_to_saccade: 1000
|
||||
}
|
||||
|
||||
var validation = {
|
||||
type: 'webgazer-validate',
|
||||
validation_points: [[-200,-200], [-200,200], [200,-200], [200,200]],
|
||||
validation_point_mode: 'center-offset-pixels'
|
||||
validation_point_coordinates: 'center-offset-pixels',
|
||||
show_validation_data: true
|
||||
}
|
||||
|
||||
var fixation = {
|
||||
|
@ -70,7 +70,7 @@ jsPsych.extensions['webgazer'] = (function () {
|
||||
|
||||
// send back the gazeData
|
||||
return {
|
||||
gazeData: JSON.stringify(state.currentTrialData)
|
||||
webgazer_gaze_data: state.currentTrialData
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,10 @@ jsPsych.plugins["webgazer-calibrate"] = (function() {
|
||||
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
|
||||
@ -64,10 +68,6 @@ jsPsych.plugins["webgazer-calibrate"] = (function() {
|
||||
"<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>"
|
||||
// "<p>Quality of detection:</p>"+
|
||||
// "<div id='video-detect-quality-container' style='width:700px; height: 20px; background-color:#ccc; position: relative;'>"+
|
||||
// "<div id='video-detect-quality-inner' style='width:0%; height:20px; background-color: #5c5;'></div>"+
|
||||
// "<div id='video-detect-threshold' style='width: 1px; height: 20px; background-color: #f00; position: absolute; top:0; left:"+(trial.face_detect_threshold*100)+"%;'></div>"+
|
||||
"</div>"+
|
||||
"</div>"
|
||||
|
||||
@ -81,20 +81,7 @@ jsPsych.plugins["webgazer-calibrate"] = (function() {
|
||||
document.querySelector('#jspsych-wg-cont').addEventListener('click', function(){
|
||||
observer.disconnect();
|
||||
show_begin_calibrate_message();
|
||||
})
|
||||
|
||||
// function waitForFace(){
|
||||
// var score = check_face_score();
|
||||
// //wg_container.querySelector('#video-detect-quality-inner').style.width = (score*100) + "%"
|
||||
// if(score){
|
||||
// document.querySelector('#jspsych-wg-cont').disabled = false;
|
||||
// } else {
|
||||
// requestAnimationFrame(waitForFace);
|
||||
// }
|
||||
// }
|
||||
// requestAnimationFrame(waitForFace);
|
||||
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
function face_detect_event_observer(mutationsList, observer){
|
||||
@ -110,12 +97,22 @@ jsPsych.plugins["webgazer-calibrate"] = (function() {
|
||||
|
||||
function show_begin_calibrate_message(){
|
||||
jsPsych.extensions['webgazer'].hideVideo();
|
||||
wg_container.innerHTML = "<div style='position: absolute; top: 50%; left: calc(50% - 350px); transform: translateY(-50%); width:700px;'>"+
|
||||
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 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>"+
|
||||
"<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();
|
||||
});
|
||||
@ -127,6 +124,9 @@ jsPsych.plugins["webgazer-calibrate"] = (function() {
|
||||
|
||||
function calibrate(){
|
||||
jsPsych.extensions['webgazer'].resume();
|
||||
if(trial.calibration_mode == 'click'){
|
||||
jsPsych.extensions['webgazer'].startMouseCalibration();
|
||||
}
|
||||
next_calibration_round();
|
||||
}
|
||||
|
||||
@ -160,36 +160,40 @@ jsPsych.plugins["webgazer-calibrate"] = (function() {
|
||||
wg_container.innerHTML = pt_html;
|
||||
|
||||
var pt_dom = wg_container.querySelector('#calibration-point');
|
||||
|
||||
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 {
|
||||
if(trial.calibration_mode == 'click'){
|
||||
pt_dom.style.cursor = 'pointer';
|
||||
pt_dom.addEventListener('click', function(){
|
||||
next_calibration_point();
|
||||
}
|
||||
})
|
||||
|
||||
// jsPsych.pluginAPI.setTimeout(function(){
|
||||
// pt_dom.style.backgroundColor = "#fff";
|
||||
// pt_dom.addEventListener('click', function(){
|
||||
// next_calibration_point();
|
||||
// });
|
||||
// }, Math.random()*(trial.maximum_dot_change_delay-trial.minimum_dot_change_delay)+trial.minimum_dot_change_delay);
|
||||
})
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ jsPsych.plugins["webgazer-validate"] = (function() {
|
||||
type: jsPsych.plugins.parameterType.INT,
|
||||
default: [[10,10], [10,50], [10,90], [50,10], [50,50], [50,90], [90,10], [90,50], [90,90]]
|
||||
},
|
||||
validation_point_mode: {
|
||||
validation_point_coordinates: {
|
||||
type: jsPsych.plugins.parameterType.STRING,
|
||||
default: 'percent' // options: 'percent', 'center-offset-pixels'
|
||||
},
|
||||
@ -34,21 +34,20 @@ jsPsych.plugins["webgazer-validate"] = (function() {
|
||||
point_size:{
|
||||
type: jsPsych.plugins.parameterType.INT,
|
||||
default: 10
|
||||
},
|
||||
show_validation_data: {
|
||||
type: jsPsych.plugins.parameterType.BOOL,
|
||||
default: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 trial_data = {}
|
||||
trial_data.raw_gaze = [];
|
||||
trial_data.percent_in_roi = [];
|
||||
trial_data.average_offset = [];
|
||||
|
||||
var html = `
|
||||
<div id='webgazer-validate-container' style='position: relative; width:100vw; height:100vh; overflow: hidden;'>
|
||||
@ -125,22 +124,15 @@ jsPsych.plugins["webgazer-validate"] = (function() {
|
||||
trial_data.raw_gaze.push(pt_data);
|
||||
next_validation_point();
|
||||
}
|
||||
})
|
||||
|
||||
// jsPsych.pluginAPI.setTimeout(function(){
|
||||
// pt_dom.style.backgroundColor = "#fff";
|
||||
// pt_dom.addEventListener('click', function(){
|
||||
// next_calibration_point();
|
||||
// });
|
||||
// }, Math.random()*(trial.maximum_dot_change_delay-trial.minimum_dot_change_delay)+trial.minimum_dot_change_delay);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function drawValidationPoint(x,y){
|
||||
if(trial.validation_point_mode == 'percent'){
|
||||
if(trial.validation_point_coordinates == 'percent'){
|
||||
return drawValidationPoint_PercentMode(x,y);
|
||||
}
|
||||
if(trial.validation_point_mode == 'center-offset-pixels'){
|
||||
if(trial.validation_point_coordinates == 'center-offset-pixels'){
|
||||
return drawValidationPoint_CenterOffsetMode(x,y);
|
||||
}
|
||||
}
|
||||
@ -154,43 +146,45 @@ jsPsych.plugins["webgazer-validate"] = (function() {
|
||||
}
|
||||
|
||||
function drawCircle(target_x, target_y, dx, dy, r){
|
||||
if(trial.validation_point_mode == 'percent'){
|
||||
if(trial.validation_point_coordinates == 'percent'){
|
||||
return drawCircle_PercentMode(target_x, target_y, dx, dy, r);
|
||||
}
|
||||
if(trial.validation_point_mode == 'center-offset-pixels'){
|
||||
if(trial.validation_point_coordinates == 'center-offset-pixels'){
|
||||
return drawCircle_CenterOffsetMode(target_x, target_y, dx, dy, r);
|
||||
}
|
||||
}
|
||||
|
||||
function drawCircle_PercentMode(target_x, target_y, dx, dy, r){
|
||||
var html = `
|
||||
<div class="validation-centroid" style="width:${r*2}px; height:${r*2}px; border: 2px solid red; border-radius: ${r}px; background-color: transparent; position: absolute; left:calc(${target_x}% + ${dx-r}px); top:calc(${target_y}% + ${dy-r}px);"></div>
|
||||
<div class="validation-centroid" style="width:${r*2}px; height:${r*2}px; border: 2px dotted #ccc; border-radius: ${r}px; background-color: transparent; position: absolute; left:calc(${target_x}% + ${dx-r}px); top:calc(${target_y}% + ${dy-r}px);"></div>
|
||||
`
|
||||
return html;
|
||||
}
|
||||
|
||||
function drawCircle_CenterOffsetMode(target_x, target_y, dx, dy, r){
|
||||
var html = `
|
||||
<div class="validation-centroid" style="width:${r*2}px; height:${r*2}px; border: 2px solid red; border-radius: ${r}px; background-color: transparent; position: absolute; left:calc(50% + ${target_x}px + ${dx-r}px); top:calc(50% + ${target_y}px + ${dy-r}px);"></div>
|
||||
<div class="validation-centroid" style="width:${r*2}px; height:${r*2}px; border: 2px dotted #ccc; border-radius: ${r}px; background-color: transparent; position: absolute; left:calc(50% + ${target_x}px + ${dx-r}px); top:calc(50% + ${target_y}px + ${dy-r}px);"></div>
|
||||
`
|
||||
return html;
|
||||
}
|
||||
|
||||
function drawRawDataPoint(target_x, target_y, dx, dy, ){
|
||||
if(trial.validation_point_mode == 'percent'){
|
||||
if(trial.validation_point_coordinates == 'percent'){
|
||||
return drawRawDataPoint_PercentMode(target_x, target_y, dx, dy);
|
||||
}
|
||||
if(trial.validation_point_mode == 'center-offset-pixels'){
|
||||
if(trial.validation_point_coordinates == 'center-offset-pixels'){
|
||||
return drawRawDataPoint_CenterOffsetMode(target_x, target_y, dx, dy);
|
||||
}
|
||||
}
|
||||
|
||||
function drawRawDataPoint_PercentMode(target_x, target_y, dx, dy){
|
||||
return `<div class="raw-data-point" style="width:5px; height:5px; border-radius:5px; border: 1px solid #f00; background-color: #faa; opacity:0.2; position: absolute; left:calc(${target_x}% + ${dx-2}px); top:calc(${target_y}% + ${dy-2}px);"></div>`
|
||||
var color = Math.sqrt(dx*dx + dy*dy) <= trial.roi_radius ? '#afa' : '#faa';
|
||||
return `<div class="raw-data-point" style="width:5px; height:5px; border-radius:5px; background-color: ${color}; opacity:0.8; position: absolute; left:calc(${target_x}% + ${dx-2}px); top:calc(${target_y}% + ${dy-2}px);"></div>`
|
||||
}
|
||||
|
||||
function drawRawDataPoint_CenterOffsetMode(target_x, target_y, dx, dy){
|
||||
return `<div class="raw-data-point" style="width:5px; height:5px; border-radius:5px; border: 1px solid #f00; background-color: #faa; opacity:0.2; position: absolute; left:calc(50% + ${target_x}px + ${dx-2}px); top:calc(50% + ${target_y}px + ${dy-2}px);"></div>`
|
||||
var color = Math.sqrt(dx*dx + dy*dy) <= trial.roi_radius ? '#afa' : '#faa';
|
||||
return `<div class="raw-data-point" style="width:5px; height:5px; border-radius:5px; background-color: ${color}; opacity:0.8; position: absolute; left:calc(50% + ${target_x}px + ${dx-2}px); top:calc(50% + ${target_y}px + ${dy-2}px);"></div>`
|
||||
}
|
||||
|
||||
function median(arr){
|
||||
@ -247,23 +241,31 @@ jsPsych.plugins["webgazer-validate"] = (function() {
|
||||
}
|
||||
|
||||
function validation_done(){
|
||||
for(var i=0; i<trial.validation_points.length; i++){
|
||||
trial_data.percent_in_roi[i] = calculatePercentInROI(trial_data.raw_gaze[i]);
|
||||
trial_data.average_offset[i] = calculateGazeCentroid(trial_data.raw_gaze[i]);
|
||||
}
|
||||
if(trial.show_validation_data){
|
||||
show_validation_data();
|
||||
} else {
|
||||
end_trial();
|
||||
}
|
||||
}
|
||||
|
||||
function 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]);
|
||||
var percent_in_roi = calculatePercentInROI(trial_data.raw_gaze[i]);
|
||||
trial_data.percent_in_roi[i] = percent_in_roi;
|
||||
var gc = calculateGazeCentroid(trial_data.raw_gaze[i]);
|
||||
html += drawCircle(trial.validation_points[i][0], trial.validation_points[i][1], gc.x, gc.y, gc.r);
|
||||
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)
|
||||
}
|
||||
}
|
||||
// debugging
|
||||
html += '<button id="cont" class="jspsych-btn">Continue</btn>';
|
||||
html += '<button id="cont" style="position:absolute; top: 50%; left:calc(50% - 50px); width: 100px;" class="jspsych-btn">Continue</btn>';
|
||||
wg_container.innerHTML = html;
|
||||
wg_container.querySelector('#cont').addEventListener('click', end_trial);
|
||||
jsPsych.extensions.webgazer.showPredictions();
|
||||
console.log(trial_data.percent_in_roi);
|
||||
}
|
||||
|
||||
// function to end trial when it is time
|
||||
|
Loading…
Reference in New Issue
Block a user