Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TrialHandler attributes are not updated after creation #49

Closed
dvbridges opened this issue Apr 23, 2019 · 14 comments

Comments

Projects
None yet
3 participants
@dvbridges
Copy link
Contributor

commented Apr 23, 2019

TrialHandler attributes (e.g., below) need to update every block and every trial:

.thisRepN - which repeat you are currently on
.thisTrialN - which trial number within that repeat

In PsychoPy, this happens within the trialhandler class (see __next__ iterator method).

https://github.com/psychopy/psychopy/blob/2dfefd7506ea3f7b4854454b61210dcc7586928e/psychopy/data/trial.py#L316

@apitiot

This comment has been minimized.

Copy link
Contributor

commented Apr 26, 2019

That is rather surprising. Could you provide me with a sample experiment?

@dvbridges

This comment has been minimized.

Copy link
Contributor Author

commented Apr 26, 2019

Thanks for looking Alain, here is an example. The text components have the trialHandler attributes set to update on every repeat, or every trial.

https://pavlovia.org/run/dvbridges/trialattributes/html/

@robpetrosino

This comment has been minimized.

Copy link

commented Apr 30, 2019

Hi, would it be at all possible to have some update on the issue?

Thanks!

@apitiot

This comment has been minimized.

Copy link
Contributor

commented Apr 30, 2019

Hello Roberto and David,

What you are experiencing has to do with the fact that PsychoJs experiments are entirely scheduled before they start running. That means that the internal variables of a TrialHandler, such as nTotal, will not change at run time, they only change at scheduling time.
I did not originally intend them to be available during running but it certainly is possible to capture their values during the scheduling in a way to make them accessable at runtime. For the sake of convenience, I've just added the method TrialHandler.getSnapshot, which returns an object with various internal variables:

getSnapshot() {
	return {
		nStim: this.nStim,
		nTotal: this.nTotal,
		nRemaining: this.nRemaining,
		thisRepN: this.thisRepN,
		thisTrialN: this.thisTrialN,
		thisN: this.thisN,
		thisIndex: this.thisIndex
	};
}

It can be used as follows:

  • In trialsLoopBegin, we capture a snapshot and pass it to trialRoutineBegin
  // Schedule all the trials in the trialList:
  for (const thisTrial of trials) {
    thisScheduler.add(importConditions(trials));
    thisScheduler.add(trialRoutineBegin(trials.getSnapshot()));
    thisScheduler.add(trialRoutineEachFrame);
    thisScheduler.add(trialRoutineEnd);
    thisScheduler.add(endLoopIteration(thisScheduler, thisTrial));
  }
  • trialRoutineBegin now returns a function:
function trialRoutineBegin(trials) {
	return function () {
		[...]
		// update component parameters for each repeat
		trialNTotal.setText(('nTotal: ' + trials.nTotal));
		trialnRemaining.setText(('nRemaining: ' + trials.nRemaining));
		trialN.setText(('thisN: ' + trials.thisN));
		trialRepN.setText(('thisRepN: ' + trials.thisRepN));
		trialTrialN.setText(('thisTrialN: ' + trials.thisTrialN));
		[...]
		return Scheduler.Event.NEXT;
	}
}

This ensures that the repeated calls to trialRoutineBegin in the trialsLoopBegin function will all contain a different snapshot of trials.

I hope this helps! Let me know what you think.
Cheers,

Alain

@robpetrosino

This comment has been minimized.

Copy link

commented Apr 30, 2019

Hi Alain,
thank you for your message. I am no JS expert, so forgive my naive question. What I am trying to implement in my experiment is a break after a given number of trials has been presented. In particular, a trial consists of a number of routines as shown in the Flow figure below: the routines fmaskWarm, primeWarm and targetWarm are to be presented one after the other in the loop loopWarmand the routinebreakWarm` should be presented after, say, half of the fmask-prime-target triplets have been presented.

Screen Shot 2019-04-23 at 9 03 18 PM

If I understand correctly, you are suggesting to insert the line

thisScheduler.add(trialRoutineBegin(trials.getSnapshot()));

at the beginning of the loop (i.e., when importing the conditions for that loop). This way, I could refer to the trialHandler attributes in the breakWarm routine for my purposes.

Is that correct?

@robpetrosino

This comment has been minimized.

Copy link

commented May 1, 2019

UPDATE: I tried to follow your instructions, and the following message pops up:

Screen Shot 2019-05-01 at 8 28 21 AM

URL to the experiment: https://pavlovia.org/run/roberto.petrosino/maskedpriming/html/

@apitiot

This comment has been minimized.

Copy link
Contributor

commented May 2, 2019

Let me have a look. I should be able to get back to you later today or tomorrow at the latest.

@robpetrosino

This comment has been minimized.

Copy link

commented May 2, 2019

UPDATE. Now no error pops up, but the counter does not seem to update properly. Here's an excerpt of my code.

var loopWarm;
var currentLoop;
function loopWarmLoopBegin(thisScheduler) {
  // set up handler to look after randomisation of conditions etc
  loopWarm = new TrialHandler({
    psychoJS: psychoJS,
    nReps: 1, method: TrialHandler.Method.RANDOM,
    extraInfo: expInfo, originPath: undefined,
    trialList: 'warm_up.csv',
    seed: undefined, name: 'loopWarm'});
  psychoJS.experiment.addLoop(loopWarm); // add the loop to the experiment
  currentLoop = loopWarm;  // we're now the current loop

  // Schedule all the trials in the trialList:
  for (const thisLoopWarm of loopWarm) {
    thisScheduler.add(importConditions(loopWarm));
    thisScheduler.add(fmaskWarmRoutineBegin(loopWarm.getSnapshot()));
    //thisScheduler.add(fmaskWarmRoutineBegin);
    thisScheduler.add(fmaskWarmRoutineEachFrame);
    thisScheduler.add(fmaskWarmRoutineEnd);
    thisScheduler.add(primeWarmRoutineBegin);
    thisScheduler.add(primeWarmRoutineEachFrame);
    thisScheduler.add(primeWarmRoutineEnd);
    thisScheduler.add(targetWarmRoutineBegin);
    thisScheduler.add(targetWarmRoutineEachFrame);
    thisScheduler.add(targetWarmRoutineEnd);
    thisScheduler.add(breakWarmRoutineBegin);
    thisScheduler.add(breakWarmRoutineEachFrame);
    thisScheduler.add(breakWarmRoutineEnd);
    thisScheduler.add(endLoopIteration(thisScheduler, thisLoopWarm));
  }

  return Scheduler.Event.NEXT;
}

[...]

function breakWarmRoutineBegin() {
  //------Prepare to start Routine 'breakWarm'-------
  t = 0;
  breakWarmClock.reset(); // clock
  frameN = -1;
  // update component parameters for each repeat
  if ( loopWarm.thisTrialN !== (loopWarm.nTotal/2) ) {     
      continueRoutine === false; 
  } //take a break after first half of stimuli has been presented
  
  text_breakWarm.text = "You can now take a short break.\n\nThere are " + (loopWarm.nRemaining+1) + " words left. \nthisN="+loopWarm.thisN+"\nthisTrialN:"+loopWarm.thisTrialN+"\nnTotal:"+loopWarm.nTotal+"\n\nWhen you are ready, press 'SPACE' to resume the experiment.";
  key_breakWarm = new core.BuilderKeyResponse(psychoJS);
  
  // keep track of which components have finished
  breakWarmComponents = [];
  breakWarmComponents.push(key_breakWarm);
  breakWarmComponents.push(text_breakWarm);
  
  for (const thisComponent of breakWarmComponents)
    if ('status' in thisComponent)
      thisComponent.status = PsychoJS.Status.NOT_STARTED;
  
  return Scheduler.Event.NEXT;
}

Thank you so much for your help!

@apitiot

This comment has been minimized.

Copy link
Contributor

commented May 3, 2019

You were almost there. You have to alter breakWarmRoutineBegin so it take loopWarm as a parameter and returns a function, as per my example above.
I have made the changes to your experiment on the repository: you'll see them if you pull.
Cheers,

Alain

@robpetrosino

This comment has been minimized.

Copy link

commented May 3, 2019

Thank you, Alain! I can see that now the counter works, though the breakWarm routine still shows up at every trial instead of after half of the stimuli are presented. Is there any issue with the if-statement I wrote (ll. 763-4 of my code)?

@apitiot

This comment has been minimized.

Copy link
Contributor

commented May 3, 2019

My pleasure!
I'll leave you in the hands of David for the if-statement issue.
@dvbridges would you mind having a quick look?

Alain

@dvbridges

This comment has been minimized.

Copy link
Contributor Author

commented May 3, 2019

Thanks @apitiot , adding this to the JS output. @robpetrosino , the if-statement is not working because you are testing the continueRoutine variable for equality with false, rather than assigning the value of continueRoutine as false. Try:

if ( loopWarm.thisTrialN !== (loopWarm.nTotal/2) ) {     
      continueRoutine = false; 
  } //take a break after first half of stimuli has been presented
@robpetrosino

This comment has been minimized.

Copy link

commented May 3, 2019

@dvbridges, thank you for replying so quickly. I just tried that but the breakWarm routine still pops up at every trial. I am not sure if that is the right place to discuss this - I am sorry if that is not the case. We could continue the discussion on the forum.

@apitiot

This comment has been minimized.

Copy link
Contributor

commented May 3, 2019

Yes, if you don't mind, do continue independently from this particular thread.

@dvbridges I'll contact you next week to discuss changes to the code generation related to systematically getting snapshots and passing them to all routines.

@apitiot apitiot closed this May 3, 2019

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.