An easy-to-use platform for EEG experimentation in the classroom
at main 526 lines 21 kB view raw
1/* eslint-disable */ 2 3// Define study 4const experimentObject = { 5 title: 'root', 6 type: 'lab.flow.Sequence', 7 parameters: {}, 8 plugins: [], 9 metadata: {}, 10 files: {}, 11 responses: {}, 12 content: [ 13 { 14 type: 'lab.flow.Sequence', 15 files: {}, 16 parameters: {}, 17 responses: {}, 18 messageHandlers: {}, 19 title: 'The face-house task', 20 content: [ 21 { 22 type: 'lab.html.Screen', 23 files: {}, 24 parameters: {}, 25 responses: { 26 'keypress(Space)': 'continue', 27 'keypress(q)': 'skipPractice', 28 }, 29 messageHandlers: {}, 30 title: 'Instruction', 31 content: 32 '\u003Cheader class="content-vertical-center content-horizontal-center"\u003E\n \u003Ch1\u003E${this.parameters.title || "The face-house task"}\u003C\u002Fh1\u003E\n\u003C\u002Fheader\u003E\n\n\u003Cmain\u003E\n\n \u003Cp\u003E\n ${this.parameters.intro}\n \u003C\u002Fp\u003E\n \n\u003C\u002Fmain\u003E\n\n\u003Cfooter class="content-vertical-center content-horizontal-center"\u003E\n \n\u003C\u002Ffooter\u003E', 33 }, 34 { 35 type: 'lab.flow.Loop', 36 files: {}, 37 parameters: {}, 38 templateParameters: [], 39 sample: { 40 mode: 'draw-shuffle', 41 n: '', 42 }, 43 responses: {}, 44 messageHandlers: { 45 'before:prepare': function anonymous() { 46 let initParameters = [...this.parameters.stimuli] || []; 47 initParameters = 48 initParameters.filter((t) => t.phase === 'practice') || []; 49 let numberTrials = this.parameters.nbPracticeTrials; 50 if (initParameters.length === 0) { 51 numberTrials = 0; 52 } 53 const randomize = this.parameters.randomize; 54 const trialsLength = initParameters.length; 55 if (numberTrials > trialsLength) { 56 const append = [...initParameters]; 57 const multiply = Math.ceil(numberTrials / trialsLength); 58 for (let i = 0; i < multiply; i++) { 59 initParameters = initParameters.concat(append); 60 } 61 } 62 63 function shuffle(a) { 64 let j, x, i; 65 for (i = a.length - 1; i > 0; i--) { 66 j = Math.floor(Math.random() * (i + 1)); 67 x = a[i]; 68 a[i] = a[j]; 69 a[j] = x; 70 } 71 return a; 72 } 73 74 if (randomize === 'random') { 75 shuffle(initParameters); 76 } 77 78 const trialConstructor = (file) => ({ 79 condition: file.condition, 80 image: `${file.dir}/${file.filename}`, 81 correctResponse: file.response, 82 phase: 'practice', 83 name: file.name, 84 type: file.type, 85 }); 86 87 // balance design across conditions 88 const conditions = Array.from( 89 new Set(initParameters.map((p) => p.condition)) 90 ); 91 const conditionsParameters = {}; 92 for (const c of conditions) { 93 conditionsParameters[c] = initParameters.filter( 94 (p) => p.condition == c 95 ); 96 } 97 const numberConditionsTrials = Math.ceil( 98 numberTrials / conditions.length 99 ); 100 let balancedParameters = []; 101 for (let i = 0; i < numberConditionsTrials; i++) { 102 for (const c of conditions) { 103 balancedParameters = balancedParameters.concat( 104 conditionsParameters[c][i % conditionsParameters[c].length] 105 ); 106 } 107 } 108 initParameters = [...balancedParameters.slice(0, numberTrials)]; 109 110 let practiceParameters = []; 111 for (let i = 0; i < numberTrials; i++) { 112 practiceParameters = practiceParameters.concat( 113 trialConstructor(initParameters[i]) 114 ); 115 } 116 117 // assign options values to parameters of this task 118 this.options.templateParameters = practiceParameters; 119 if (randomize === 'random') { 120 this.options.shuffle = true; 121 } else { 122 this.options.shuffle = false; 123 } 124 }, 125 }, 126 title: 'Practice loop', 127 shuffleGroups: [], 128 template: { 129 type: 'lab.flow.Sequence', 130 files: {}, 131 parameters: {}, 132 responses: {}, 133 messageHandlers: {}, 134 title: 'Trial', 135 content: [ 136 { 137 type: 'lab.canvas.Screen', 138 content: [ 139 { 140 type: 'rect', 141 left: 0, 142 top: 0, 143 angle: 0, 144 width: 10, 145 height: '50', 146 stroke: null, 147 strokeWidth: 1, 148 fill: 'black', 149 }, 150 { 151 type: 'rect', 152 left: 0, 153 top: 0, 154 angle: 90, 155 width: 10, 156 height: '50', 157 stroke: null, 158 strokeWidth: 1, 159 fill: 'black', 160 }, 161 ], 162 files: {}, 163 parameters: {}, 164 responses: {}, 165 messageHandlers: {}, 166 viewport: [800, 600], 167 title: 'Fixation cross', 168 timeout: '${parameters.iti}', 169 }, 170 { 171 type: 'lab.html.Screen', 172 files: {}, 173 responses: {}, 174 parameters: {}, 175 messageHandlers: { 176 'before:prepare': function anonymous() { 177 // This code registers an event listener for this screen. 178 // We have a timeout for this screen, but we also want to record responses. 179 // On a keydown event, we record the key and the time of response. 180 // We also record whether the response was correct (by comparing the pressed key with the correct response which is defined inside the Experiment loop). 181 // "this" in the code means the lab.js experiment. 182 const responses = [ 183 ...new Set( 184 this.parameters.stimuli.map((e) => e.response) 185 ), 186 ]; 187 this.data.trial_number = 188 1 + 189 parseInt( 190 this.options.id.split('_')[ 191 this.options.id.split('_').length - 2 192 ] 193 ); 194 this.data.response_given = 'no'; 195 196 this.options.events = { 197 keydown: (event) => { 198 if (responses.includes(event.key)) { 199 this.data.reaction_time = this.timer; 200 if (this.parameters.phase === 'task') 201 this.data.response_given = 'yes'; 202 this.data.response = event.key; 203 if ( 204 this.data.response == 205 this.parameters.correctResponse 206 ) { 207 this.data.correct_response = true; 208 } else { 209 this.data.correct_response = false; 210 } 211 this.end(); 212 } 213 }, 214 }; 215 }, 216 run: function anonymous() { 217 this.parameters.callbackForEEG(this.parameters.type); 218 }, 219 }, 220 title: 'Stimulus', 221 timeout: 222 "${parameters.selfPaced ? '3600000' : parameters.presentationTime}", 223 content: 224 '\u003Cmain class="content-horizontal-center content-vertical-center"\u003E\n \u003Cdiv\u003E\n \u003Cimg src=${ this.files[this.parameters.image] } height=${ this.parameters.imageHeight } \u002F\u003E\n \u003C\u002Fdiv\u003E\n\u003C\u002Fmain\u003E\n\n\u003Cfooter class="content-vertical-center content-horizontal-center"\u003E\n \u003Cp\u003E\n ${this.parameters.taskHelp} \n \u003C\u002Fp\u003E\n\u003C\u002Ffooter\u003E', 225 }, 226 { 227 type: 'lab.canvas.Screen', 228 content: [ 229 { 230 type: 'i-text', 231 left: 0, 232 top: 0, 233 angle: 0, 234 width: 895.3, 235 height: 36.16, 236 stroke: null, 237 strokeWidth: 1, 238 fill: "${ state.correct_response ? 'green' : 'red' }", 239 text: "${ state.correct_response ? 'Well done!' : 'Please respond accurately' }", 240 fontStyle: 'normal', 241 fontWeight: 'bold', 242 fontSize: '52', 243 fontFamily: 'sans-serif', 244 lineHeight: 1.16, 245 textAlign: 'center', 246 }, 247 ], 248 files: {}, 249 parameters: {}, 250 responses: {}, 251 messageHandlers: { 252 end: function anonymous() { 253 this.data.correct_response = false; 254 }, 255 }, 256 viewport: [800, 600], 257 title: 'Feedback', 258 tardy: true, 259 timeout: '1000', 260 skip: "${ parameters.phase === 'task' }", 261 }, 262 ], 263 }, 264 }, 265 { 266 type: 'lab.html.Screen', 267 files: {}, 268 parameters: {}, 269 responses: { 270 'keypress(Space)': 'continue', 271 }, 272 messageHandlers: {}, 273 title: 'Main task', 274 content: 275 '\u003Cheader class="content-vertical-center content-horizontal-center"\u003E\n \u003Ch1\u003EReady for the real data collection?\u003C\u002Fh1\u003E\n\u003C\u002Fheader\u003E\n\u003Cmain\u003E\n\n \u003Cp\u003E\n Press the the space bar to start the main task.\n \u003C\u002Fp\u003E\n\n\u003C\u002Fmain\u003E\n\u003Cfooter class="content-vertical-center content-horizontal-center"\u003E\n \n\u003C\u002Ffooter\u003E', 276 }, 277 { 278 type: 'lab.flow.Loop', 279 files: {}, 280 parameters: {}, 281 templateParameters: [], 282 sample: { 283 mode: 'draw-shuffle', 284 n: '', 285 }, 286 responses: {}, 287 messageHandlers: { 288 'before:prepare': function anonymous() { 289 let initialParameters = [...this.parameters.stimuli] || []; 290 initialParameters = 291 initialParameters.filter((t) => t.phase === 'main') || []; 292 let numberTrials = this.parameters.nbTrials; 293 if (initialParameters.length === 0) { 294 numberTrials = 0; 295 } 296 const randomize = this.parameters.randomize; 297 const trialsLength = initialParameters.length; 298 if (numberTrials > trialsLength) { 299 const append = [...initialParameters]; 300 const multiply = Math.ceil(numberTrials / trialsLength); 301 for (let i = 0; i < multiply; i++) { 302 initialParameters = initialParameters.concat(append); 303 } 304 } 305 306 function shuffle(a) { 307 let j, x, i; 308 for (i = a.length - 1; i > 0; i--) { 309 j = Math.floor(Math.random() * (i + 1)); 310 x = a[i]; 311 a[i] = a[j]; 312 a[j] = x; 313 } 314 return a; 315 } 316 317 if (randomize === 'random') { 318 shuffle(initialParameters); 319 } 320 321 const trialConstructor = (file) => ({ 322 condition: file.condition, 323 image: `${file.dir}/${file.filename}`, 324 correctResponse: file.response, 325 phase: 'task', 326 name: file.name, 327 type: file.type, 328 }); 329 // balance design across conditions 330 const conditions = Array.from( 331 new Set(initialParameters.map((p) => p.condition)) 332 ); 333 const conditionsParameters = {}; 334 for (const c of conditions) { 335 conditionsParameters[c] = initialParameters.filter( 336 (p) => p.condition == c 337 ); 338 } 339 const numberConditionsTrials = Math.ceil( 340 numberTrials / conditions.length 341 ); 342 let balancedParameters = []; 343 for (let i = 0; i < numberConditionsTrials; i++) { 344 for (const c of conditions) { 345 balancedParameters = balancedParameters.concat( 346 conditionsParameters[c][i % conditionsParameters[c].length] 347 ); 348 } 349 } 350 initialParameters = [ 351 ...balancedParameters.slice(0, numberTrials), 352 ]; 353 354 let trialParameters = []; 355 for (let i = 0; i < numberTrials; i++) { 356 trialParameters = [ 357 ...trialParameters.concat( 358 trialConstructor(initialParameters[i]) 359 ), 360 ]; 361 } 362 // assign options values to parameters of this task 363 this.options.templateParameters = trialParameters; 364 if (randomize === 'random') { 365 this.options.shuffle = true; 366 } else { 367 this.options.shuffle = false; 368 } 369 }, 370 }, 371 title: 'Experiment loop', 372 shuffleGroups: [], 373 template: { 374 type: 'lab.flow.Sequence', 375 files: {}, 376 parameters: {}, 377 responses: {}, 378 messageHandlers: {}, 379 title: 'Trial', 380 content: [ 381 { 382 type: 'lab.canvas.Screen', 383 content: [ 384 { 385 type: 'rect', 386 left: 0, 387 top: 0, 388 angle: 0, 389 width: 10, 390 height: '50', 391 stroke: null, 392 strokeWidth: 1, 393 fill: 'black', 394 }, 395 { 396 type: 'rect', 397 left: 0, 398 top: 0, 399 angle: 90, 400 width: 10, 401 height: '50', 402 stroke: null, 403 strokeWidth: 1, 404 fill: 'black', 405 }, 406 ], 407 files: {}, 408 parameters: {}, 409 responses: {}, 410 messageHandlers: {}, 411 viewport: [800, 600], 412 title: 'Fixation cross', 413 timeout: '${parameters.iti}', 414 }, 415 { 416 type: 'lab.html.Screen', 417 files: {}, 418 responses: {}, 419 parameters: {}, 420 messageHandlers: { 421 'before:prepare': function anonymous() { 422 // This code registers an event listener for this screen. 423 // We have a timeout for this screen, but we also want to record responses. 424 // On a keydown event, we record the key and the time of response. 425 // We also record whether the response was correct (by comparing the pressed key with the correct response which is defined inside the Experiment loop). 426 // "this" in the code means the lab.js experiment. 427 const responses = [ 428 ...new Set( 429 this.parameters.stimuli.map((e) => e.response) 430 ), 431 ]; 432 this.data.trial_number = 433 1 + 434 parseInt( 435 this.options.id.split('_')[ 436 this.options.id.split('_').length - 2 437 ] 438 ); 439 this.data.response_given = 'no'; 440 441 this.options.events = { 442 keydown: (event) => { 443 if (responses.includes(event.key)) { 444 this.data.reaction_time = this.timer; 445 if (this.parameters.phase === 'task') 446 this.data.response_given = 'yes'; 447 this.data.response = event.key; 448 if ( 449 this.data.response == 450 this.parameters.correctResponse 451 ) { 452 this.data.correct_response = true; 453 } else { 454 this.data.correct_response = false; 455 } 456 this.end(); 457 } 458 }, 459 }; 460 }, 461 run: function anonymous() { 462 this.parameters.callbackForEEG(this.parameters.type); 463 }, 464 }, 465 title: 'Stimulus', 466 timeout: 467 "${parameters.selfPaced ? '3600000' : parameters.presentationTime}", 468 timeout: 469 "${parameters.selfPaced ? '3600000' : parameters.presentationTime}", 470 content: 471 '\u003Cmain class="content-horizontal-center content-vertical-center"\u003E\n \u003Cdiv\u003E\n \u003Cimg src=${ this.files[this.parameters.image] } height=${ this.parameters.imageHeight } \u002F\u003E\n \u003C\u002Fdiv\u003E\n\u003C\u002Fmain\u003E\n\n\u003Cfooter class="content-vertical-center content-horizontal-center"\u003E\n \u003Cp\u003E\n ${this.parameters.taskHelp} \n \u003C\u002Fp\u003E\n\u003C\u002Ffooter\u003E', 472 }, 473 { 474 type: 'lab.canvas.Screen', 475 content: [ 476 { 477 type: 'i-text', 478 left: 0, 479 top: 0, 480 angle: 0, 481 width: 895.3, 482 height: 36.16, 483 stroke: null, 484 strokeWidth: 1, 485 fill: "${ state.correct_response ? 'green' : 'red' }", 486 text: "${ state.correct_response ? 'Well done!' : 'Please respond accurately' }", 487 fontStyle: 'normal', 488 fontWeight: 'bold', 489 fontSize: '52', 490 fontFamily: 'sans-serif', 491 lineHeight: 1.16, 492 textAlign: 'center', 493 }, 494 ], 495 files: {}, 496 parameters: {}, 497 responses: {}, 498 messageHandlers: {}, 499 viewport: [800, 600], 500 title: 'Feedback', 501 tardy: true, 502 timeout: '1000', 503 skip: "${ parameters.phase === 'task' }", 504 }, 505 ], 506 }, 507 }, 508 { 509 type: 'lab.html.Screen', 510 files: {}, 511 parameters: {}, 512 responses: { 513 'keypress(Space)': 'end', 514 }, 515 messageHandlers: {}, 516 title: 'End', 517 content: 518 '\u003Cheader class="content-vertical-center content-horizontal-center"\u003E\n \n\u003C\u002Fheader\u003E\n\n\u003Cmain\u003E\n \u003Ch1\u003E\n Thank you!\n \u003C\u002Fh1\u003E\n \u003Ch1\u003E\n Press the space bar to finish the task.\n \u003C\u002Fh1\u003E\n\u003C\u002Fmain\u003E\n\n', 519 }, 520 ], 521 }, 522 ], 523}; 524 525// export 526export default experimentObject;