Scheduler.Event.FLIP REPEAT causes Unspecified Javascript Error somtimes

URL of experiment: training_paradigm_s1 [PsychoPy]

Description of the problem:
Hello All,

Any help would be greatly appreciated. I am in the process of trying to get my experiment online. Whilst checking if blocks run properly, I noticed that trials on a certain routine sporadically cause an “Unspecified Javascript Error.” After lots of console.log’ging, I narrowed the problem down to a Scheduler.Event.FLIP_REPEAT call on the first flip of the eachFrame function for that routine on the trial that causes the crash (which appears to be random). Why that occasionally causes a problem, I am at a loss. I have the experiment set up so that if you mash spacebar it will take you through the instructions to the correct block, if you go through enough trials (press ‘1’, ‘2’, or ‘3’ randomly), you will eventually get the error too. I am sure that I am at fault here. The experiment is a builder port from a project with a lot of hacky python code components. Please excuse any strange commit messages or comments in the code, it has been a frustrating few days. Please let me know if I am missing any information. Thank you for your time!

Do you have a flip command in each frame?

Flip should not be used in Builder experiments. There is an automatic flip at the end of each frame.

Sorry, I should have been more clear. As you know, when the builder exports an experiment to JavaScript, there is an eachFrame function for each routine. For the routine that is causing the error (on the seemingly random trials where the error occurs), the error occurs immediately following the first flip call of the eachFrame function (i.e. after the end of the first pass of the eachFrame function). This leads me to suspect that something is happening during that function sometimes which causes the screen to crash when it is flipped. I am out of ideas as to what though.

In that case, please could you show the contents of your Each Frame code (Python + JS) where it’s crashing?

I certainly can.

Here is the python code:

 # -------Run Routine "criterion_trial"-------
        while continueRoutine:
            # get current time
            t = criterion_trialClock.getTime()
            tThisFlip = win.getFutureFlipTime(clock=criterion_trialClock)
            tThisFlipGlobal = win.getFutureFlipTime(clock=None)
            frameN = frameN + 1  # number of completed frames (so 0 is the first frame)
            # update/draw components on each frame
            
            # *image_1_display* updates
            if image_1_display.status == NOT_STARTED and tThisFlip >= 0.0-frameTolerance:
                # keep track of start time/frame for later
                image_1_display.frameNStart = frameN  # exact frame index
                image_1_display.tStart = t  # local t and not account for scr refresh
                image_1_display.tStartRefresh = tThisFlipGlobal  # on global time
                win.timeOnFlip(image_1_display, 'tStartRefresh')  # time at next scr refresh
                image_1_display.setAutoDraw(True)
            
            # *label_1* updates
            if label_1.status == NOT_STARTED and tThisFlip >= 0.0-frameTolerance:
                # keep track of start time/frame for later
                label_1.frameNStart = frameN  # exact frame index
                label_1.tStart = t  # local t and not account for scr refresh
                label_1.tStartRefresh = tThisFlipGlobal  # on global time
                win.timeOnFlip(label_1, 'tStartRefresh')  # time at next scr refresh
                label_1.setAutoDraw(True)
            
            # *image_2_display* updates
            if image_2_display.status == NOT_STARTED and tThisFlip >= 0.0-frameTolerance:
                # keep track of start time/frame for later
                image_2_display.frameNStart = frameN  # exact frame index
                image_2_display.tStart = t  # local t and not account for scr refresh
                image_2_display.tStartRefresh = tThisFlipGlobal  # on global time
                win.timeOnFlip(image_2_display, 'tStartRefresh')  # time at next scr refresh
                image_2_display.setAutoDraw(True)
            
            # *label_2* updates
            if label_2.status == NOT_STARTED and tThisFlip >= 0.0-frameTolerance:
                # keep track of start time/frame for later
                label_2.frameNStart = frameN  # exact frame index
                label_2.tStart = t  # local t and not account for scr refresh
                label_2.tStartRefresh = tThisFlipGlobal  # on global time
                win.timeOnFlip(label_2, 'tStartRefresh')  # time at next scr refresh
                label_2.setAutoDraw(True)
            
            # *image_3_display* updates
            if image_3_display.status == NOT_STARTED and tThisFlip >= 0.0-frameTolerance:
                # keep track of start time/frame for later
                image_3_display.frameNStart = frameN  # exact frame index
                image_3_display.tStart = t  # local t and not account for scr refresh
                image_3_display.tStartRefresh = tThisFlipGlobal  # on global time
                win.timeOnFlip(image_3_display, 'tStartRefresh')  # time at next scr refresh
                image_3_display.setAutoDraw(True)
            
            # *label_3* updates
            if label_3.status == NOT_STARTED and tThisFlip >= 0.0-frameTolerance:
                # keep track of start time/frame for later
                label_3.frameNStart = frameN  # exact frame index
                label_3.tStart = t  # local t and not account for scr refresh
                label_3.tStartRefresh = tThisFlipGlobal  # on global time
                win.timeOnFlip(label_3, 'tStartRefresh')  # time at next scr refresh
                label_3.setAutoDraw(True)
            
            # *features_text* updates
            if features_text.status == NOT_STARTED and tThisFlip >= 0.0-frameTolerance:
                # keep track of start time/frame for later
                features_text.frameNStart = frameN  # exact frame index
                features_text.tStart = t  # local t and not account for scr refresh
                features_text.tStartRefresh = tThisFlipGlobal  # on global time
                win.timeOnFlip(features_text, 'tStartRefresh')  # time at next scr refresh
                features_text.setAutoDraw(True)
            
            # *crit_key_resp* updates
            waitOnFlip = False
            if crit_key_resp.status == NOT_STARTED and tThisFlip >= 0.0-frameTolerance:
                # keep track of start time/frame for later
                crit_key_resp.frameNStart = frameN  # exact frame index
                crit_key_resp.tStart = t  # local t and not account for scr refresh
                crit_key_resp.tStartRefresh = tThisFlipGlobal  # on global time
                win.timeOnFlip(crit_key_resp, 'tStartRefresh')  # time at next scr refresh
                crit_key_resp.status = STARTED
                # keyboard checking is just starting
                waitOnFlip = True
                win.callOnFlip(crit_key_resp.clock.reset)  # t=0 on next screen flip
                win.callOnFlip(crit_key_resp.clearEvents, eventType='keyboard')  # clear events on next screen flip
            if crit_key_resp.status == STARTED and not waitOnFlip:
                theseKeys = crit_key_resp.getKeys(keyList=['1', '2', '3'], waitRelease=False)
                _crit_key_resp_allKeys.extend(theseKeys)
                if len(_crit_key_resp_allKeys):
                    crit_key_resp.keys = _crit_key_resp_allKeys[-1].name  # just the last key pressed
                    crit_key_resp.rt = _crit_key_resp_allKeys[-1].rt
                    # was this correct?
                    if (crit_key_resp.keys == str(new_corr_ans)) or (crit_key_resp.keys == new_corr_ans):
                        crit_key_resp.corr = 1
                    else:
                        crit_key_resp.corr = 0
                    # a response ends the routine
                    continueRoutine = False
            
            # check if all components have finished
            if not continueRoutine:  # a component has requested a forced-end of Routine
                break
            continueRoutine = False  # will revert to True if at least one component still running
            for thisComponent in criterion_trialComponents:
                if hasattr(thisComponent, "status") and thisComponent.status != FINISHED:
                    continueRoutine = True
                    break  # at least one component has not yet finished
            
            # refresh the screen
            if continueRoutine:  # don't flip if this routine is over or we'll get a blank screen
                win.flip()

And here is the corresponding JavaScript code:

function criterion_trialRoutineEachFrame(snapshot) {
  return function () {
    console.log("begin criterion_trial Each frame")
    //------Loop for each frame of Routine 'criterion_trial'-------
    let continueRoutine = true; //until were told otherwise
    // skip routine if we have reached the required criterion
    if (criterion_reached == true) {
        console.log("criterion reached, skipping trial")
        return Scheduler.Event.NEXT;
    }
    // get current time
    t = criterion_trialClock.getTime();
    frameN = frameN + 1;// number of completed frames (so 0 is the first frame)
    // update/draw components on each frame
    // *image_1_display* updates
    if (t >= 0.5 && image_1_display.status === PsychoJS.Status.NOT_STARTED) {
      console.log("initialising image 1")
      // keep track of start time/frame for later
      image_1_display.tStart = t;  // (not accounting for frame time here)
      image_1_display.frameNStart = frameN;  // exact frame index
      console.log(image_2_display.image);
      image_1_display.setAutoDraw(true);
      console.log("done")
    }

    
    // *label_1* updates
    if (t >= 0.0 && label_1.status === PsychoJS.Status.NOT_STARTED) {
      // keep track of start time/frame for later
      label_1.tStart = t;  // (not accounting for frame time here)
      label_1.frameNStart = frameN;  // exact frame index
      
      label_1.setAutoDraw(true);
    }

    
    // *image_2_display* updates
    if (t >= 0.5 && image_2_display.status === PsychoJS.Status.NOT_STARTED) {
      console.log("initialising image 2")
      // keep track of start time/frame for later
      image_2_display.tStart = t;  // (not accounting for frame time here)
      image_2_display.frameNStart = frameN;  // exact frame index
      console.log(image_2_display.image);
      image_2_display.setAutoDraw(true);
      console.log("done")
    }

    
    // *label_2* updates
    if (t >= 0.0 && label_2.status === PsychoJS.Status.NOT_STARTED) {
      // keep track of start time/frame for later
      label_2.tStart = t;  // (not accounting for frame time here)
      label_2.frameNStart = frameN;  // exact frame index
      
      label_2.setAutoDraw(true);
    }

    
    // *image_3_display* updates
    if (t >= 0.5 && image_3_display.status === PsychoJS.Status.NOT_STARTED) {
      console.log("initialising image 3")
      // keep track of start time/frame for later
      image_3_display.tStart = t;  // (not accounting for frame time here)
      image_3_display.frameNStart = frameN;  // exact frame index
      console.log(image_2_display.image)
      image_3_display.setAutoDraw(true);
      console.log("done")
    }

    
    // *label_3* updates
    if (t >= 0.0 && label_3.status === PsychoJS.Status.NOT_STARTED) {
      // keep track of start time/frame for later
      label_3.tStart = t;  // (not accounting for frame time here)
      label_3.frameNStart = frameN;  // exact frame index
      
      label_3.setAutoDraw(true);
    }

    
    // *features_text* updates
    if (t >= 0.5 && features_text.status === PsychoJS.Status.NOT_STARTED) {
      console.log("intialising features text")
      // keep track of start time/frame for later
      features_text.tStart = t;  // (not accounting for frame time here)
      features_text.frameNStart = frameN;  // exact frame index
      
      features_text.setAutoDraw(true);
      console.log("done")
        
    }

    
    // *crit_key_resp* updates
    if (t >= 0.5 && crit_key_resp.status === PsychoJS.Status.NOT_STARTED) {
      console.log("initialising key_resp")
      // keep track of start time/frame for later
      crit_key_resp.tStart = t;  // (not accounting for frame time here)
      crit_key_resp.frameNStart = frameN;  // exact frame index
      
      // keyboard checking is just starting
      psychoJS.window.callOnFlip(function() { crit_key_resp.clock.reset(); });  // t=0 on next screen flip
      psychoJS.window.callOnFlip(function() { crit_key_resp.start(); }); // start on screen flip
      psychoJS.window.callOnFlip(function() { crit_key_resp.clearEvents(); });
      console.log("done")
        
    }
    console.log(crit_key_resp.status)
    if (crit_key_resp.status === PsychoJS.Status.STARTED) {
      console.log("begin tracking key_resp")
      let theseKeys = crit_key_resp.getKeys({keyList: ['1', '2', '3'], waitRelease: false});
      _crit_key_resp_allKeys = _crit_key_resp_allKeys.concat(theseKeys);
      if (_crit_key_resp_allKeys.length > 0) {
        crit_key_resp.keys = _crit_key_resp_allKeys[_crit_key_resp_allKeys.length - 1].name;  // just the last key pressed
        crit_key_resp.rt = _crit_key_resp_allKeys[_crit_key_resp_allKeys.length - 1].rt;
        // was this correct?
        if (crit_key_resp.keys == new_corr_ans) {
            crit_key_resp.corr = 1;
        } else {
            crit_key_resp.corr = 0;
        }
        // a response ends the routine
        continueRoutine = false;
      }
      console.log("end tracking key_resp")
    }
    
    console.log("checking if experiment should be escaped")
    // check for quit (typically the Esc key)
    if (psychoJS.experiment.experimentEnded || psychoJS.eventManager.getKeys({keyList:['escape']}).length > 0) {
      return quitPsychoJS('The [Escape] key was pressed. Goodbye!', false);
    }
    console.log("checking if routine should terminate should be escaped")
    // check if the Routine should terminate
    if (!continueRoutine) {  // a component has requested a forced-end of Routine
      return Scheduler.Event.NEXT;
    }
    console.log("check if the components are still running")
    continueRoutine = false;  // reverts to True if at least one component still running
    for (const thisComponent of criterion_trialComponents)
      if ('status' in thisComponent && thisComponent.status !== PsychoJS.Status.FINISHED) {
        continueRoutine = true;
        break;
      }
    console.log("refresh screen or not")
    // refresh the screen if continuing
    if (continueRoutine) {
      console.log("refresh screen")
      return Scheduler.Event.FLIP_REPEAT;
    } else {
      console.log("move to next routine")
      return Scheduler.Event.NEXT;
    }
  };
}

Note that my assumptions about the root of the problem are based on the fact that, in the console, I see the message “refresh screen” immediately prior to the error.

Are you using Builder?

If so, please could you show the contents of your Each Frame code component (Python + JS)? The code you’ve shown contains lots of code generated by PsychoPy and I’m wanting to see your custom code.

Ah yes, sorry.
I do not have any Each Frame code components for this Routine. There is one for Begin Routine.

Here is the Python Code:

images = [[1,image_1_display], [2, image_2_display], [3, image_3_display]]
np.random.shuffle(images)
for i in range(len(images)):
    images[i][1].pos = (image_x_locations[i], 0)
    if images[i][0] == corr_ans:
        new_corr_ans = i+1

criterion_features = [feature_1, feature_2, feature_3]
np.random.shuffle(criterion_features)
features_text.setText(f"Which is {criterion_features[0]}, {criterion_features[1]}, and {criterion_features[2]}")

NB: image_<1-3>_display are image components, and image_x_locations is an array of length: 3, populated with integers representing locations on the screen, feature_<1-3> are parameters from a conditions csv file.

Because the code translator cannot automatically translate this code to JS (it doesn’t seem to recognise numpy as Python only), I had to make a few changes. I used a custom function to shuffle the images array. Therefore, the JS equivalent (with the function I used) is:

// function to shuffle array
function shuffleArray(array) {
    for (var i = array.length - 1; i > 0; i--) {
        var j = Math.floor(Math.random() * (i + 1));
        var temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
}

// code snippet from beginRoutine
 // update component parameters for each repeat
    images = [[1, image_1_display], [2, image_2_display], [3, image_3_display]];
    shuffleArray(images);
    for (var i = 0, _pj_a = images.length; (i < _pj_a); i += 1) {
        images[i][1].pos = [image_x_locations[i], 0];
        if ((images[i][0] === corr_ans)) {
            new_corr_ans = (i + 1);
        }
    }
    criterion_features = [feature_1, feature_2, feature_3];
    shuffleArray(criterion_features);
    features_text.setText(`Which is ${criterion_features[0]}, ${criterion_features[1]}, and ${criterion_features[2]}`);

Thank you!

This way of concatenating strings doesn’t work in JS.

[quote=“JDVinson, post:7, topic:20895”]

features_text.setText(`Which is ${criterion_features[0]}, ${criterion_features[1]}, and ${criterion_features[2]}`);

This must work because not all trials lead to the error. Are there certain conditions under which this would not work?