···176176 );
177177 });
178178179179+// Returns an array of images that are used in a timeline for use in preloading
180180+export const getImages = (params: ExperimentParameters) =>
181181+ readdirSync(params.stimulus1.dir)
182182+ .map((filename) => path.join(params.stimulus1.dir, filename))
183183+ .concat(
184184+ readdirSync(params.stimulus2.dir).map((filename) => path.join(params.stimulus2.dir, filename))
185185+ );
186186+187187+179188// -----------------------------------------------------------------------------------------------
180189// Util
181190
-202
app/utils/jspsych/functions.js
···11-import { isNil } from 'lodash';
22-import { jsPsych } from 'jspsych-react';
33-import * as path from 'path';
44-import { readdirSync } from 'fs';
55-import { EXPERIMENTS } from '../../constants/constants';
66-import { buildOddballTimeline } from './timelines/oddball';
77-import { buildN170Timeline } from './timelines/n170';
88-import { buildSSVEPTimeline } from './timelines/ssvep';
99-import { buildStroopTimeline } from './timelines/stroop';
1010-import { buildMultiTimeline } from './timelines/multi';
1111-import { buildSearchTimeline } from './timelines/search';
1212-import { buildCustomLine } from './timelines/custom';
1313-1414-import { MainTimeline, Trial, ExperimentParameters } from '../../constants/interfaces';
1515-1616-// loads a normalized timeline for the default experiments with specific callback fns
1717-export const loadTimeline = (paradigm: EXPERIMENTS) => {
1818- console.log('paradigm', paradigm);
1919- let timeline;
2020- switch (paradigm) {
2121- case EXPERIMENTS.P300:
2222- timeline = buildOddballTimeline();
2323- break;
2424-2525- case EXPERIMENTS.N170:
2626- timeline = buildN170Timeline();
2727- break;
2828-2929- case EXPERIMENTS.SSVEP:
3030- timeline = buildSSVEPTimeline();
3131- break;
3232-3333- case EXPERIMENTS.STROOP:
3434- timeline = buildStroopTimeline();
3535- break;
3636-3737- case EXPERIMENTS.MULTI:
3838- timeline = buildMultiTimeline();
3939- break;
4040-4141- case EXPERIMENTS.SEARCH:
4242- timeline = buildSearchTimeline();
4343- break;
4444-4545- case EXPERIMENTS.CUSTOM:
4646- timeline = buildN170Timeline();
4747- break;
4848-4949- default:
5050- timeline = buildN170Timeline();
5151- break;
5252- }
5353- return timeline;
5454-};
5555-5656-// Converts a normalized timeline template into a classic jsPsych timeline array
5757-export const parseTimeline = (
5858- params: ExperimentParameters,
5959- mainTimeline: MainTimeline,
6060- trials: { [string]: Trial },
6161- timelines: {}
6262-) => {
6363- const parsedTimelines = Object.assign(
6464- ...Object.entries(timelines).map(([id, timeline]) => ({
6565- [id]: {
6666- ...timeline,
6767- timeline: [
6868- {
6969- ...timeline.timeline[0],
7070- trial_duration: () => params.iti + Math.random() * params.jitter,
7171- },
7272- {
7373- ...timeline.timeline[1],
7474- stimulus: jsPsych.timelineVariable('stimulusVar'),
7575- type: params.pluginName,
7676- trial_duration: params.trialDuration,
7777- choices: [params.stimulus1.response, params.stimulus2.response],
7878- event_title: jsPsych.timelineVariable('eventTitleVar'),
7979- correct_response: jsPsych.timelineVariable('responseVar'),
8080- },
8181- ],
8282- sample: {
8383- type: params.sampleType,
8484- size: params.nbTrials,
8585- },
8686- timeline_variables: readdirSync(params.stimulus1.dir)
8787- .map((filename) => ({
8888- stimulusVar: path.join(params.stimulus1.dir, filename),
8989- eventTypeVar: params.stimulus1.type,
9090- eventTitleVar: params.stimulus1.title,
9191- responseVar: params.stimulus1.response,
9292- }))
9393- .concat(
9494- readdirSync(params.stimulus2.dir).map((filename) => ({
9595- stimulusVar: path.join(params.stimulus2.dir, filename),
9696- eventTypeVar: params.stimulus2.type,
9797- eventTitleVar: params.stimulus2.title,
9898- responseVar: params.stimulus2.response,
9999- }))
100100- ),
101101- },
102102- }))
103103- );
104104-105105- // Inserts param.intro as stimulus property of first trial
106106- const parsedTrials = Object.assign(
107107- ...Object.entries(trials).map(([id, trial]) => {
108108- if (id === mainTimeline[0]) {
109109- return { [id]: { ...trial, stimulus: params.intro } };
110110- }
111111- return { [id]: trial };
112112- })
113113- );
114114-115115- // Combine trials and timelines into one object
116116- const jsPsychObject = { ...parsedTrials, ...parsedTimelines };
117117- // Map through the mainTimeline, returning the appropriate trial or timeline based on id
118118- return mainTimeline.map((id) => jsPsychObject[id]);
119119-};
120120-121121-// Fills in on_load functions in jsPsych timeline array with callback functions
122122-// For events, looks for an existing eventType object in the trial to set a value for the eventCallback
123123-export const instantiateTimeline = (
124124- timeline: Object,
125125- eventCallback: (?string) => void,
126126- startCallback: ?() => void = null,
127127- stopCallback: ?() => void = null,
128128- showProgessBar: ?boolean = false
129129-) =>
130130- timeline.map((jspsychObject, index) => {
131131- if (index === 0) {
132132- // intro
133133- return { ...jspsychObject, on_finish: startCallback };
134134- }
135135- if (index === timeline.length - 1) {
136136- // end
137137- return { ...jspsychObject, on_load: stopCallback };
138138- }
139139- if (!isNil(jspsychObject.timeline)) {
140140- const timelineWithCallback = jspsychObject.timeline.map((trial) => {
141141- if (trial.id === 'trial') {
142142- return {
143143- ...trial,
144144- on_start: () => {
145145- const eventType = jsPsych.timelineVariable('eventTypeVar')() || 0;
146146- eventCallback(jsPsych.timelineVariable('eventTypeVar')(), new Date().getTime());
147147- },
148148- on_finish: (data: any) => {
149149- data.key_press = jsPsych.pluginAPI.convertKeyCodeToKeyCharacter(data.key_press);
150150- data.expected_key_press = trial.correct_response();
151151- data.event_title = trial.event_title();
152152- if (data.key_press === data.expected_key_press) {
153153- data.correct = true;
154154- } else {
155155- data.correct = false;
156156- }
157157- if (showProgessBar) {
158158- jsPsych.setProgressBar(
159159- jsPsych.progress().current_trial_global / 2 / jspsychObject.sample.size
160160- );
161161- }
162162- },
163163- };
164164- }
165165- return trial;
166166- });
167167- return { ...jspsychObject, timeline: timelineWithCallback };
168168- }
169169- return jspsychObject;
170170- });
171171-172172-// Gets the last set of behavioral (key press) data stored in jsPsych
173173-export const getBehaviouralData = () => {
174174- const rawData = jsPsych.data.get().values();
175175-176176- // Mutate rawData array to customize behavioural results output
177177- for (let index = 0; index < rawData.length; index++) {
178178- rawData[index] = {
179179- ...rawData[index],
180180- reaction_time: rawData[index].rt, // rename rt to reaction_time
181181- trial_index: rawData[index].trial_index / 2, // Remove fixations from trial index
182182- };
183183- }
184184- rawData.shift(); // Remove first 'welcome' trial
185185- rawData.pop(); // Remove last 'thanks for participating' trial
186186-187187- return jsPsych.data
188188- .get()
189189- .filterCustom((trial) => !trial.stimulus.includes('fixation')) // Remove inter trial data
190190- .ignore('rt')
191191- .ignore('internal_node_id')
192192- .ignore('trial_type')
193193- .csv();
194194-};
195195-196196-// Returns an array of images that are used in a timeline for use in preloading
197197-export const getImages = (params: ExperimentParameters) =>
198198- readdirSync(params.stimulus1.dir)
199199- .map((filename) => path.join(params.stimulus1.dir, filename))
200200- .concat(
201201- readdirSync(params.stimulus2.dir).map((filename) => path.join(params.stimulus2.dir, filename))
202202- );
···11-/**
22- * callback-image-display
33- * Teon L Brooks
44- * modified from Josh de Leeuw
55- *
66- * plugin for displaying a stimulus and getting a keyboard response
77- *
88- * documentation: docs.jspsych.org
99- *
1010- **/
1111-import { jsPsych } from 'jspsych-react';
1212-1313-var plugin = (function() {
1414- var plugin = {};
1515-1616- jsPsych.pluginAPI.registerPreload('image-keyboard-response', 'stimulus', 'image');
1717-1818- plugin.info = {
1919- name: 'image-keyboard-response',
2020- description: '',
2121- parameters: {
2222- stimulus: {
2323- type: jsPsych.plugins.parameterType.IMAGE,
2424- pretty_name: 'stimulus',
2525- default: undefined,
2626- description: 'The image to be displayed',
2727- },
2828- choices: {
2929- type: jsPsych.plugins.parameterType.KEYCODE,
3030- array: true,
3131- pretty_name: 'Choices',
3232- default: jsPsych.ALL_KEYS,
3333- description: 'The keys the subject is allowed to press to respond to the stimulus.',
3434- },
3535- prompt: {
3636- type: jsPsych.plugins.parameterType.STRING,
3737- pretty_name: 'Prompt',
3838- default: '',
3939- description: 'Any content here will be displayed below the stimulus.',
4040- },
4141- stimulus_duration: {
4242- type: jsPsych.plugins.parameterType.INT,
4343- pretty_name: 'Stimulus duration',
4444- default: -1,
4545- description: 'How long to hide the stimulus.',
4646- },
4747- trial_duration: {
4848- type: jsPsych.plugins.parameterType.INT,
4949- pretty_name: 'Trial duration',
5050- default: -1,
5151- description: 'How long to show trial before it ends.',
5252- },
5353- response_ends_trial: {
5454- type: jsPsych.plugins.parameterType.BOOL,
5555- pretty_name: 'Response ends trial',
5656- default: true,
5757- description: 'If true, trial will end when subject makes a response.',
5858- },
5959- },
6060- };
6161-6262- plugin.trial = function(display_element, trial) {
6363- document.getElementById('experiment').focus();
6464- var new_html =
6565- '<img src="' + trial.stimulus + '" id="jspsych-image-keyboard-response-stimulus"></img>';
6666-6767- // add prompt
6868- new_html += trial.prompt;
6969-7070- // add option for on_start parameter in the experiment
7171- if (typeof trial.on_start === 'function') {
7272- trial.on_start.call();
7373- } else if (typeof trial.on_start !== 'undefined') {
7474- trial.data['on_start'] = trial.on_start;
7575- }
7676-7777- // draw
7878- display_element.innerHTML = new_html;
7979-8080- // store response
8181- var response = {
8282- rt: -1,
8383- key: -1,
8484- };
8585-8686- // function to end trial when it is time
8787- var end_trial = function() {
8888- // kill any remaining setTimeout handlers
8989- jsPsych.pluginAPI.clearAllTimeouts();
9090-9191- // kill keyboard listeners
9292- if (typeof keyboardListener !== 'undefined') {
9393- jsPsych.pluginAPI.cancelKeyboardResponse(keyboardListener);
9494- }
9595-9696- // gather the data to store for the trial
9797- var trial_data = {
9898- rt: response.rt,
9999- stimulus: trial.stimulus,
100100- key_press: response.key,
101101- };
102102-103103- // clear the display
104104- display_element.innerHTML = '';
105105-106106- // move on to the next trial
107107- jsPsych.finishTrial(trial_data);
108108- };
109109-110110- // function to handle responses by the subject
111111- var after_response = function(info) {
112112- // after a valid response, the stimulus will have the CSS class 'responded'
113113- // which can be used to provide visual feedback that a response was recorded
114114- display_element.querySelector('#jspsych-image-keyboard-response-stimulus').className +=
115115- ' responded';
116116-117117- // only record the first response
118118- if (response.key == -1) {
119119- response = info;
120120- }
121121-122122- if (trial.response_ends_trial) {
123123- end_trial();
124124- }
125125- };
126126-127127- // start the response listener
128128- if (trial.choices != jsPsych.NO_KEYS) {
129129- var keyboardListener = jsPsych.pluginAPI.getKeyboardResponse({
130130- callback_function: after_response,
131131- valid_responses: trial.choices,
132132- rt_method: 'performance',
133133- persist: false,
134134- allow_held_key: false,
135135- });
136136- }
137137-138138- // hide stimulus if stimulus_duration is set
139139- if (trial.stimulus_duration > 0) {
140140- jsPsych.pluginAPI.setTimeout(function() {
141141- display_element.querySelector(
142142- '#jspsych-image-keyboard-response-stimulus'
143143- ).style.visibility = 'hidden';
144144- }, trial.stimulus_duration);
145145- }
146146-147147- // end trial if trial_duration is set
148148- if (trial.trial_duration > 0) {
149149- jsPsych.pluginAPI.setTimeout(function() {
150150- end_trial();
151151- }, trial.trial_duration);
152152- }
153153- };
154154-155155- return plugin;
156156-})(); // IIFE closure
157157-158158-export default plugin;
-154
app/utils/jspsych/plugins/jspsych-animation.js
···11-/**
22- * jsPsych plugin for showing animations and recording keyboard responses
33- * Josh de Leeuw
44- *
55- * documentation: docs.jspsych.org
66- */
77-import { jsPsych } from 'jspsych-react';
88-99-var plugin = (function() {
1010- var plugin = {};
1111-1212- jsPsych.pluginAPI.registerPreload('animation', 'stimuli', 'image');
1313-1414- plugin.info = {
1515- name: 'animation',
1616- description: '',
1717- parameters: {
1818- stimuli: {
1919- type: jsPsych.plugins.parameterType.STRING,
2020- pretty_name: 'Stimuli',
2121- default: undefined,
2222- array: true,
2323- description: 'The images to be displayed.',
2424- },
2525- frame_time: {
2626- type: jsPsych.plugins.parameterType.INT,
2727- pretty_name: 'Frame time',
2828- default: 250,
2929- description: 'Duration to display each image.',
3030- },
3131- frame_isi: {
3232- type: jsPsych.plugins.parameterType.INT,
3333- pretty_name: 'Frame gap',
3434- default: 0,
3535- description: 'Length of gap to be shown between each image.',
3636- },
3737- sequence_reps: {
3838- type: jsPsych.plugins.parameterType.INT,
3939- pretty_name: 'Sequence repetitions',
4040- default: 1,
4141- description: 'Number of times to show entire sequence.',
4242- },
4343- choices: {
4444- type: jsPsych.plugins.parameterType.KEYCODE,
4545- pretty_name: 'Choices',
4646- default: jsPsych.ALL_KEYS,
4747- array: true,
4848- description: 'Keys subject uses to respond to stimuli.',
4949- },
5050- prompt: {
5151- type: jsPsych.plugins.parameterType.STRING,
5252- pretty_name: 'Prompt',
5353- default: null,
5454- description: 'Any content here will be displayed below stimulus.',
5555- },
5656- },
5757- };
5858-5959- plugin.trial = function(display_element, trial) {
6060- var interval_time = trial.frame_time + trial.frame_isi;
6161- var animate_frame = -1;
6262- var reps = 0;
6363- var startTime = new Date().getTime();
6464- var animation_sequence = [];
6565- var responses = [];
6666- var current_stim = '';
6767-6868- var animate_interval = setInterval(function() {
6969- var showImage = true;
7070- display_element.innerHTML = ''; // clear everything
7171- animate_frame++;
7272- if (animate_frame == trial.stimuli.length) {
7373- animate_frame = 0;
7474- reps++;
7575- if (reps >= trial.sequence_reps) {
7676- endTrial();
7777- clearInterval(animate_interval);
7878- showImage = false;
7979- }
8080- }
8181- if (showImage) {
8282- show_next_frame();
8383- }
8484- }, interval_time);
8585-8686- function show_next_frame() {
8787- // show image
8888- display_element.innerHTML =
8989- '<img src="' + trial.stimuli[animate_frame] + '" id="jspsych-animation-image"></img>';
9090-9191- current_stim = trial.stimuli[animate_frame];
9292-9393- // record when image was shown
9494- animation_sequence.push({
9595- stimulus: trial.stimuli[animate_frame],
9696- time: new Date().getTime() - startTime,
9797- });
9898-9999- if (trial.prompt !== null) {
100100- display_element.innerHTML += trial.prompt;
101101- }
102102-103103- if (trial.frame_isi > 0) {
104104- jsPsych.pluginAPI.setTimeout(function() {
105105- display_element.querySelector('#jspsych-animation-image').style.visibility = 'hidden';
106106- current_stim = 'blank';
107107- // record when blank image was shown
108108- animation_sequence.push({
109109- stimulus: 'blank',
110110- time: new Date().getTime() - startTime,
111111- });
112112- }, trial.frame_time);
113113- }
114114- }
115115-116116- var after_response = function(info) {
117117- responses.push({
118118- key_press: info.key,
119119- rt: info.rt,
120120- stimulus: current_stim,
121121- });
122122-123123- // after a valid response, the stimulus will have the CSS class 'responded'
124124- // which can be used to provide visual feedback that a response was recorded
125125- display_element.querySelector('#jspsych-animation-image').className += ' responded';
126126- };
127127-128128- // hold the jspsych response listener object in memory
129129- // so that we can turn off the response collection when
130130- // the trial ends
131131- var response_listener = jsPsych.pluginAPI.getKeyboardResponse({
132132- callback_function: after_response,
133133- valid_responses: trial.choices,
134134- rt_method: 'performance',
135135- persist: true,
136136- allow_held_key: false,
137137- });
138138-139139- function endTrial() {
140140- jsPsych.pluginAPI.cancelKeyboardResponse(response_listener);
141141-142142- var trial_data = {
143143- animation_sequence: JSON.stringify(animation_sequence),
144144- responses: JSON.stringify(responses),
145145- };
146146-147147- jsPsych.finishTrial(trial_data);
148148- }
149149- };
150150-151151- return plugin;
152152-})();
153153-154154-export default plugin;
-71
app/utils/jspsych/timelines/custom.js
···11-import * as path from 'path';
22-import { EVENTS } from '../../../constants/constants';
33-44-// Default directories containing stimuli
55-const rootFolder = __dirname; // Note: there's a weird issue where the fs readdir function reads from BrainWaves dir
66-77-const facesDir = path.join(rootFolder, 'assets', 'face_house', 'faces');
88-const housesDir = path.join(rootFolder, 'assets', 'face_house', 'houses');
99-const fixation = path.join(rootFolder, 'assets', 'common', 'fixationcross.png');
1010-1111-export const buildCustomLine = () => ({
1212- overview: 'Here is the placeholder for the overview of the Visual Search task',
1313- background: 'Here is the background of the Visual Search task',
1414- background_title: `Title`,
1515- protocol: 'Here is the protocol of the Visual Search task',
1616- params: {
1717- trialDuration: 1000,
1818- nbTrials: 150,
1919- iti: 500,
2020- jitter: 200,
2121- sampleType: 'with-replacement',
2222- pluginName: 'callback-image-display',
2323- intro:
2424- 'You will see the visual search task. Press 1 when a face appears and 9 for a house. Press any key to continue',
2525- showProgressBar: false,
2626- stimulus1: {
2727- dir: facesDir,
2828- title: 'Face',
2929- type: EVENTS.STIMULUS_1,
3030- response: '1',
3131- },
3232- stimulus2: {
3333- dir: housesDir,
3434- title: 'House',
3535- type: EVENTS.STIMULUS_2,
3636- response: '9',
3737- },
3838- },
3939- mainTimeline: ['intro', 'faceHouseTimeline', 'end'], // array of trial and timeline ids
4040- trials: {
4141- intro: {
4242- type: 'callback-html-display',
4343- id: 'intro',
4444- post_trial_gap: 1000,
4545- },
4646- end: {
4747- id: 'end',
4848- type: 'callback-html-display',
4949- stimulus: 'Thanks for participating. Press any key to continue',
5050- response_ends_trial: true,
5151- post_trial_gap: 500,
5252- },
5353- },
5454- timelines: {
5555- faceHouseTimeline: {
5656- id: 'faceHouseTimeline',
5757- timeline: [
5858- {
5959- id: 'interTrial',
6060- type: 'callback-image-display',
6161- stimulus: fixation,
6262- response_ends_trial: false,
6363- },
6464- {
6565- id: 'trial',
6666- response_ends_trial: false,
6767- },
6868- ],
6969- },
7070- },
7171-});
-87
app/utils/jspsych/timelines/multi.js
···11-import * as path from 'path';
22-import { EVENTS } from '../../../constants/constants';
33-44-// Default directories containing stimuli
55-const rootFolder = __dirname; // Note: there's a weird issue where the fs readdir function reads from BrainWaves dir
66-77-const facesDir = path.join(rootFolder, 'assets', 'face_house', 'faces');
88-const housesDir = path.join(rootFolder, 'assets', 'face_house', 'houses');
99-const fixation = path.join(rootFolder, 'assets', 'common', 'fixationcross.png');
1010-1111-export const buildMultiTimeline = () => ({
1212- overview_title: `Multi-Tasking Experiment`,
1313- overview: `Imagine doing your homework while watching television.
1414- Multitasking can be defined as our (in)ability to complete multiple tasks at the same time.`,
1515- background_first_column: `We are constantly facing different tasks at the same time:
1616- doing your homework while watching TV, writing emails and also responding to your friend’s text messages, etc.
1717- Some people pride themselves with being “good multitaskers.” But is this scientifically plausible?`,
1818- background_first_column_question: `Are some people better than others at performing multiple tasks at the same time?`,
1919- background_second_column: `Some research suggests that good multitaskers are actually not performing different tasks simultaneously,
2020- but are instead rapidly switching back ‘n forth between different tasks (click here to read more about this research).
2121- Other research suggests that our brains are able to distribute different tasks across hemispheres
2222- (you can read more about it here).`,
2323- background_second_column_question: `Do you think some people could be better brain ‘distributors’ than others?`,
2424- protocol_title: `What participants are shown`,
2525- protocol: `Participants are shown either a square or diamonds with dots inside.
2626- The location of the object on the screen indicates which rule the participant needs to follow`,
2727- protocol_condition_first_img: `multiConditionShape`,
2828- protocol_condition_first_title: `Rule 1`,
2929- protocol_condition_first: `If the object is shown on top, they need to respond to the shape (pressing ‘n’ for square and ‘b’ for diamond).`,
3030- protocol_condition_second_img: `multiConditionDots`,
3131- protocol_condition_second_title: `Rule 2`,
3232- protocol_condition_second: `If the object is shown on the bottom, they need to respond to the number of dots inside (pressing ‘n’ for 3 dots and ‘b’ for 2 dots). `,
3333- params: {
3434- trialDuration: 1000,
3535- nbTrials: 150,
3636- iti: 500,
3737- jitter: 200,
3838- sampleType: 'with-replacement',
3939- pluginName: 'callback-image-display',
4040- intro: 'You will see the multi-tasking test. Press any key to continue',
4141- showProgressBar: false,
4242- stimulus1: {
4343- dir: facesDir,
4444- title: 'No switching',
4545- type: EVENTS.STIMULUS_1,
4646- response: '1',
4747- },
4848- stimulus2: {
4949- dir: housesDir,
5050- title: 'Switching',
5151- type: EVENTS.STIMULUS_2,
5252- response: '9',
5353- },
5454- },
5555- mainTimeline: ['intro', 'faceHouseTimeline', 'end'], // array of trial and timeline ids
5656- trials: {
5757- intro: {
5858- type: 'callback-html-display',
5959- id: 'intro',
6060- post_trial_gap: 1000,
6161- },
6262- end: {
6363- id: 'end',
6464- type: 'callback-html-display',
6565- stimulus: 'Thanks for participating. Press any key to continue',
6666- response_ends_trial: true,
6767- post_trial_gap: 500,
6868- },
6969- },
7070- timelines: {
7171- faceHouseTimeline: {
7272- id: 'faceHouseTimeline',
7373- timeline: [
7474- {
7575- id: 'interTrial',
7676- type: 'callback-image-display',
7777- stimulus: fixation,
7878- response_ends_trial: false,
7979- },
8080- {
8181- id: 'trial',
8282- response_ends_trial: false,
8383- },
8484- ],
8585- },
8686- },
8787-});
-89
app/utils/jspsych/timelines/n170.js
···11-import * as path from 'path';
22-import { EVENTS } from '../../../constants/constants';
33-44-// Default directories containing stimuli
55-const rootFolder = __dirname; // Note: there's a weird issue where the fs readdir function reads from BrainWaves dir
66-77-const facesDir = path.join(rootFolder, 'assets', 'face_house', 'faces');
88-const housesDir = path.join(rootFolder, 'assets', 'face_house', 'houses');
99-const fixation = path.join(rootFolder, 'assets', 'common', 'fixationcross.png');
1010-1111-export const buildN170Timeline = () => ({
1212- overview_title: `Faces Houses Experiment`,
1313- overview: `When you scroll through your social media feed, you may find that you are more likely to pause when a picture
1414- contains a face, than, for example, a tree.
1515- In this experiment, you will explore whether our brains process faces differently than other objects (in this case, houses).`,
1616- background_first_column: `Did you know that we spend more time looking at faces than any other type of stimuli?
1717- Faces contain a lot of information that is relevant to our day-to-day lives.
1818- For example, by looking at someone’s face we can assess their emotional state.
1919- This has led researchers speculate that faces may be processed differently than other stimuli.`,
2020- background_first_column_question: `In fact, there is a special area in your brain, the Fusiform Face Area, that has been shown to be selective for faces.
2121- People who have damage in this area may have a hard time recognizing faces, a condition called face blindness, or prosopagnosia.`,
2222- background_second_column: `In this video the famous neurologist Oliver Sacks explains what it is like to have face blindness to the extent
2323- that he sometimes didn’t even recognize his own face (!)`,
2424- background_second_column_question: `Fun fact: Brad Pitt claims he has face blindness, but he has not been tested.
2525- Do you know anyone who has face blindness?`,
2626- protocol_title: `What participants are shown`,
2727- protocol: `In the Faces/Houses experiment, you will see pictures of different faces and houses.`,
2828- protocol_condition_first_img: `conditionFace`,
2929- protocol_condition_first_title: `Faces`,
3030- protocol_condition_first: `When you see a face, press the key “1”.`,
3131- protocol_condition_second_img: `conditionHouse`,
3232- protocol_condition_second_title: `Houses`,
3333- protocol_condition_second: `If you see a house, press “9”.`,
3434- params: {
3535- trialDuration: 1000,
3636- nbTrials: 150,
3737- iti: 500,
3838- jitter: 200,
3939- sampleType: 'with-replacement',
4040- pluginName: 'callback-image-display',
4141- intro:
4242- 'You will view a series of faces and houses. Press 1 when a face appears and 9 for a house. Press any key to continue',
4343- showProgressBar: false,
4444- stimulus1: {
4545- dir: facesDir,
4646- title: 'Face',
4747- type: EVENTS.STIMULUS_1,
4848- response: '1',
4949- },
5050- stimulus2: {
5151- dir: housesDir,
5252- title: 'House',
5353- type: EVENTS.STIMULUS_2,
5454- response: '9',
5555- },
5656- },
5757- mainTimeline: ['intro', 'faceHouseTimeline', 'end'], // array of trial and timeline ids
5858- trials: {
5959- intro: {
6060- type: 'callback-html-display',
6161- id: 'intro',
6262- post_trial_gap: 1000,
6363- },
6464- end: {
6565- id: 'end',
6666- type: 'callback-html-display',
6767- stimulus: 'Thanks for participating. Press any key to continue',
6868- response_ends_trial: true,
6969- post_trial_gap: 500,
7070- },
7171- },
7272- timelines: {
7373- faceHouseTimeline: {
7474- id: 'faceHouseTimeline',
7575- timeline: [
7676- {
7777- id: 'interTrial',
7878- type: 'callback-image-display',
7979- stimulus: fixation,
8080- response_ends_trial: false,
8181- },
8282- {
8383- id: 'trial',
8484- response_ends_trial: false,
8585- },
8686- ],
8787- },
8888- },
8989-});
-92
app/utils/jspsych/timelines/oddball.js
···11-import { jsPsych } from 'jspsych-react';
22-import * as path from 'path';
33-import { readdirSync } from 'fs';
44-55-// Default experiment parameters
66-const params = {
77- trial_duration: 300,
88- stim_duration: 300,
99- iti: 300,
1010- jitter: 200,
1111- n_trials: 170, // Around two minutes at a rate of ~700 ms per trial
1212- prob: 0.15,
1313- plugin_name: 'callback-image-display',
1414-};
1515-1616-// Default directories containing stimuli
1717-// Note: there's a weird issue where the fs readdir function reads from BrainWaves dir
1818-// while the timeline reads from Brainwaves/app. Currently removing 'app/' from path in timeline
1919-const rootFolder = __dirname;
2020-const targetsDir = path.join(rootFolder, './app/assets/cat_dog/cats/');
2121-const nontargetsDir = path.join(rootFolder, './app/assets/cat_dog/dogs/');
2222-2323-// Oddball sampling function
2424-// Assumes first half of the trials are oddball stimuli
2525-// TODO: Make this autogenerate from reading dir
2626-const oddballSamplingFn = (trials) => {
2727- const trialOrder = new Array(params.n_trials).fill(0).map(() => {
2828- if (Math.random() > params.prob) {
2929- return Math.floor(Math.random() * (trials.length - trials.length / 2) + trials.length / 2);
3030- }
3131- return Math.floor(Math.random() * (trials.length / 2));
3232- });
3333- return trialOrder;
3434-};
3535-3636-export const buildOddballTimeline = (callback) => ({
3737- mainTimeline: ['welcome', 'oddballTimeline', 'end'], // array of trial and timeline ids
3838- trials: {
3939- welcome: {
4040- type: 'callback-html-display',
4141- id: 'welcome',
4242- stimulus: 'Welcome to the experiment. Press any key to begin.',
4343- post_trial_gap: 1000,
4444- on_load: () => callback('start'),
4545- },
4646- end: {
4747- id: 'end',
4848- type: 'callback-html-display',
4949- stimulus: 'Thanks for participating',
5050- post_trial_gap: 500,
5151- on_load: callback('stop'),
5252- },
5353- },
5454- timelines: {
5555- oddballTimeline: {
5656- id: 'oddballTimeline',
5757- timeline: [
5858- {
5959- id: 'interTrial',
6060- type: 'callback-image-display',
6161- stimulus: './assets/cat_dog/fixation.jpg',
6262- trial_duration: () => params.iti + Math.random() * params.jitter,
6363- post_trial_gap: 0,
6464- },
6565- {
6666- id: 'trial',
6767- stimulus: jsPsych.timelineVariable('stimulusVar'),
6868- on_load: jsPsych.timelineVariable('callbackVar'),
6969- type: params.plugin_name,
7070- choices: ['f', 'j'],
7171- trial_duration: params.trial_duration,
7272- post_trial_gap: 0,
7373- },
7474- ],
7575- sample: {
7676- type: 'custom',
7777- fn: oddballSamplingFn,
7878- },
7979- timeline_variables: readdirSync(targetsDir)
8080- .map((filename) => ({
8181- stimulusVar: targetsDir + filename,
8282- callbackVar: () => callback('target'),
8383- }))
8484- .concat(
8585- readdirSync(nontargetsDir).map((filename) => ({
8686- stimulusVar: nontargetsDir + filename,
8787- callbackVar: () => callback('nontarget'),
8888- }))
8989- ),
9090- },
9191- },
9292-});
-91
app/utils/jspsych/timelines/search.js
···11-import * as path from 'path';
22-import { EVENTS } from '../../../constants/constants';
33-44-// Default directories containing stimuli
55-const rootFolder = __dirname; // Note: there's a weird issue where the fs readdir function reads from BrainWaves dir
66-77-const facesDir = path.join(rootFolder, 'assets', 'face_house', 'faces');
88-const housesDir = path.join(rootFolder, 'assets', 'face_house', 'houses');
99-const fixation = path.join(rootFolder, 'assets', 'common', 'fixationcross.png');
1010-1111-export const buildSearchTimeline = () => ({
1212- overview_title: `Visual Search Experiment`,
1313- overview: `Imagine yourself looking for your keys in a messy room.
1414- Visual search corresponds to looking for a specific object (e.g. keys) surrounded by other objects (e.g. clothing and books).
1515- In the Visual Search Task, you will explore how irrelevant objects affect how well you can find the object you are looking for,
1616- and what this may tell us about how your brain can find and focus on relevant information from all the task-irrelevant
1717- information around us.`,
1818- background_first_column: `Say you’re meeting a friend in a public space: do you think it easier to find each other on a street corner
1919- than in the middle of Times Square around rush hour? What about a parking lot filled with cars?
2020- Scientists have long wondered how our brains allow us to navigate a world full of irrelevant visual information with such relative ease.
2121- One way to investigate this is to ask people to complete a visual search task.`,
2222- background_first_column_question: ``,
2323- background_second_column: `The hypothesis would be that if you see objects that are very similar to what you are looking for
2424- (like other people who might look like your friend, as opposed to parked cars),
2525- these might distract you, making it harder to complete your search.
2626- Brain scientists aren’t the only ones who are interested in exploring the most optimal way in which visual searches occur.`,
2727- background_second_column_question: `Can you think of who else might want to know this?`,
2828- protocol_title: `What participants are shown`,
2929- protocol: `In the Visual Search Task, your goal is to find the right-side up orange T while ignoring upside-down orange T’s or T’s in other colors.`,
3030- protocol_condition_first_img: `conditionOrangeT`,
3131- protocol_condition_first_title: `Orange T`,
3232- protocol_condition_first: `If you find the orange T, you should press the ‘b’ key. `,
3333- protocol_condition_second_img: `conditionNoOrangeT`,
3434- protocol_condition_second_title: `No orange T`,
3535- protocol_condition_second: `If the orange T is not on the screen, press the ‘n’ key instead.`,
3636- params: {
3737- trialDuration: 1000,
3838- nbTrials: 150,
3939- iti: 500,
4040- jitter: 200,
4141- sampleType: 'with-replacement',
4242- pluginName: 'callback-image-display',
4343- intro:
4444- 'You will see the visual search task. Press 1 when a face appears and 9 for a house. Press any key to continue',
4545- showProgressBar: false,
4646- stimulus1: {
4747- dir: facesDir,
4848- title: 'No target',
4949- type: EVENTS.STIMULUS_1,
5050- response: '1',
5151- },
5252- stimulus2: {
5353- dir: housesDir,
5454- title: 'Target',
5555- type: EVENTS.STIMULUS_2,
5656- response: '9',
5757- },
5858- },
5959- mainTimeline: ['intro', 'faceHouseTimeline', 'end'], // array of trial and timeline ids
6060- trials: {
6161- intro: {
6262- type: 'callback-html-display',
6363- id: 'intro',
6464- post_trial_gap: 1000,
6565- },
6666- end: {
6767- id: 'end',
6868- type: 'callback-html-display',
6969- stimulus: 'Thanks for participating. Press any key to continue',
7070- response_ends_trial: true,
7171- post_trial_gap: 500,
7272- },
7373- },
7474- timelines: {
7575- faceHouseTimeline: {
7676- id: 'faceHouseTimeline',
7777- timeline: [
7878- {
7979- id: 'interTrial',
8080- type: 'callback-image-display',
8181- stimulus: fixation,
8282- response_ends_trial: false,
8383- },
8484- {
8585- id: 'trial',
8686- response_ends_trial: false,
8787- },
8888- ],
8989- },
9090- },
9191-});
···11-import * as path from 'path';
22-import { EVENTS } from '../../../constants/constants';
33-44-// Default directories containing stimuli
55-const rootFolder = __dirname; // Note: there's a weird issue where the fs readdir function reads from BrainWaves dir
66-77-const facesDir = path.join(rootFolder, 'assets', 'face_house', 'faces');
88-const housesDir = path.join(rootFolder, 'assets', 'face_house', 'houses');
99-const fixation = path.join(rootFolder, 'assets', 'common', 'fixationcross.png');
1010-1111-export const buildStroopTimeline = () => ({
1212- overview_title: `Stroop Experiment`,
1313- overview: `The stroop effect occurs when different properties of something you see or hear contradict one another.
1414- The most common example is a mismatch between the meaning and color of a word (e.g., the word BLUE written in red ink).
1515- In the Stroop task, you will explore how these types of mismatches affect your behavior and what that may tell us about
1616- how your brain processes information.`,
1717- background_first_column: `You may have played a game with your friends or siblings with the following seemingly simple rule:
1818- “yes” means “no” and “no” means “yes”. If so, you are all too familiar with Stroop-like effect:
1919- the information you receive points to different concepts and you have to “ignore” one source of information.`,
2020- background_first_column_question: `Fun fact: in some Balkan countries, nodding means “no” and shaking your head means “yes”.`,
2121- background_second_column: `Researchers have used different kinds of Stroop tasks to ask how our brains deal with contradictory information.
2222- This may be more difficult under some conditions (for example, when we don’t get enough sleep) and for some people
2323- (for example, children with Attention-deficit/hyperactivity disorder (ADHD)).`,
2424- background_second_column_question: `You can read more about the Stroop task here. `,
2525- protocol_title: `What participants are shown`,
2626- protocol: `In the Stroop task, you will see different words written in different colors
2727- (e.g., the word “GREEN” may be written in a green-colored font, but it may also be written in a red font).
2828- You will respond only to the color of the font, ignoring the meaning of the word.
2929- If the font is red, press the key ‘r’; if yellow, press ‘y’; if blue, press ‘b’; and if green, press ‘g’.`,
3030- protocol_condition_first_img: `conditionCongruent`,
3131- protocol_condition_first_title: `"Green" written in green`,
3232- protocol_condition_first: `The color is green, so the correct response is ‘g’.`,
3333- protocol_condition_second_img: `conditionIncongruent`,
3434- protocol_condition_second_title: `"Green" written in red`,
3535- protocol_condition_second: `The color is red, so the correct response is ‘r’.`,
3636- params: {
3737- trialDuration: 1000,
3838- nbTrials: 150,
3939- iti: 500,
4040- jitter: 200,
4141- sampleType: 'with-replacement',
4242- pluginName: 'callback-image-display',
4343- intro:
4444- 'You will see the stroop task. Press 1 when a face appears and 9 for a house. Press any key to continue',
4545- showProgressBar: false,
4646- stimulus1: {
4747- dir: facesDir,
4848- title: 'Incongruent',
4949- type: EVENTS.STIMULUS_1,
5050- response: '1',
5151- },
5252- stimulus2: {
5353- dir: housesDir,
5454- title: 'Congruent',
5555- type: EVENTS.STIMULUS_2,
5656- response: '9',
5757- },
5858- },
5959- mainTimeline: ['intro', 'faceHouseTimeline', 'end'], // array of trial and timeline ids
6060- trials: {
6161- intro: {
6262- type: 'callback-html-display',
6363- id: 'intro',
6464- post_trial_gap: 1000,
6565- },
6666- end: {
6767- id: 'end',
6868- type: 'callback-html-display',
6969- stimulus: 'Thanks for participating. Press any key to continue',
7070- response_ends_trial: true,
7171- post_trial_gap: 500,
7272- },
7373- },
7474- timelines: {
7575- faceHouseTimeline: {
7676- id: 'faceHouseTimeline',
7777- timeline: [
7878- {
7979- id: 'interTrial',
8080- type: 'callback-image-display',
8181- stimulus: fixation,
8282- response_ends_trial: false,
8383- },
8484- {
8585- id: 'trial',
8686- response_ends_trial: false,
8787- },
8888- ],
8989- },
9090- },
9191-});
···6868 selfPaced: true,
6969 jitter: 200,
7070 sampleType: 'with-replacement',
7171- pluginName: 'callback-image-display',
7271 intro: `You will view a series of faces and houses. Press 1 when a face appears and 9 for a house. Press the the space bar on your keyboard to start doing the practice trials. If you want to skip the practice trials and go directly to the task, press the "q" button on your keyboard.`,
7372 taskHelp: `Press 1 for a face and 9 for a house`,
7473 showProgressBar: false,
-1
app/utils/labjs/protocols/multi.js
···4949 iti: 1000,
5050 jitter: 200,
5151 sampleType: 'with-replacement',
5252- pluginName: 'callback-image-display',
5352 intro: `In this task you will learn about multitasking difficulties using a task mixing and switching paradigm. You will go through several instruction and training blocks and then several blocks of real data collection will follow. Press the space bar to continue with the instructions.`,
5453 showProgressBar: false,
5554 stimulus1: {
-1
app/utils/labjs/protocols/search.js
···4242 iti: 500,
4343 jitter: 200,
4444 sampleType: 'with-replacement',
4545- pluginName: 'callback-image-display',
4645 intro: `You know how difficult it is to find your keys in a messy room. We want to know how good you are in quickly finding your keys. Instead of keys, we just want to know how quickly you can find an orange T amongst blue Ts and upside-down orange Ts. Sounds easy! But it is not at all that easy!`,
4746 showProgressBar: false,
4847 stimulus1: {
-1
app/utils/labjs/protocols/stroop.js
···4848 iti: 500,
4949 jitter: 200,
5050 sampleType: 'with-replacement',
5151- pluginName: 'callback-image-display',
5251 intro: `In this experiment, your task will be to identify the color of the word shown on the screen. The word itself is immaterial - you can safely ignore it.`,
5352 showProgressBar: false,
5453 stimulus1: {