Plugin best practices for accurate reaction times

183 views
Skip to first unread message

pat...@g.harvard.edu

unread,
Jul 20, 2016, 4:43:49 PM7/20/16
to jsPsych
Hi all,

I'm building a sequential decision-making task (e.g. Daw 2-step) and have been constructing a plugin for the trials. I identify the CSS divs with an id and then keep replacing stimuli depending on the "step" the subject is in and the choices they've made. I'm concerned the task will not run smoothly because of inefficient coding practices. Reaction time is of main interest and we intend on running the experiment on MTurk. I've copied my code thus far below.

Thanks for the feedback, Edward

/*
* Example plugin template
*/

jsPsych.plugins["tree-stim"] = (function() {

var plugin = {};

jsPsych.pluginAPI.registerPreload('tree-stim', 'stimuli', 'image');

plugin.trial = function(display_element, trial) {

// set default values for parameters
// trial.parameter = trial.parameter || 'default value';

// allow variables as functions
// this allows any trial variable to be specified as a function
// that will be evaluated when the trial runs. this allows users
// to dynamically adjust the contents of a trial as a result
// of other trials, among other uses. you can leave this out,
// but in general it should be included
trial = jsPsych.pluginAPI.evaluateFunctionParameters(trial);

// if any trial variables are functions
// this evaluates the function and replaces
// it with the output of the function
// default trial values

trial.choices = trial.choices || ["F","J"];
trial.timing_response = trial.timing_response || -1; //
trial.response_ends_trial = (typeof trial.response_ends_trial == 'undefined') ? true : trial.response_ends_trial;
trial.is_html = (typeof trial.is_html === 'undefined') ? false : trial.is_html;


// this array holds handlers from setTimeout calls
// that need to be cleared if the trial ends early
var setTimeoutHandlers = [];

// path to first 2 images which are shuffled in the preload function at the start of the experiment
var face_image_1_path = trial.stimuli[0]
var face_image_2_path = trial.stimuli[1]
var tool_image_1_path = trial.stimuli[2]
var tool_image_2_path = trial.stimuli[3]
var tool_image_3_path = trial.stimuli[4]
var scene_image_1_path = trial.stimuli[5]
var scene_image_1_path = trial.stimuli[6]
var scene_image_1_path = trial.stimuli[7]
var scene_image_1_path = trial.stimuli[8]

// randomize the starting state
var starting_state = [face_image_1_path];
var starting_state_test = (Math.floor(Math.random() * 2) === 0); // 50% chance target is on left.
if (!starting_state_test) {
starting_state = [face_image_2_path];
}

// randomize stimuli sides
var randomize_stimuli_side_stage1 = (Math.floor(Math.random() * 2) === 0);
var randomize_stimuli_side_stage2 = (Math.floor(Math.random() * 2) === 0);


var state2 = -1;
var stage = 1;
var stage2_starting_state = -1;

// clear the display
display_element.html('');

// create layout and containers to place stimuli in
display_element.append($('<div>',{
id: 'jspsych-choice-text-top',
}));

display_element.append($('<div>',{
id: 'jspsych-tree-stim-top',
}));

display_element.append($('<div>', {
style: 'clear:both',
}));

display_element.append($('<div>',{
id: 'jspsych-tree-stim-bottom-left',
}));

display_element.append($('<div>',{
id: 'jspsych-tree-stim-bottom-right',
}));

display_element.append($('<div>',{
id: 'jspsych-choice-text-bottom',
}));

// create variable to hold subject responses
var response = {
rt: -1,
rt2: -1,
key: -1,
key2: -1,
stage1_left_stim: -1,
stage1_right_stim: -1
};

// function to start listening for subject response
var start_response_listener = function(){

if(JSON.stringify(trial.choices) != JSON.stringify(["none"])) {
var keyboardListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: trial.choices,
rt_method: 'date',
persist: false,
allow_held_key: false,
});
}

}


var display_stimuli = function(stage) {

if (stage == 1) {
$('#jspsych-choice-text-top').text('Current State')
$('#jspsych-tree-stim-top').css('background-image', 'url('+starting_state+')')

if (!starting_state_test){
if (!randomize_stimuli_side_stage1){
$('#jspsych-tree-stim-bottom-left').css('background-image', 'url('+tool_image_2_path+')')
$('#jspsych-tree-stim-bottom-right').css('background-image', 'url('+tool_image_3_path+')')
response.stage1_left_stim = tool_image_2_path
response.stage1_right_stim = tool_image_3_path
} else {
$('#jspsych-tree-stim-bottom-right').css('background-image', 'url('+tool_image_2_path+')')
$('#jspsych-tree-stim-bottom-left').css('background-image', 'url('+tool_image_3_path+')')
response.stage1_left_stim = tool_image_3_path
response.stage1_right_stim = tool_image_2_path
}
$('#jspsych-choice-text-bottom').text('Choose next state')
} else {
if (!randomize_stimuli_side_stage1){
$('#jspsych-tree-stim-bottom-left').css('background-image', 'url('+tool_image_1_path+')')
$('#jspsych-tree-stim-bottom-right').css('background-image', 'url('+tool_image_3_path+')')
response.stage1_left_stim = tool_image_1_path
response.stage1_right_stim = tool_image_3_path
} else {
$('#jspsych-tree-stim-bottom-right').css('background-image', 'url('+tool_image_1_path+')')
$('#jspsych-tree-stim-bottom-left').css('background-image', 'url('+tool_image_3_path+')')
response.stage1_left_stim = tool_image_3_path
response.stage1_right_stim = tool_image_1_path
}
$('#jspsych-choice-text-bottom').text('Choose next state')
}
}
if (stage == 2) {
$('#jspsych-choice-text-top').text('Current State')
$('#jspsych-tree-stim-top').css('background-image', 'url('+stage1_chosen_stimulus+')')

if (!starting_state_test){
if (!randomize_stimuli_side_stage1){
$('#jspsych-tree-stim-bottom-left').css('background-image', 'url('+tool_image_1_path+')')
$('#jspsych-tree-stim-bottom-right').css('background-image', 'url('+tool_image_2_path+')')
response.stage2_left_stim = scene_image_1_path
response.stage2_right_stim = scene_image_2_path
} else {
$('#jspsych-tree-stim-bottom-right').css('background-image', 'url('+tool_image_1_path+')')
$('#jspsych-tree-stim-bottom-left').css('background-image', 'url('+tool_image_2_path+')')
response.stage2_left_stim = tool_image_2_path
response.stage2_right_stim = tool_image_1_path
}
$('#jspsych-choice-text-bottom').text('Choose next state')
} else {
if (!randomize_stimuli_side_stage1){
$('#jspsych-tree-stim-bottom-left').css('background-image', 'url('+tool_image_3_path+')')
$('#jspsych-tree-stim-bottom-right').css('background-image', 'url('+tool_image_4_path+')')
response.stage2_left_stim = tool_image_3_path
response.stage2_right_stim = tool_image_4_path
} else {
$('#jspsych-tree-stim-bottom-right').css('background-image', 'url('+tool_image_3_path+')')
$('#jspsych-tree-stim-bottom-left').css('background-image', 'url('+tool_image_4_path+')')
response.stage2_left_stim = tool_image_4_path
response.stage2_right_stim = tool_image_3_path
}
$('#jspsych-choice-text-bottom').text('Choose next state')
}
}
}

// function to end trial when it is time
var end_trial = function() {

// kill any remaining setTimeout handlers
for (var i = 0; i < setTimeoutHandlers.length; i++) {
clearTimeout(setTimeoutHandlers[i]);
}

// kill keyboard listeners
if (typeof keyboardListener !== 'undefined') {
jsPsych.pluginAPI.cancelKeyboardResponse(keyboardListener);
}

// gather the data to store for the trial
var trial_data = {
"rt": response.rt,
"rt2": response.rt2,
"stimulus": trial.stimulus,
"stage1_key": response.key,
"stage2_key": response.key2,
"stage1_chosen_stimulus": stage1_chosen_stimulus,
"stage2_chosen_stimulus": stage2_chosen_stimulus
};

//jsPsych.data.write(trial_data);

// clear the display
display_element.html('');

// move on to the next trial
jsPsych.finishTrial(trial_data);
};

// function to handle responses by the subject
var after_response = function(info) {
// kill_listeners();
// kill_timers();

if (String.fromCharCode(info.key) == trial.choices[0]) { // left button press
$('#jspsych-tree-stim-bottom-left').css('border-style', 'rgba(1,0,0,1)');

stage1_chosen_stimulus = response.stage1_left_stim
console.log(stage1_chosen_stimulus)

} else { // right button press

$('#jspsych-tree-stim-bottom-right').css('border-color', 'rgba(1,0,0,1)');
stage1_chosen_stimulus = response.stage1_right_stim

}

// only record the first response
if (response.key == -1) {
response = info;
}

if (trial.response_ends_trial) {
end_trial();
}
};

start_response_listener();
display_stimuli(1);


};
return plugin;
})();

Josh de Leeuw

unread,
Jul 21, 2016, 12:06:53 PM7/21/16
to pat...@g.harvard.edu, jsPsych
I don't see any obvious issues.

From a quick look at the code, it seems like the preloading should work. I don't know of any data about whether using CSS properties or DOM modification is faster for rendering an image but I suspect there is no difference. In any event, the accuracy of the RT measurement is going to depend on the refresh rate of the monitor/page and it's unlikely that any of the choices that you have made here will have a large enough consequence to cause the page to not update within a single refresh cycle.

--
You received this message because you are subscribed to the Google Groups "jsPsych" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jspsych+u...@googlegroups.com.
To post to this group, send email to jsp...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/jspsych/321ce86e-bb66-4f0b-bdd5-45f686184d8c%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

pat...@g.harvard.edu

unread,
Jul 25, 2016, 10:31:12 AM7/25/16
to jsPsych, pat...@g.harvard.edu
Thanks for taking a look Josh,

Edward

Reply all
Reply to author
Forward
0 new messages