tatool / tatool-web

open-source experiment software for researchers
http://www.tatool-web.com
GNU General Public License v3.0
46 stars 26 forks source link

Creating a timer for just the first iteration #125

Closed taylorlmyers closed 5 years ago

taylorlmyers commented 5 years ago

We want to advance to the next executable after an amount of time, and not a number of iterations. We have created a timer that runs and then uses executableUtils.stopIteration() to move on to the next executable. Our problem now is that we only want to start the timer on the first iteration of each executable. It seems that the iteration number for each individual executable is located in the back end of Tatool rather than something within the control or service files. trialNr is not what we are looking for, as this does not start over at the beginning of each executable. Please help!

alocher commented 5 years ago

If you are looking to identify the first execution of an executable per session you could just create a variable in your Executable init() method. By setting it in your init method it will only be executed once per session. So in your first execution you can check the flag, start the timer and set the flag to 0 afterwards. This way your next execution won't start the timer again. Would this work for you?

this.firstIteration = 1;

taylorlmyers commented 5 years ago

Thanks for the idea; we tried to implement it and are still running into some problems. I've included some of the code to see if you could help us identify the problem.

We tried to define our variable in the init method:

  // set flag up in the init() method
           this.firstIteration = 1;
        alert ('set at ' + this.firstIteration);

    // prepare stimuli
  if (this.stimuliFile) {
    var self = this;
    executableUtils.getCSVResource(this.stimuliFile, true, this.stimuliPath).then(function(list) {
        self.processStimuliFile(list, deferred);
      }, function(error) {
        deferred.reject('Resource not found: ' + self.stimuliFile.resourceName);
      });
  } else {
    deferred.reject('Invalid property settings for Executable tatoolStroopReaction. Expected property 
   <b>stimuliFile</b> of type Resource.');
  }

  return deferred;
};

Then we define the function that would end the Executable.

function trialUp() {
    alert('trialUp');
     executableUtils.stopIteration();

   };

// Create stimulus and set properties
ChoiceReaction.prototype.createStimulus = function() {
  // reset executable properties
  this.startTime = 0;
  this.endTime = 0;

Then we try to check the flag, start the timer, and set the flag to 0.

    //alert(this.firstIteration);   
    if(this.firstIteration == 1){
            this.timerOverallTrial=timerUtils.createTimer(5000, true, this);
        this.timerOverallTrial.start(trialUp); 
        alert('timer started'); 
        this.firstIteration=0;
        };

However, when we do this as written above, the trialUp function is never called. If we eliminate the if statement, the trialUp function is called after 5 seconds, but the timer will start every execution of the Executable. If we write it as this.timerOverallTrial.start(trialUp()), the function is called immediately rather than after the 5 second timer has run. None of these options allows us to start the timer only once AND have trialUp be called once the timer is done.

Any help would be much appreciated!

alocher commented 5 years ago

It's a bit hard to follow the code above, as I'm not entirely sure what is in your service and what is in your controller. But I assume the last bit is part of your controller. If this is the case, you wouldn't be able to refer to this.firstIteration, as you defined it in your service. So you would refer to it using service.firstIteration instead.

If this doesn't solve the problem, would you mind sharing your full service/controller files.

taylorlmyers commented 5 years ago

It's all in the service.

 'use strict';

 tatool
   .factory('tatoolStroopReaction', [ 'executableUtils', 'dbUtils', 'timerUtils', 'stimulusServiceFactory', 
 'inputServiceFactory',
     function (executableUtils, dbUtils, timerUtils, stimulusServiceFactory, inputServiceFactory) {

var ChoiceReaction = executableUtils.createExecutable();

var DISPLAY_DURATION_DEFAULT = 100000;

var trialNr = dbUtils.getTrialNr()

//  Initialze variables at the start of every session
ChoiceReaction.prototype.init = function() {
  var deferred = executableUtils.createPromise();

  if (!this.showKeys) {
    this.showKeys = { propertyValue: true };
  } else {
    this.showKeys.propertyValue = (this.showKeys.propertyValue === true) ? true : false;
  }

  if (!this.timerEnabled) {
    this.timerEnabled = { propertyValue: false };
  } else {
    this.timerEnabled.propertyValue = (this.timerEnabled.propertyValue === true) ? true : false;
  }

  if (!this.stimuliPath) {
    deferred.reject('Invalid property settings for Executable tatoolStroopReaction. Expected property <b>stimuliPath</b> of type Path.');
  }

  // template properties
  this.stimulusService = stimulusServiceFactory.createService(this.stimuliPath);
  this.inputService = inputServiceFactory.createService(this.stimuliPath);

  // timing properties
  this.displayDuration = (this.displayDuration ) ? this.displayDuration : DISPLAY_DURATION_DEFAULT;
  this.timer = timerUtils.createTimer(this.displayDuration, true, this); 

 //alert(this.displayDuration);

  // trial counter property
  this.counter = 0;

  this.firstIteration = 1;
alert ('set at ' + this.firstIteration);
 // this.timerOverallTrial=timerUtils.createTimer(5000, true, this);

  // prepare stimuli
  if (this.stimuliFile) {
    var self = this;
    executableUtils.getCSVResource(this.stimuliFile, true, this.stimuliPath).then(function(list) {
        self.processStimuliFile(list, deferred);
      }, function(error) {
        deferred.reject('Resource not found: ' + self.stimuliFile.resourceName);
      });
  } else {
    deferred.reject('Invalid property settings for Executable tatoolStroopReaction. Expected property <b>stimuliFile</b> of type Resource.');
  }

  return deferred;
};

      // End iteration when timer ends

function trialUp() {
    alert('trialUp');
     executableUtils.stopIteration();
 };

// process stimuli file according to randomisation property
ChoiceReaction.prototype.processStimuliFile = function(list, deferred) {

    //alert('processStimuliFile');
  if (this.randomisation === 'full-condition') {
    this.stimuliList = this.splitStimuliList(list);
  } else if (this.randomisation === 'full') {
    this.stimuliList = executableUtils.shuffle(list);
  } else {
    this.stimuliList = list;
  }

  this.totalStimuli = list.length;
  this.setupInputKeys(list);
  deferred.resolve();
};

// Splitting the stimuliList according to stimulusType for full-condition and randomise
ChoiceReaction.prototype.splitStimuliList = function(list) {
    //alert('splitStimuliList');

  var newList = {};
  for (var i = 0; i < list.length; i++) {
    var stimulusType = list[i].stimulusType; 
    if(!newList[stimulusType]) {
      newList[stimulusType] = [];
    }
    newList[stimulusType].push(list[i]);
  }

  return newList;
};

// Adding keyInputs and show by default
ChoiceReaction.prototype.setupInputKeys = function(list) {
  var keys = this.inputService.addInputKeys(list, !this.showKeys.propertyValue);
 //alert('setupInputKeys');
  if (keys.length === 0) {
    executableUtils.fail('Error creating input template for Executable tatoolStroopReaction. No keyCode provided in stimuliFile.');
  }
};

// Create stimulus and set properties
ChoiceReaction.prototype.createStimulus = function() {
  // reset executable properties
  this.startTime = 0;
  this.endTime = 0;

    //alert('prototype.createStimulus');         
    //alert('trial = ' + trialNr);
    //Set timer only once?
    //alert(this.firstIteration);   
    if(this.firstIteration == 1){
            this.timerOverallTrial=timerUtils.createTimer(5000, true, this);
        this.timerOverallTrial.start(trialUp); 
    alert('timer started'); 
        this.firstIteration=0;
        };

    //if(this.timerOverallTrial=0){
            //this.changeFlag; alert('iteration now at '+this.firstIteration)
        //}

    //alert('timer created');
    //alert('timer created');

  // reset counter to 0 if > no. of total stimuli
  if (this.counter >= this.totalStimuli) {
    this.counter = 0;
    if (this.randomisation === 'full') {
      this.stimuliList = executableUtils.shuffle(this.stimuliList);
    }
  }

  // create new trial
  this.trial = {};
  this.trial.givenResponse = null;
  this.trial.reactionTime = 0;
  this.trial.score = null;

  // pick stimulus to display
  var stimulus = null;
  if (this.randomisation === 'full-condition') {
    stimulus = this.createRandomConditionStimulus();
  } else if (this.randomisation === 'full') {
    stimulus = this.createRandomStimulus();
  } else {
    stimulus = this.createNonRandomStimulus();
  }

  if (stimulus === null) {
    executableUtils.fail('Error creating stimulus in Executable tatoolStroopReaction. No more stimuli available in current stimuliList.');
  } else {
    this.trial.stimulusValue = stimulus.stimulusValue;
    this.trial.stimulusType = stimulus.stimulusType;
    this.trial.correctResponse = stimulus.correctResponse;
    this.stimulusService.set(stimulus);
  }

  // increment trial index counter
  this.counter++;
};

ChoiceReaction.prototype.createRandomConditionStimulus = function() {
  // get random stimuliType with replacement
  var stimuliType = executableUtils.getRandomReplace(this.stimuliList);

  // get random stimulus out of selected stimuliType
  var  randomStimulus = executableUtils.getRandomReplace(stimuliType);
  return randomStimulus;
};

ChoiceReaction.prototype.createRandomStimulus = function() {
  // get random stimulus out of selected stimuliType
  var  randomStimulus = executableUtils.getNext(this.stimuliList, this.counter);
  return randomStimulus;
};

ChoiceReaction.prototype.createNonRandomStimulus = function() {
  // get stimulus next replacement
  var nonRandomStimulus = executableUtils.getNext(this.stimuliList, this.counter);
  return nonRandomStimulus;
};

// Process given response and stop executable
ChoiceReaction.prototype.processResponse = function(givenResponse) {
  this.trial.reactionTime = this.endTime - this.startTime;
  this.trial.givenResponse = givenResponse;
  if (this.trial.correctResponse == this.trial.givenResponse) {
    this.trial.score = 1;
  } else {
    this.trial.score = 0;
  }
  dbUtils.saveTrial(this.trial).then(executableUtils.stop);
};

return ChoiceReaction;

   }]);

And, so you have it, here is the controller: 'use strict';

 tatool
   .controller('tatoolStroopReactionCtrl', [ '$scope', 'service',
     function ($scope, service) {

// Make the stimulus service available for the <tatool-stimulus> directive
$scope.stimulusService = service.stimulusService;
// Make the input service available for the <tatool-input> directive
$scope.inputService = service.inputService;

// Start execution
$scope.start = function() {
    //('createStimulus');
  service.createStimulus();

  service.inputService.enable();

  if (service.showKeys.propertyValue === true) {
    service.inputService.show();
  }
  if (service.timerEnabled.propertyValue === true) {
    service.timer.start(timerUp);
  }
  service.startTime = service.stimulusService.show();
};

// Called by timer when time elapsed without user input
function timerUp() {
    //alert('timerUp');
  service.inputService.disable();
  service.endTime = service.stimulusService.hide();
  service.processResponse('');
}

// Capture user input
$scope.inputAction = function(input, timing, event) {
  service.inputService.disable();
  service.timer.stop();
  service.inputService.hide();
  service.stimulusService.hide();
  service.endTime = timing;
  service.processResponse(input.givenResponse);
};

}]);

alocher commented 5 years ago

Alright I think there is still a bit of confusion around what should be happening. I copied your code into an example Executable and just ran it. After 5 seconds the alert box pops up and the call to your trialUp() function executes executableUtils.stopIteration() which essentially stops the entire iteration of the List Element.

Is it possible that you have two List Elements and you use the same Executable in both but you actually want to refer to the SAME instance of the executable in both? If that's the case you will need to give your 2nd Executable the same "Name" in the Editor to make sure it reuses the same instance of your Executable and doesn't create a new one.

Let me know if that's not the case and what your Module looks like (you can also paste the Module JSON if that's easier).

taylorlmyers commented 5 years ago

It's not that I want to refer to the same instance of the executable. I want the five-second timer to start on the first iteration, run during subsequent iterations, and call the trialUp() function while the subsequent iterations are running. The problem that we're having is that either the timer starts over on each iteration or, as it is now it's not calling trialUp at all.

taylorlmyers commented 5 years ago

Stroop_Module.zip

alocher commented 5 years ago

Ah I think I get what you're trying to do: you're starting a timer (created by timerUtils) in the first execution that is supposed to stop the iterator once it ends. Unfortunately this won't work as Tatool makes sure that any timer created in an Executable is canceled once you stop an Executable. This is to make sure people don't leave timers running which would lead to strange behavior most of the time.

I would suggest you keep track of the startTime with your first execution in the if part (this.firstIteration == 1))) and in your else case you check whether the difference between currentTime and your startTime >= your timer duration for all subsequent executions. If that's the case, you can stop the iteration.

taylorlmyers commented 5 years ago

I got it!!!! If you could please check your email, I will send over the files that need to be uploaded for me to use in tatool-web. Thanks so much.