For loop results in 5 times more output per second

Using 2020.2
OS: Windows10
PsychoPy standalone through Pavlovia

I want to identify when individuals are looking at a video on screen. Every second, the code will identify if the predicted eye gaze (tracking square) is within the video stimulus. This results in an increase in offtask or ontask counts. I would like to provide feedback based on % of ontask every 2 minutes. Until I started using the for loop, I had good results.

However, I can’t figure out why I am getting 5 entries every second now. What am I missing? The last version of code that was working properly had no loop and without resetting the ontask and offtask at the end of intervals is:

   window.webgazer.removeMouseEventListeners();
    ontask = 0;
    offtask = 0;
     if (psychoJS.experiment.experimentEnded ==false || psychoJS.eventManager.getKeys({keyList:['escape']}).length < 1)
    {
    setInterval(function()
        {
           if (movie.contains(tracking_square) == true) 
            {
                if (webgazer.checkEyesInValidationBox() === true) 
                 {
     //      console.log("Prediction from tracking_square" + util.to_norm(
     //      tracking_square, 'height', psychoJS.window))); 
       //add parameters later
                ++ontask;
                console.log('eyes detected' + " " + 'offtask:' + offtask + ",  ontask:" + ontask);
                psychoJS.experiment.addData('ontask', 1);
                psychoJS.experiment.addData('offtask', 0);
                psychoJS.experiment.nextEntry();
    // Get position of object for data sheet
    // getPositionFromObject(, _mouseXYs[0]);
                }
                else
                   {
                    ++offtask;
                    console.log('eyes NOT detected' + " " + 'offtask:' + offtask + ",  ontask:" + ontask);
                    psychoJS.experiment.addData('ontask', 0);
                    psychoJS.experiment.addData('offtask', 1);
                    psychoJS.experiment.nextEntry();
                    }
           }
           else 
           {
           ++offtask;
           console.log('offtask:' + offtask + ",  ontask:" + ontask);
           psychoJS.experiment.addData('ontask', 0);
           psychoJS.experiment.addData('offtask', 1);
           psychoJS.experiment.nextEntry();
           }
       }, 1000);
    }
    else 
    {psychoJS.experiment.experimentEnded ==false;
    console.log("experiment ended.");
    Scheduler.Event.NEXT;}

When I add in the for loop and try to reset the ontask and offtask variables, I get 5 entries of offtask or ontask each time there is an increase and no instances of “eyes not detected.”

var headtowardscreen = 0;
var ontask = 0; 
var offtask = 0;

//if (psychoJS.experiment.experimentEnded ==false || psychoJS.eventManager.getKeys({keyList:['escape']}).length < 1)
//  {

// Intervals last 20 seconds. Once six 20 second intervals have passed, end loop)    
for (var interval = 1; (ontask + offtask < 20) && (interval < 6); interval++)  
      {
//Check for offtask or ontask every second
            setInterval(function()
            {
               //While eyes looking within video stimulus (tracking square is within video stimulus) and eyes are identified, increase ontask by 1
               if (movie.contains(tracking_square) == true) 
                {                    
                    if (webgazer.checkEyesInValidationBox() === true) 
                     {
                    ++ontask;
                    console.log('eyes detected' + " " + 'offtask:' + offtask + ",  ontask:" + ontask);
                    psychoJS.experiment.addData('interval', interval);
                    psychoJS.experiment.addData('ontask', 1);
                    psychoJS.experiment.addData('offtask', 0);
                    psychoJS.experiment.addData('head toward screen', 1);
                    psychoJS.experiment.nextEntry();
                    }
                    //If eyes cannot be detected, ignore location of tracking square, and increase offtask by 1)
                    else
                    {
                    ++offtask;
                    console.log('eyes NOT detected' + " " + 'offtask:' + offtask + ",  ontask:" + ontask);
                    psychoJS.experiment.addData('interval', interval);
                    psychoJS.experiment.addData('ontask', 0);
                    psychoJS.experiment.addData('offtask', 1);
                    psychoJS.experiment.addData('head AWAY from screen', 1);
                    psychoJS.experiment.nextEntry();
                    }
               }
               //If eyes are detected but tracking square is not in video stimulus, increase offtask by 1)
               else 
                   {
                   ++offtask;
                   console.log('eyes detected' + ' ' + 'offtask:' + offtask + ",  ontask:" + ontask);
                   psychoJS.experiment.addData('interval', interval);
                   psychoJS.experiment.addData('ontask', 0);
                   psychoJS.experiment.addData('offtask', 1);
                   psychoJS.experiment.addData('head toward screen', 1);
                   psychoJS.experiment.nextEntry();
                   }
           }, 1000);
//Reset offtask and ontask counts to 0, and increase interval by 1 
       offtask = 0;
       ontask = 0;
      }
//else 
//  {
//    psychoJS.experiment.experimentEnded ==false;
//    console.log("experiment ended.");
//    Scheduler.Event.NEXT;
//  }

Any ideas? Any input would be greatly helpful.

Hey again!

Nice to see you’re doing some funky stuff with my demo :). About your questions; it has to do with loops and synchronous vs asynchronous processing.

This loop will (practically always) run 5 times…

for (var interval = 1; (ontask + offtask < 20) && (interval < 6); interval++) {
}

Inside of it you call setInterval, so that’s called 5 times. setInterval means “schedule calling a callback function every X milliseconds”. Since you call setInterval 5 times, the callback is also called 5 times. That’s why everything happens in fives.

What you could do instead, is move the script that runs the checks in to a code component that’s executed every frame. Every time, check whether 1 second passed since the last time you checked. It would look something like this:

// Get current time in ms (since 1970-01-01)
var currentTime = Date.now();
// If we never checked or last check was longer than 1000 ms ago, do something
if (window.lastTime === undefined || currentTime - window.lastTime >= 1000) {
  // *** Do your checks here ***
  // Time of last check is current time
  window.lastTime = currentTime;
}
1 Like

Hey! Thanks for the feedback. Yeah, I tried using just the webgazer sourcecode, but was having very wonky results. After I saw the .contains() method, it seemed like it would be perfectly usable with your demo code. I had been reading on asynch vs synch, but I’m still learning how to bring these concepts into my coding. It certainly makes sense. I’d have tp have a similar code to what you’ve presented that checks last time the setInterval() method was called, which may be annoying since they aren’t being used by the same objects.

I originally had all this being doing in the each frame, but as you’d predicted, the data collection became a bit much pretty fast. I figured the loops would be an issue if they were being called in rapid succession like per frame, but I hadn’t thought of using a check like that within the each frame routine. I’m going to mess with it and see what I get.

Much appreciated on all your help!

Aight. Have fun! :slight_smile:

1 Like