From 36d6ca564e2182a224218114ade45a09aade8d35 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Wed, 22 Jul 2020 18:59:12 +0200 Subject: [PATCH 1/5] set config for mh linter --- .travis.yml | 10 ++++++++-- miss_hit.cfg | 3 +++ tests/miss_hit.cfg | 3 +++ 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 miss_hit.cfg create mode 100644 tests/miss_hit.cfg diff --git a/.travis.yml b/.travis.yml index 8e1e427..d4a0a33 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,7 @@ before_install: - travis_retry sudo apt-get -y -qq update - travis_retry sudo apt-get -y install octave - travis_retry sudo apt-get -y install liboctave-dev + - cd .. && git clone https://github.com/florianschanda/miss_hit.git && export PATH=$PATH:`pwd`/miss_hit && cd CPP_PTB install: - octave $OCTFLAGS --eval "addpath (pwd); savepath ();" @@ -21,5 +22,10 @@ before_script: # Change current directory - cd tests -script: - - octave $OCTFLAGS --eval "results = runtests; assert(all(~[results.Failed]))" +jobs: + include: + - stage: "Tests and linter" + name: "Unit Tests" # names the first job + script: octave $OCTFLAGS --eval "results = runTests; assert(all(~[results.Failed]))" + - script: cd .. && mh_style.py `pwd` + name: "miss_hit linter" # names the second job \ No newline at end of file diff --git a/miss_hit.cfg b/miss_hit.cfg new file mode 100644 index 0000000..fce7fa4 --- /dev/null +++ b/miss_hit.cfg @@ -0,0 +1,3 @@ +line_length: 100 +regex_function_name: "[a-z]+(([A-Z]){1}[A-Za-z]+)*" +suppress_rule: "copyright_notice" \ No newline at end of file diff --git a/tests/miss_hit.cfg b/tests/miss_hit.cfg new file mode 100644 index 0000000..30aa3b8 --- /dev/null +++ b/tests/miss_hit.cfg @@ -0,0 +1,3 @@ +line_length: 100 +regex_function_name: "((test_[a-z]+)|[a-z]+)(([A-Z]){1}[A-Za-z]+)*" +suppress_rule: "copyright_notice" \ No newline at end of file From cb9f5cb38c9326864f8195a5eebf8c3d15b4db8c Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Wed, 22 Jul 2020 19:04:31 +0200 Subject: [PATCH 2/5] apply some linting --- CPP_getResponseDemo.m | 118 ++++++++++ README.md | 5 +- deg2Pix.m | 11 - degToPix.m | 11 + demos/CPP_wait4TriggerDemo.m | 16 +- demos/CPP_waitForTriggerDemo.m | 11 + drawFixationCross.m | 14 +- eyeTracker.m | 405 ++++++++++++++++----------------- pressSpace4me.m | 26 --- pressSpaceForMe.m | 17 ++ printCredits.m | 56 ++--- 11 files changed, 403 insertions(+), 287 deletions(-) create mode 100644 CPP_getResponseDemo.m delete mode 100644 deg2Pix.m create mode 100644 degToPix.m create mode 100644 demos/CPP_waitForTriggerDemo.m delete mode 100644 pressSpace4me.m create mode 100644 pressSpaceForMe.m diff --git a/CPP_getResponseDemo.m b/CPP_getResponseDemo.m new file mode 100644 index 0000000..b276cc2 --- /dev/null +++ b/CPP_getResponseDemo.m @@ -0,0 +1,118 @@ +%% Demo showing how to use the getResponse function + +% This small script shows how to use the getReponse function +% (a wrapper around the KbQueue function from PTB) + +% start with a clean slate +clear; clc; +if IsOctave + more off % for a better display experience +end + +%% set parameters + +% cfg.responseBox would be the device number of the device used by the participant to give his/her +% response: like the button box in the scanner or a separate keyboard for a behavioral experiment +% +% cfg.keyboard would be the device number of the keyboard on which the experimenter will type or +% press the keys necessary to start or abort the experiment. + +% cfg.responseBox and cfg.keyboard can be different or the same. + +% If you want to know the device number of all the keyboards and responses +% boxes connected to the computer you can use the following code. +% [cfg.keyboardNumbers, cfg.keyboardNames] = GetKeyboardIndices + +% Using empty vectors should work for linux when to select the "main" +% keyboard. You might have to try some other values for Windows. To +% assigne a specific keyboard input the kb assigned value (see README) +cfg.keyboard = []; +cfg.responseBox = []; + +% We set which keys are "valid", any keys other than those will be ignored +expParameters.responseKey = {}; + + +%% init + +% Keyboard +% Make sure keyboard mapping is the same on all supported operating systems +% Apple MacOS/X, MS-Windows and GNU/Linux: +KbName('UnifyKeyNames'); + + +% we ask PTB to tell us which keyboard devices are connected to the computer +[cfg.keyboardNumbers, cfg.keyboardNames] = GetKeyboardIndices; + +cfg.keyboardNumbers +cfg.keyboardNames + + +% Test that the keyboards are correctly configured +testKeyboards(cfg); + +% Give the time to the test key to be released and not listened +WaitSecs(1); + + +fprintf('\nDuring the next 5 seconds we will collect responses on the following keys: \n\n'); +if isempty(expParameters.responseKey) + fprintf('\nALL KEYS\n\n'); +else + for iKey=1:numel(expParameters.responseKey) + fprintf('\n%s', expParameters.responseKey{iKey}); + end + fprintf('\n\n'); +end + + + +%% Run demo + +% Create the keyboard queue to collect responses. +getResponse('init', cfg, expParameters, 1); + +% Start collecting responses for 5 seconds +% Each new key press is added to the queue of events recorded by KbQueue +startSecs = GetSecs(); +getResponse('start', cfg, expParameters, 1); + + + +% Here we wait for 5 seconds but are still collecting responses. +% So you could still be doing something else (presenting audio and visual stim) and +% still collect responses. +WaitSecs(5); + + + +% Check what keys were pressed (all of them) +responseEvents = getResponse('check', cfg, expParameters, 0); + +% The following line would only return key presses and not releases +% responseEvents = getResponse('check', cfg, expParameters, 1); + +% This can be used to flush the queue: empty all events that are still present in the queue +getResponse('flush', cfg, expParameters, 1); + +% If you wan to stop listening to key presses. +getResponse('stop', cfg, expParameters, 1); + + + + +%% Now we look what keys were pressed and when +for iEvent = 1:size(responseEvents, 1) + + if responseEvents(iEvent).pressed + eventType = 'pressed'; + else + eventType = 'released'; + end + + fprintf('%s was %s at time %.3f seconds\n', ... + responseEvents(iEvent).key_name, ... + eventType, ... + responseEvents(iEvent).onset - startSecs); + +end diff --git a/README.md b/README.md index 20e68a2..7c81498 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,11 @@ The exact version required for this to work but it is known to work with: ## Code guidestyle We use the `camelCase` to more easily differentiates our functions from the ones from PTB that use a `PascalCase`. +We use the following regular expression for function names: `[a-z]+(([A-Z]|[0-9]){1}[a-z]+)*`. -We keep the McCabe complexity as reported by the [check_my_code function](https://github.com/Remi-Gau/matlab_checkcode) below 15. +We keep the McCabe complexity as reported by the [check_my_code function](https://github.com/Remi-Gau/check_my_code) below 15. + +We use the [MISS_HIT linter](https://florianschanda.github.io/miss_hit/style_checker.html) to automatically fix some linting issues. ## How to install diff --git a/deg2Pix.m b/deg2Pix.m deleted file mode 100644 index e6f0bf1..0000000 --- a/deg2Pix.m +++ /dev/null @@ -1,11 +0,0 @@ -function structure = deg2Pix(fieldName, structure, cfg) -% For a given field value in degrees of visual angle in the structure, -% this computes its value in pixel using the pixel per degree value of the cfg structure -% and returns a structure with an additional field with Pix suffix holding that new value. - -deg = getfield( structure, fieldName); - -structure = setfield( structure, [fieldName 'Pix'], ... - floor(cfg.ppd * deg) ) ; - -end diff --git a/degToPix.m b/degToPix.m new file mode 100644 index 0000000..00d0367 --- /dev/null +++ b/degToPix.m @@ -0,0 +1,11 @@ +function structure = degToPix(fieldName, structure, cfg) + % For a given field value in degrees of visual angle in the structure, + % this computes its value in pixel using the pixel per degree value of the cfg structure + % and returns a structure with an additional field with Pix suffix holding that new value. + + deg = getfield(structure, fieldName); %#ok + + structure = setfield(structure, [fieldName 'Pix'], ... + floor(cfg.ppd * deg)) ; %#ok + +end diff --git a/demos/CPP_wait4TriggerDemo.m b/demos/CPP_wait4TriggerDemo.m index 64665fe..c3584ac 100644 --- a/demos/CPP_wait4TriggerDemo.m +++ b/demos/CPP_wait4TriggerDemo.m @@ -1,17 +1,11 @@ -% set up -cfg.testingDevice = 'scanner'; -cfg.numTriggers = 4; -cfg.triggerKey = 't'; +cd .. + +cfg.device = 'Scanner'; -% this field is not required but can be mentioned -cfg.MRI.repetitionTime = 3; +cfg.numTriggers = 4; -% add parent directory to the path (to make sure we can access the CPP_PTB -% functions) -addpath(fullfile(pwd, '..')) +cfg.triggerKey = 'space'; -% beginning of demo KbName('UnifyKeyNames'); -% press the key "t" to simulate triggers wait4Trigger(cfg) \ No newline at end of file diff --git a/demos/CPP_waitForTriggerDemo.m b/demos/CPP_waitForTriggerDemo.m new file mode 100644 index 0000000..f311c90 --- /dev/null +++ b/demos/CPP_waitForTriggerDemo.m @@ -0,0 +1,11 @@ +cd ..; + +cfg.device = 'Scanner'; + +cfg.numTriggers = 4; + +cfg.triggerKey = 'space'; + +KbName('UnifyKeyNames'); + +waitForTrigger(cfg); diff --git a/drawFixationCross.m b/drawFixationCross.m index 3c10953..96cde03 100644 --- a/drawFixationCross.m +++ b/drawFixationCross.m @@ -1,11 +1,11 @@ function drawFixationCross(cfg, expParameters, color) -% Define the parameters of the fixation cross in `cfg` and `expParameters` and this does the rest. + % Define the parameters of the fixation cross in `cfg` and `expParameters` and this does the rest. -Screen('DrawLines', ... - cfg.win, ... - cfg.allCoords, ... - expParameters.lineWidthPix, ... - color , ... - [cfg.center(1) cfg.center(2)], 1); + Screen('DrawLines', ... + cfg.win, ... + cfg.allCoords, ... + expParameters.lineWidthPix, ... + color, ... + [cfg.center(1) cfg.center(2)], 1); end diff --git a/eyeTracker.m b/eyeTracker.m index 57621df..1e148c2 100755 --- a/eyeTracker.m +++ b/eyeTracker.m @@ -1,219 +1,218 @@ -function [ el, edfFile ] = eyeTracker(input, cfg, expParameters, varargin) +function [el, edfFile] = eyeTracker(input, cfg, expParameters, varargin) -if ~cfg.eyeTracker + if ~cfg.eyeTracker - el = []; + el = []; -else + else - switch input + switch input - case 'Calibration' + case 'Calibration' - %% STEP 2 - % Provide Eyelink with details about the graphics environment - % and perform some initializations. The information is returned - % in a structure that also contains useful defaults - % and control codes (e.g. tracker state bit and Eyelink key values). - el = EyelinkInitDefaults(cfg.win); + %% STEP 2 + % Provide Eyelink with details about the graphics environment + % and perform some initializations. The information is returned + % in a structure that also contains useful defaults + % and control codes (e.g. tracker state bit and Eyelink key values). + el = EyelinkInitDefaults(cfg.win); - % calibration has silver background with black targets, sound and smaller - % targets - el.backgroundcolour = [ 192 192 192, (cfg.win)]; - el.msgfontcolour = BlackIndex(cfg.win); - el.calibrationtargetcolour = BlackIndex(cfg.win); - el.calibrationtargetsize = 1; - el.calibrationtargetwidth = 0.5; - el.displayCalResults = 1; + % calibration has silver background with black targets, sound and smaller + % targets + el.backgroundcolour = [192 192 192, (cfg.win)]; + el.msgfontcolour = BlackIndex(cfg.win); + el.calibrationtargetcolour = BlackIndex(cfg.win); + el.calibrationtargetsize = 1; + el.calibrationtargetwidth = 0.5; + el.displayCalResults = 1; - % call this function for changes to the calibration structure to take - % affect - EyelinkUpdateDefaults(el); + % call this function for changes to the calibration structure to take + % affect + EyelinkUpdateDefaults(el); - % STEP 3 - % Initialization of the connection with the Eyelink Gazetracker. - % exit program if this fails. + % STEP 3 + % Initialization of the connection with the Eyelink Gazetracker. + % exit program if this fails. - % make sure EL is initialized. - ELinit = Eyelink('Initialize'); - if ELinit~=0 - fprintf('Eyelink is not initialized, aborted.\n'); - Eyelink('Shutdown'); - Screen('CloseAll'); - return; - end - - % make sure we're still connected. - ELconnection = Eyelink('IsConnected'); - if ELconnection~=1 - fprintf('Eyelink is not connected, aborted.\n'); - Eyelink('Shutdown'); - Screen('CloseAll'); - return; - end - - % - if ~EyelinkInit(0, 1) - fprintf('Eyelink Init aborted.\n'); - return; - end - - % Open the edf file to write the data - edfFile = 'demo.edf'; - Eyelink('Openfile', edfFile); - - [el.v, el.vs] = Eyelink('GetTrackerVersion'); - fprintf('Running experiment on a ''%s'' tracker.\n', el.vs ); - - % make sure that we get gaze data from the Eyelink - Eyelink('Command', 'link_sample_data = LEFT,RIGHT,GAZE,AREA'); - - % STEP 4 - % SET UP TRACKER CONFIGURATION - % Setting the proper recording resolution, proper calibration type, - % as well as the data file content; - % Eyelink('command', 'add_file_preamble_text ''Recorded by EyelinkToolbox demo-experiment'''); - - % This command is crucial to map the gaze positions from the tracker to - % screen pixel positions to determine fixation - Eyelink('command','screen_pixel_coords = %ld %ld %ld %ld', 0, 0, 0, 0); - Eyelink('message', 'DISPLAY_COORDS %ld %ld %ld %ld', 0, 0, 0, 0); - - % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % - % DEFAULT CALIBRATION - % set calibration type. - Eyelink('command', 'calibration_type = HV5'); - - % you must send this command with value NO for custom calibration - % you must also reset it to YES for subsequent experiments - Eyelink('command', 'generate_default_targets = YES'); - % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % - - % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % - % % CUSTOM CALIBRATION - % % (SET MANUALLY THE DOTS COORDINATES, HERE FOR 6 DOTS) - % Eyelink('command', 'calibration_type = HV5'); - % % you must send this command with value NO for custom calibration - % % you must also reset it to YES for subsequent experiments - % Eyelink('command', 'generate_default_targets = NO'); - % - % % calibration and validation target locations - % [width, height]=Screen('WindowSize', screenNumber); - % Eyelink('command','calibration_samples = 6'); - % Eyelink('command','calibration_sequence = 0,1,2,3,4,5'); - % Eyelink('command','calibration_targets = %d,%d %d,%d %d,%d %d,%d %d,%d',... - % 640,512, ... %width/2,height/2 - % 640,102, ... %width/2,height*0.1 - % 640,614, ... %width/2,height*0.6 - % 128,341, ... %width*0.1,height*1/3 - % 1152,341 ); %width-width*0.1,height*1/3 - % - % Eyelink('command','validation_samples = 5'); - % Eyelink('command','validation_sequence = 0,1,2,3,4,5'); - % Eyelink('command','validation_targets = %d,%d %d,%d %d,%d %d,%d %d,%d',... - % 640,512, ... %width/2,height/2 - % 640,102, ... %width/2,height*0.1 - % 640,614, ... %width/2,height*0.6 - % 128,341, ... %width*0.1,height*1/3 - % 1152,341 ); %width-width*0.1,height*1/3 - % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % - - % % set parser (conservative saccade thresholds) - % Eyelink('command', 'saccade_velocity_threshold = 35'); - % Eyelink('command', 'saccade_acceleration_threshold = 9500'); - - % set EDF file contents (not clear what this lines are used for) - el.vsn = regexp(el.vs,'\d','match'); % wont work on EL - - % enter Eyetracker camera setup mode, calibration and validation - EyelinkDoTrackerSetup(el); - - % % % do a final check of calibration using driftcorrection - % % % You have to hit esc before return. - % % EyelinkDoDriftCorrection(el); - % - % % % do a final check of calibration using driftcorrection - % % success=EyelinkDoDriftCorrection(el); - % % if success~=1 - % % Eyelink('shutdown'); - % % Screen('CloseAll'); - % % return; - % % end - - % Go back to black screen - Screen('FillRect', cfg.win, [0 0 0]); - Screen('Flip', cfg.win); - - case 'StartRecording' - - % STEP 5 - % EyeLink Start recording the block - Eyelink('Command', 'set_idle_mode'); - WaitSecs(0.05); - Eyelink('StartRecording'); - % % here to tag the recording, in the past caused delays during the - % % presentation so I avoided to use it - % Eyelink('message',['TRIALID ',num2str(blocks),'_startTrial']); - - % check recording status, stop display if error - checkrec=Eyelink('checkrecording'); - if(checkrec~=0) - fprintf('\nEyelink is not recording.\n\n'); - Eyelink('Shutdown'); - Screen('CloseAll'); - return; - end - - % record a few samples before we actually start displaying - % otherwise you may lose a few msec of data - WaitSecs(0.1); - - % HERE START THE STIMALTION OF THE BLOCK - % to mark the beginning of the trial - Eyelink('Message', 'SYNCTIME'); - - case 'StopRecordings' - - % STEP 8 - % finish up: stop recording eye-movements, - % EyeLink Stop recording the block - Eyelink('Message', 'BLANK_SCREEN'); - % adds 100 msec of data to catch final events - WaitSecs(0.1); - % close graphics window, close data file and shut down tracker - Eyelink('StopRecording'); - - case 'Shutdown' - - edfFileName = expParameters.fileName.eyetracker; - edfFile = 'demo.edf'; - - % STEP 6 - % At the end of the experiment, save the edf file and shut down connection - % with Eyelink - - Eyelink('Command', 'set_idle_mode'); - WaitSecs(0.5); - Eyelink('CloseFile'); - - % download data file - try - fprintf('Receiving data file ''%s''\n', edfFileName ); - status=Eyelink('ReceiveFile','',[expParameters.outputDir, filesep, 'eyetracker', filesep, edfFileName]); - if status > 0 - fprintf('ReceiveFile status %d\n', status); + % make sure EL is initialized. + ELinit = Eyelink('Initialize'); + if ELinit ~= 0 + fprintf('Eyelink is not initialized, aborted.\n'); + Eyelink('Shutdown'); + Screen('CloseAll'); + return + end + + % make sure we're still connected. + ELconnection = Eyelink('IsConnected'); + if ELconnection ~= 1 + fprintf('Eyelink is not connected, aborted.\n'); + Eyelink('Shutdown'); + Screen('CloseAll'); + return + end + + % + if ~EyelinkInit(0, 1) + fprintf('Eyelink Init aborted.\n'); + return + end + + % Open the edf file to write the data + edfFile = 'demo.edf'; + Eyelink('Openfile', edfFile); + + [el.v, el.vs] = Eyelink('GetTrackerVersion'); + fprintf('Running experiment on a ''%s'' tracker.\n', el.vs); + + % make sure that we get gaze data from the Eyelink + Eyelink('Command', 'link_sample_data = LEFT,RIGHT,GAZE,AREA'); + + % STEP 4 + % SET UP TRACKER CONFIGURATION + % Setting the proper recording resolution, proper calibration type, + % as well as the data file content; + % Eyelink('command', 'add_file_preamble_text ''Recorded by EyelinkToolbox demo-experiment'''); + + % This command is crucial to map the gaze positions from the tracker to + % screen pixel positions to determine fixation + Eyelink('command', 'screen_pixel_coords = %ld %ld %ld %ld', 0, 0, 0, 0); + Eyelink('message', 'DISPLAY_COORDS %ld %ld %ld %ld', 0, 0, 0, 0); + + % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % + % DEFAULT CALIBRATION + % set calibration type. + Eyelink('command', 'calibration_type = HV5'); + + % you must send this command with value NO for custom calibration + % you must also reset it to YES for subsequent experiments + Eyelink('command', 'generate_default_targets = YES'); + % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % + + % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % + % % CUSTOM CALIBRATION + % % (SET MANUALLY THE DOTS COORDINATES, HERE FOR 6 DOTS) + % Eyelink('command', 'calibration_type = HV5'); + % % you must send this command with value NO for custom calibration + % % you must also reset it to YES for subsequent experiments + % Eyelink('command', 'generate_default_targets = NO'); + % + % % calibration and validation target locations + % [width, height]=Screen('WindowSize', screenNumber); + % Eyelink('command','calibration_samples = 6'); + % Eyelink('command','calibration_sequence = 0,1,2,3,4,5'); + % Eyelink('command','calibration_targets = %d,%d %d,%d %d,%d %d,%d %d,%d',... + % 640,512, ... %width/2,height/2 + % 640,102, ... %width/2,height*0.1 + % 640,614, ... %width/2,height*0.6 + % 128,341, ... %width*0.1,height*1/3 + % 1152,341 ); %width-width*0.1,height*1/3 + % + % Eyelink('command','validation_samples = 5'); + % Eyelink('command','validation_sequence = 0,1,2,3,4,5'); + % Eyelink('command','validation_targets = %d,%d %d,%d %d,%d %d,%d %d,%d',... + % 640,512, ... %width/2,height/2 + % 640,102, ... %width/2,height*0.1 + % 640,614, ... %width/2,height*0.6 + % 128,341, ... %width*0.1,height*1/3 + % 1152,341 ); %width-width*0.1,height*1/3 + % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % + + % % set parser (conservative saccade thresholds) + % Eyelink('command', 'saccade_velocity_threshold = 35'); + % Eyelink('command', 'saccade_acceleration_threshold = 9500'); + + % set EDF file contents (not clear what this lines are used for) + el.vsn = regexp(el.vs, '\d', 'match'); % wont work on EL + + % enter Eyetracker camera setup mode, calibration and validation + EyelinkDoTrackerSetup(el); + + % % % do a final check of calibration using driftcorrection + % % % You have to hit esc before return. + % % EyelinkDoDriftCorrection(el); + % + % % % do a final check of calibration using driftcorrection + % % success=EyelinkDoDriftCorrection(el); + % % if success~=1 + % % Eyelink('shutdown'); + % % Screen('CloseAll'); + % % return; + % % end + + % Go back to black screen + Screen('FillRect', cfg.win, [0 0 0]); + Screen('Flip', cfg.win); + + case 'StartRecording' + + % STEP 5 + % EyeLink Start recording the block + Eyelink('Command', 'set_idle_mode'); + WaitSecs(0.05); + Eyelink('StartRecording'); + % % here to tag the recording, in the past caused delays during the + % % presentation so I avoided to use it + % Eyelink('message',['TRIALID ',num2str(blocks),'_startTrial']); + + % check recording status, stop display if error + checkrec = Eyelink('checkrecording'); + if checkrec ~= 0 + fprintf('\nEyelink is not recording.\n\n'); + Eyelink('Shutdown'); + Screen('CloseAll'); + return end - if 2==exist([expParameters.outputDir, filesep, 'eyetracker', filesep, edfFileName], 'file') - fprintf('Data file ''%s'' can be found in ''%s''\n', edfFileName, [expParameters.outputDir, filesep, 'eyetracker', filesep] ); + + % record a few samples before we actually start displaying + % otherwise you may lose a few msec of data + WaitSecs(0.1); + + % HERE START THE STIMALTION OF THE BLOCK + % to mark the beginning of the trial + Eyelink('Message', 'SYNCTIME'); + + case 'StopRecordings' + + % STEP 8 + % finish up: stop recording eye-movements, + % EyeLink Stop recording the block + Eyelink('Message', 'BLANK_SCREEN'); + % adds 100 msec of data to catch final events + WaitSecs(0.1); + % close graphics window, close data file and shut down tracker + Eyelink('StopRecording'); + + case 'Shutdown' + + edfFileName = expParameters.fileName.eyetracker; + edfFile = 'demo.edf'; + + % STEP 6 + % At the end of the experiment, save the edf file and shut down connection + % with Eyelink + + Eyelink('Command', 'set_idle_mode'); + WaitSecs(0.5); + Eyelink('CloseFile'); + + % download data file + try + fprintf('Receiving data file ''%s''\n', edfFileName); + status = Eyelink('ReceiveFile', '', [expParameters.outputDir, filesep, 'eyetracker', filesep, edfFileName]); + if status > 0 + fprintf('ReceiveFile status %d\n', status); + end + if 2 == exist([expParameters.outputDir, filesep, 'eyetracker', filesep, edfFileName], 'file') + fprintf('Data file ''%s'' can be found in ''%s''\n', edfFileName, [expParameters.outputDir, filesep, 'eyetracker', filesep]); + end + catch + fprintf('Problem receiving data file ''%s''\n', edfFileName); end - catch - fprintf('Problem receiving data file ''%s''\n', edfFileName ); - end - Eyelink('shutdown'); - Screen('CloseAll'); + Eyelink('shutdown'); + Screen('CloseAll'); + end end - -end diff --git a/pressSpace4me.m b/pressSpace4me.m deleted file mode 100644 index 0ecfd9c..0000000 --- a/pressSpace4me.m +++ /dev/null @@ -1,26 +0,0 @@ -function pressSpace4me(deviceNumber) -% Use that to stop your script and only restart when the space bar is pressed. -% This is a good way to have a final checkpoint before starting and letting the -% user or the experimenter press the space bar to start. -% When no deviceNumber is set then it will check the default device - -if nargin < 1 || isempty(deviceNumber) - deviceNumber = -1; -end - -fprintf('\nPress space to continue.\n'); - -while 1 - - % check keyboard very 100 ms - WaitSecs(0.1); - - [~, keyCode, ~] = KbWait(deviceNumber); - if strcmp(KbName(find(keyCode)), 'space') - fprintf('Starting the experiment...\n'); - break - end - -end - -end diff --git a/pressSpaceForMe.m b/pressSpaceForMe.m new file mode 100644 index 0000000..f23f548 --- /dev/null +++ b/pressSpaceForMe.m @@ -0,0 +1,17 @@ +function pressSpaceForMe + % Use that to stop your script and only restart when the space bar is pressed. + + fprintf('\nPress space to continue.\n'); + + while 1 + + WaitSecs(0.1); + + [~, keyCode, ~] = KbWait(-1); + + if strcmp(KbName(find(keyCode)), 'space') + fprintf('starting the experiment...\n'); + break + end + + end diff --git a/printCredits.m b/printCredits.m index 4399399..3e4e9cf 100644 --- a/printCredits.m +++ b/printCredits.m @@ -1,42 +1,42 @@ function printCredits() -version = '0.0.1'; + version = '0.0.1'; -contributors = {... - 'Rémi Gau', ... - 'Marco Barilari'}; + contributors = { ... + 'Rémi Gau', ... + 'Marco Barilari', ... + 'Ceren Battal'}; -% DOI_URL = 'https://doi.org/10.5281/zenodo.3554331.'; + % DOI_URL = 'https://doi.org/10.5281/zenodo.3554331.'; -repoURL = 'https://github.com/cpp-lln-lab/CPP_PTB'; + repoURL = 'https://github.com/cpp-lln-lab/CPP_PTB'; -disp( '____________________________________________________________________________________________'); -disp( '____________________________________________________________________________________________'); -disp( ' '); -disp( ' __ ____ ____ ____ _____ _ '); -disp( ' / _)( _ \( _ \ ( _ \ |_ _| | ) '); -disp( '( (_ )___/ )___/ )___/ | | | \ '); -disp( ' \__)(__) (__) (__) |_| |__)'); -disp( ' '); + disp('________________________________________'); + disp('________________________________________'); + disp(' '); + disp(' __ ____ ____ ____ _____ _ '); + disp(' / _)( _ \( _ \ ( _ \ |_ _| | ) '); + disp('( (_ )___/ )___/ )___/ | | | \ '); + disp(' \__)(__) (__) (__) |_| |__)'); + disp(' '); -splash = 'Thank you for using the CPP PTB - version %s. '; -fprintf(splash, version) -fprintf('\n\n'); + splash = 'Thank you for using the CPP PTB - version %s. '; + fprintf(splash, version); + fprintf('\n\n'); -fprintf('Current list of contributors includes\n') -for iCont = 1:numel(contributors) - fprintf(' %s\n', contributors{iCont}) -end -fprintf('\b\n\n') - -% fprintf('Please cite using the following DOI: \n %s\n\n', DOI_URL) + fprintf('Current list of contributors includes\n'); + for iCont = 1:numel(contributors) + fprintf(' %s\n', contributors{iCont}); + end + fprintf('\b\n\n'); -fprintf('For bug report, suggestions for improvements or contributions see our github repo: \n %s\n\n', repoURL) + % fprintf('Please cite using the following DOI: \n %s\n\n', DOI_URL) + fprintf('For bug report, suggestions or contributions see our repo: \n %s\n\n', repoURL); -disp( '____________________________________________________________________________________________'); -disp( '____________________________________________________________________________________________'); + disp('________________________________________'); + disp('________________________________________'); -fprintf('\n\n') + fprintf('\n\n'); end From f19e45ec241fd4d0ff45c25a8a69e2459c9074e5 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Wed, 22 Jul 2020 19:07:23 +0200 Subject: [PATCH 3/5] mh autofix --- CPP_getResponseDemo.m | 24 +-- checkAbort.m | 60 +++--- checkDependencies.m | 75 ++++--- cleanUp.m | 37 ++-- demos/CPP_checkAbortDemo.m | 17 +- demos/CPP_getResponseDemo.m | 109 +++++----- demos/CPP_pressSpace4meDemo.m | 4 +- demos/CPP_wait4TriggerDemo.m | 6 +- devSandbox.m | 340 ++++++++++++++++---------------- errorAbort.m | 6 +- errorAbortGetReponse.m | 6 +- errorRestrictedKeysGetReponse.m | 8 +- getResponse.m | 337 ++++++++++++++++--------------- initPTB.m | 299 ++++++++++++++-------------- setDefaultsPTB.m | 113 ++++++----- testKeyboards.m | 83 ++++---- tests/getResponseTest.m | 49 +++-- tests/runtests.m | 92 ++++----- tests/test_setDefaultsPTB.m | 16 +- wait4Trigger.m | 130 ++++++------ 20 files changed, 876 insertions(+), 935 deletions(-) diff --git a/CPP_getResponseDemo.m b/CPP_getResponseDemo.m index b276cc2..8c181a9 100644 --- a/CPP_getResponseDemo.m +++ b/CPP_getResponseDemo.m @@ -4,9 +4,10 @@ % (a wrapper around the KbQueue function from PTB) % start with a clean slate -clear; clc; +clear; +clc; if IsOctave - more off % for a better display experience + more off; % for a better display experience end %% set parameters @@ -32,7 +33,6 @@ % We set which keys are "valid", any keys other than those will be ignored expParameters.responseKey = {}; - %% init % Keyboard @@ -40,13 +40,11 @@ % Apple MacOS/X, MS-Windows and GNU/Linux: KbName('UnifyKeyNames'); - % we ask PTB to tell us which keyboard devices are connected to the computer [cfg.keyboardNumbers, cfg.keyboardNames] = GetKeyboardIndices; -cfg.keyboardNumbers -cfg.keyboardNames - +cfg.keyboardNumbers; +cfg.keyboardNames; % Test that the keyboards are correctly configured testKeyboards(cfg); @@ -54,19 +52,16 @@ % Give the time to the test key to be released and not listened WaitSecs(1); - fprintf('\nDuring the next 5 seconds we will collect responses on the following keys: \n\n'); if isempty(expParameters.responseKey) fprintf('\nALL KEYS\n\n'); else - for iKey=1:numel(expParameters.responseKey) + for iKey = 1:numel(expParameters.responseKey) fprintf('\n%s', expParameters.responseKey{iKey}); end fprintf('\n\n'); end - - %% Run demo % Create the keyboard queue to collect responses. @@ -77,15 +72,11 @@ startSecs = GetSecs(); getResponse('start', cfg, expParameters, 1); - - % Here we wait for 5 seconds but are still collecting responses. % So you could still be doing something else (presenting audio and visual stim) and % still collect responses. WaitSecs(5); - - % Check what keys were pressed (all of them) responseEvents = getResponse('check', cfg, expParameters, 0); @@ -98,9 +89,6 @@ % If you wan to stop listening to key presses. getResponse('stop', cfg, expParameters, 1); - - - %% Now we look what keys were pressed and when for iEvent = 1:size(responseEvents, 1) diff --git a/checkAbort.m b/checkAbort.m index cdb7dd7..0fde93e 100644 --- a/checkAbort.m +++ b/checkAbort.m @@ -1,36 +1,36 @@ function checkAbort(cfg, deviceNumber) -% Check for experiment abortion from operator -% When no deviceNumber is set then it will check the default device -% When an abort key s detected this will set a global variable and throw a -% specific error that can then be catched. -% -% Maint script -% try -% % Your awesome experiment -% catch ME % when something goes wrong -% switch ME.identifier -% case 'checkAbort:abortRequested' -% % stuff to do when an abort is requested (save data...) -% otherwise -% % stuff to do otherwise -% rethrow(ME) % display the error -% end -% end + % Check for experiment abortion from operator + % When no deviceNumber is set then it will check the default device + % When an abort key s detected this will set a global variable and throw a + % specific error that can then be catched. + % + % Maint script + % try + % % Your awesome experiment + % catch ME % when something goes wrong + % switch ME.identifier + % case 'checkAbort:abortRequested' + % % stuff to do when an abort is requested (save data...) + % otherwise + % % stuff to do otherwise + % rethrow(ME) % display the error + % end + % end -if nargin < 1 || isempty(cfg) - error('I need at least one input.') -end + if nargin < 1 || isempty(cfg) + error('I need at least one input.'); + end -if nargin < 2 || isempty(deviceNumber) - deviceNumber = -1; -end + if nargin < 2 || isempty(deviceNumber) + deviceNumber = -1; + end -[keyIsDown, ~, keyCode] = KbCheck(deviceNumber); + [keyIsDown, ~, keyCode] = KbCheck(deviceNumber); -if keyIsDown && keyCode(KbName(cfg.keyboard.escapeKey)) - - errorAbort(); - -end + if keyIsDown && keyCode(KbName(cfg.keyboard.escapeKey)) -end \ No newline at end of file + errorAbort(); + + end + +end diff --git a/checkDependencies.m b/checkDependencies.m index 3d4e4e9..4d798a8 100644 --- a/checkDependencies.m +++ b/checkDependencies.m @@ -1,43 +1,42 @@ function checkDependencies() -% Checks that the right dependencies are installed. - -printCredits() - -PTB.major = 3; -PTB.minor = 0; -PTB.point = 14; - -fprintf('Checking dependencies\n') - -% check ptb version -try - - [~, versionStruc] = PsychtoolboxVersion; - - fprintf(' Using PTB %i.%i.%i\n', ... - versionStruc.major, ... - versionStruc.minor, ... - versionStruc.point) - - if any( [... - versionStruc.major < PTB.major, ... - versionStruc.minor < PTB.minor, ... - versionStruc.point < PTB.point, ... - ]) - - str = sprintf('%s %i.%i.%i %s.\n%s', ... - 'The current version PTB version is not', ... - PTB.major, ... - PTB.minor, ... - PTB.point, ... - 'In case of problems (e.g json file related) consider updating.'); - warning(str); %#ok<*SPWRN> - end -catch - error('Failed to check the PTB version: Are you sure that PTB is in the matlab path?') -end + % Checks that the right dependencies are installed. + + printCredits(); + + PTB.major = 3; + PTB.minor = 0; + PTB.point = 14; + + fprintf('Checking dependencies\n'); + % check ptb version + try + + [~, versionStruc] = PsychtoolboxVersion; + + fprintf(' Using PTB %i.%i.%i\n', ... + versionStruc.major, ... + versionStruc.minor, ... + versionStruc.point); + + if any([ ... + versionStruc.major < PTB.major, ... + versionStruc.minor < PTB.minor, ... + versionStruc.point < PTB.point, ... + ]) + + str = sprintf('%s %i.%i.%i %s.\n%s', ... + 'The current version PTB version is not', ... + PTB.major, ... + PTB.minor, ... + PTB.point, ... + 'In case of problems (e.g json file related) consider updating.'); + warning(str); %#ok<*SPWRN> + end + catch + error('Failed to check the PTB version: Are you sure that PTB is in the matlab path?'); + end -fprintf(' We got all we need. Let us get to work.\n') + fprintf(' We got all we need. Let us get to work.\n'); end diff --git a/cleanUp.m b/cleanUp.m index 5befdd7..3560d56 100644 --- a/cleanUp.m +++ b/cleanUp.m @@ -1,30 +1,29 @@ function cleanUp -% A wrapper function to close all windows, ports, show mouse cursor, close keyboard queues -% and give access back to the keyboards. + % A wrapper function to close all windows, ports, show mouse cursor, close keyboard queues + % and give access back to the keyboards. -WaitSecs(0.5); + WaitSecs(0.5); -Priority(0); + Priority(0); -ListenChar(0); -KbQueueRelease(); + ListenChar(0); + KbQueueRelease(); -ShowCursor + ShowCursor; -% Screen Close All -sca; + % Screen Close All + sca; -% Close Psychportaudio if open -if PsychPortAudio('GetOpenDeviceCount') ~= 0 - PsychPortAudio('Close'); -end - -if ~ismac - % remove PsychDebugWindowConfiguration - clear Screen -end + % Close Psychportaudio if open + if PsychPortAudio('GetOpenDeviceCount') ~= 0 + PsychPortAudio('Close'); + end -close all + if ~ismac + % remove PsychDebugWindowConfiguration + clear Screen; + end + close all; end diff --git a/demos/CPP_checkAbortDemo.m b/demos/CPP_checkAbortDemo.m index 7808196..4d79b65 100644 --- a/demos/CPP_checkAbortDemo.m +++ b/demos/CPP_checkAbortDemo.m @@ -1,6 +1,6 @@ % add parent directory to the path (to make sure we can access the CPP_PTB % functions) -addpath(fullfile(pwd, '..')) +addpath(fullfile(pwd, '..')); % set up cfg.keyboard.escapeKey = 'ESCAPE'; @@ -8,24 +8,23 @@ % beginning of demo KbName('UnifyKeyNames'); - try % stay in the loop until the escape key is pressed while GetSecs < Inf - checkAbort(cfg) + checkAbort(cfg); end catch ME - + switch ME.identifier case 'checkAbort:abortRequested' - warning('You pressed the escape key: will try to fail gracefully.') - fprintf('\nWe did catch your abort signal.\n') + warning('You pressed the escape key: will try to fail gracefully.'); + fprintf('\nWe did catch your abort signal.\n'); otherwise - rethrow(ME) % display other errors + rethrow(ME); % display other errors end - -end \ No newline at end of file + +end diff --git a/demos/CPP_getResponseDemo.m b/demos/CPP_getResponseDemo.m index f6c2c0f..6c4dc66 100644 --- a/demos/CPP_getResponseDemo.m +++ b/demos/CPP_getResponseDemo.m @@ -3,26 +3,25 @@ % This small script shows how to use the getReponse function % add parent directory to matlab path (so we can access the CPP_PTB functions) -addpath(fullfile(pwd, '..')) - +addpath(fullfile(pwd, '..')); %% start with a clean slate -clear; +clear; clc; if IsOctave - more off % for a better display experience + more off; % for a better display experience end % use the default set up (use main keyboard and use the ESCAPE key to abort) cfg = setDefaultsPTB; % show the default option -disp(cfg.keyboard) +disp(cfg.keyboard); %% set parameters -% Change the values set by defaults (for more info about the keyboard see the doc) +% Change the values set by defaults (for more info about the keyboard see the doc) % cfg.keyboard.keyboard = ?? % cfg.keyboard.responseBox = ?? % cfg.keyboard.escapeKey = ?? @@ -39,10 +38,9 @@ cfg.keyboard.responseKey = {'a', 'b'}; % This would make sure that you listen to presses of the escape key -cfg.keyboard.responseKey{end+1} = cfg.keyboard.escapeKey; - +cfg.keyboard.responseKey{end + 1} = cfg.keyboard.escapeKey; -%% Final checks +%% Final checks % Make sure keyboard mapping is the same on all supported operating systems KbName('UnifyKeyNames'); @@ -57,78 +55,73 @@ if isempty(cfg.keyboard.responseKey) fprintf('\nALL KEYS\n\n'); else - for iKey=1:numel(cfg.keyboard.responseKey) + for iKey = 1:numel(cfg.keyboard.responseKey) fprintf('\n%s', cfg.keyboard.responseKey{iKey}); end fprintf('\n\n'); end - %% Run demo -try - -% Create the keyboard queue to collect responses. -getResponse('init', deviceNumber, cfg); +try -% Start collecting responses for 5 seconds -% Each new key press is added to the queue of events recorded by KbQueue -startSecs = GetSecs(); -getResponse('start', deviceNumber); + % Create the keyboard queue to collect responses. + getResponse('init', deviceNumber, cfg); -% Here we wait for 5 seconds but are still collecting responses. -% So you could still be doing something else (presenting audio and visual stim) and -% still collect responses. -WaitSecs(5); + % Start collecting responses for 5 seconds + % Each new key press is added to the queue of events recorded by KbQueue + startSecs = GetSecs(); + getResponse('start', deviceNumber); -% Check what keys were pressed (all of them) -% If the escapeKey was pressed at any time, it will only abort when you -% getResponse('check') -responseEvents = getResponse('check', deviceNumber, cfg, getOnlyPress); + % Here we wait for 5 seconds but are still collecting responses. + % So you could still be doing something else (presenting audio and visual stim) and + % still collect responses. + WaitSecs(5); -% This can be used to flush the queue: empty all events that are still present in the queue -getResponse('flush', deviceNumber); + % Check what keys were pressed (all of them) + % If the escapeKey was pressed at any time, it will only abort when you + % getResponse('check') + responseEvents = getResponse('check', deviceNumber, cfg, getOnlyPress); -% If you wan to stop listening to key presses. You could start listening again -% later by calling: getResponse('start', deviceNumber) -getResponse('stop', deviceNumber); + % This can be used to flush the queue: empty all events that are still present in the queue + getResponse('flush', deviceNumber); -% If you wan to destroyt the queue: you would have to initialize it again -getResponse('release', deviceNumber); + % If you wan to stop listening to key presses. You could start listening again + % later by calling: getResponse('start', deviceNumber) + getResponse('stop', deviceNumber); + % If you wan to destroyt the queue: you would have to initialize it again + getResponse('release', deviceNumber); -%% Now we look what keys were pressed and when -for iEvent = 1:size(responseEvents, 1) + %% Now we look what keys were pressed and when + for iEvent = 1:size(responseEvents, 1) - if responseEvents(iEvent).pressed - eventType = 'pressed'; - else - eventType = 'released'; - end + if responseEvents(iEvent).pressed + eventType = 'pressed'; + else + eventType = 'released'; + end - fprintf('\n %s was %s at time %.3f seconds\n', ... - responseEvents(iEvent).keyName, ... - eventType, ... - responseEvents(iEvent).onset - startSecs); - -end + fprintf('\n %s was %s at time %.3f seconds\n', ... + responseEvents(iEvent).keyName, ... + eventType, ... + responseEvents(iEvent).onset - startSecs); + end catch ME - - getResponse('release', deviceNumber) - + + getResponse('release', deviceNumber); + switch ME.identifier - + case 'getResponse:abortRequested' - warning('You pressed the escape key: will try to fail gracefully.') - - fprintf('\nWe did catch your abort signal.\n') + warning('You pressed the escape key: will try to fail gracefully.'); + + fprintf('\nWe did catch your abort signal.\n'); otherwise - rethrow(ME) % display other errors - + rethrow(ME); % display other errors + end end - - diff --git a/demos/CPP_pressSpace4meDemo.m b/demos/CPP_pressSpace4meDemo.m index 90784c6..60e07d5 100644 --- a/demos/CPP_pressSpace4meDemo.m +++ b/demos/CPP_pressSpace4meDemo.m @@ -1,9 +1,9 @@ % add parent directory to the path (to make sure we can access the CPP_PTB % functions) -addpath(fullfile(pwd, '..')) +addpath(fullfile(pwd, '..')); % beginning of demo KbName('UnifyKeyNames'); % press the key "space" to "start" the experiment -pressSpace4me \ No newline at end of file +pressSpace4me; diff --git a/demos/CPP_wait4TriggerDemo.m b/demos/CPP_wait4TriggerDemo.m index c3584ac..55d0098 100644 --- a/demos/CPP_wait4TriggerDemo.m +++ b/demos/CPP_wait4TriggerDemo.m @@ -1,11 +1,11 @@ -cd .. +cd ..; cfg.device = 'Scanner'; -cfg.numTriggers = 4; +cfg.numTriggers = 4; cfg.triggerKey = 'space'; KbName('UnifyKeyNames'); -wait4Trigger(cfg) \ No newline at end of file +wait4Trigger(cfg); diff --git a/devSandbox.m b/devSandbox.m index e488d3f..cc6642f 100644 --- a/devSandbox.m +++ b/devSandbox.m @@ -1,241 +1,233 @@ function devSandbox -% This script is a stand-alone function that can be useful as a sandbox to -% develop the PTB audio/visual stimulation of your experiment. No input/output -% required. -% -% Here, a tutorial from https://peterscarfe.com/contrastgratingdemo.html is -% provided for illustrative purpose (notice that some variable names are updated -% to our code style). For your use, you will delete that part. -% -% It is composed of two parts: -% - a fixed structure that will initialize and close PTB in 'debug mode' -% (`PsychDebugWindowConfiguration`, `SkipSyncTests`) -% - the actual sandbox where to set your dynamic variables (the stimulation -% parameters) and the 'playground' where to develop the stimulation code -% -% When you are happy with it, ideally you will move the vars in `setParameters.m` -% and the stimulation code in a separate function in `my-experiment-folder/subfun`. -% The code style and variable names are the same used in `cpp-lln-lab/CPP_PTB` -% github repo, therefore it should be easy to move everything in your experiment -% scripts (see the template that is annexed in `cpp-lln-lab/CPP_PTB`) + % This script is a stand-alone function that can be useful as a sandbox to + % develop the PTB audio/visual stimulation of your experiment. No input/output + % required. + % + % Here, a tutorial from https://peterscarfe.com/contrastgratingdemo.html is + % provided for illustrative purpose (notice that some variable names are updated + % to our code style). For your use, you will delete that part. + % + % It is composed of two parts: + % - a fixed structure that will initialize and close PTB in 'debug mode' + % (`PsychDebugWindowConfiguration`, `SkipSyncTests`) + % - the actual sandbox where to set your dynamic variables (the stimulation + % parameters) and the 'playground' where to develop the stimulation code + % + % When you are happy with it, ideally you will move the vars in `setParameters.m` + % and the stimulation code in a separate function in `my-experiment-folder/subfun`. + % The code style and variable names are the same used in `cpp-lln-lab/CPP_PTB` + % github repo, therefore it should be easy to move everything in your experiment + % scripts (see the template that is annexed in `cpp-lln-lab/CPP_PTB`) + + % Init the structure that will contain PTB setup + cfg = struct; + + % Set some colors to be choosen as background + cfg.white = [255 255 255]; + cfg.black = [0 0 0]; + cfg.grey = mean([cfg.black; cfg.white]); + + % Set the PTB window background manually + cfg.backgroundColor = cfg.grey; + + % Init PTB, see the Sub-Functions below + cfg = devSandbox_initPTB(cfg); + + %% + % ------------------------------------------------------------------------- + % -------------------------- SET YOUR VARS HERE --------------------------- + % ------------------------------------------------------------------------- + + % Define black and white + white = WhiteIndex(cfg.screen); + grey = white / 2; + inc = white - grey; + + % Grating size in pixels + gratingSizePix = 600; -% Init the structure that will contain PTB setup -cfg = struct; + % Grating frequency in cycles / pixel + freqCyclesPerPix = 0.01; -% Set some colors to be choosen as background -cfg.white = [255 255 255]; -cfg.black = [ 0 0 0 ]; -cfg.grey = mean([cfg.black; cfg.white]); + % Drift speed cycles per second + cyclesPerSecond = 1; -% Set the PTB window background manually -cfg.backgroundColor = cfg.grey; + % Contrast for our contrast modulation mask: 0 = mask has no effect, 1 = mask + % will at its strongest part be completely opaque frameCounter.e. 0 and 100% contrast + % respectively + contrast = 0.8; -% Init PTB, see the Sub-Functions below -cfg = devSandbox_initPTB(cfg); + % We set PTB to wait one frame before re-drawing + waitframes = 1; -%% -% ------------------------------------------------------------------------- -% -------------------------- SET YOUR VARS HERE --------------------------- -% ------------------------------------------------------------------------- + % ------------------------------------------------------------------------- + %% -% Define black and white -white = WhiteIndex(cfg.screen); -grey = white / 2; -inc = white - grey; + % Catch the error and restore your computer for debugging + try -% Grating size in pixels -gratingSizePix = 600; + %% + % ------------------------------------------------------------------------- + % ------------------------------ PLAYGROUND ------------------------------- + % ------------------------------------------------------------------------- + % Define Half-Size of the grating image. + textureSize = gratingSizePix / 2; -% Grating frequency in cycles / pixel -freqCyclesPerPix = 0.01; + % First we compute pixels per cycle rounded to the nearest pixel + pixPerCycle = ceil(1 / freqCyclesPerPix); -% Drift speed cycles per second -cyclesPerSecond = 1; + % Frequency in Radians + freqRad = freqCyclesPerPix * 2 * pi; -% Contrast for our contrast modulation mask: 0 = mask has no effect, 1 = mask -% will at its strongest part be completely opaque frameCounter.e. 0 and 100% contrast -% respectively -contrast = 0.8; + % This is the visible size of the grating + visibleSize = 2 * textureSize + 1; -% We set PTB to wait one frame before re-drawing -waitframes = 1; + % Define our grating. Note it is only 1 pixel high. PTB will make it a full + % grating upon drawing + x = meshgrid(-textureSize:textureSize + pixPerCycle, 1); + grating = grey * cos(freqRad * x) + inc; -% ------------------------------------------------------------------------- -%% + % Make a two layer mask filled with the background colour + mask = ones(1, numel(x), 2) * grey; -% Catch the error and restore your computer for debugging -try + % Place the grating in the 'alpha' channel of the mask + mask(:, :, 2) = grating .* contrast; -%% -% ------------------------------------------------------------------------- -% ------------------------------ PLAYGROUND ------------------------------- -% ------------------------------------------------------------------------- -% Define Half-Size of the grating image. -textureSize = gratingSizePix / 2; + % Make our grating mask texture + gratingMaskTex = Screen('MakeTexture', cfg.win, mask); -% First we compute pixels per cycle rounded to the nearest pixel -pixPerCycle = ceil(1 / freqCyclesPerPix); + % Make a black and white noise mask half the size of our grating. This will + % be scaled upon drawing to make a "chunky" noise texture which our grating + % will mask. Note the round function in here. For this demo we are simply + % rounding the size to the nearest pixel, leaving PTB to do some scaling. + noise = rand(round(visibleSize / 2)) .* white; -% Frequency in Radians -freqRad = freqCyclesPerPix * 2 * pi; + % Make our noise texture + noiseTexture = Screen('MakeTexture', cfg.win, noise); -% This is the visible size of the grating -visibleSize = 2 * textureSize + 1; + % Make a destination rectangle for our textures and center this on the + % screen + dstRect = [0 0 visibleSize visibleSize]; + dstRect = CenterRect(dstRect, cfg.winRect); + % Calculate the wait duration + waitDuration = waitframes * cfg.ifi; -% Define our grating. Note it is only 1 pixel high. PTB will make it a full -% grating upon drawing -x = meshgrid(-textureSize:textureSize + pixPerCycle, 1); -grating = grey * cos(freqRad*x) + inc; + % Recompute pixPerCycle, this time without the ceil() operation from above. + % Otherwise we will get wrong drift speed due to rounding errors + pixPerCycle = 1 / freqCyclesPerPix; + % Translate requested speed of the grating (in cycles per second) into + % a shift value in "pixels per frame" + shiftPerFrame = cyclesPerSecond * pixPerCycle * waitDuration; -% Make a two layer mask filled with the background colour -mask = ones(1, numel(x), 2) * grey; + % Sync us to the vertical retrace + vbl = Screen('Flip', cfg.win); -% Place the grating in the 'alpha' channel of the mask -mask(:, :, 2)= grating .* contrast; + % Set the frame counter to zero, we need this to 'drift' our grating + frameCounter = 0; -% Make our grating mask texture -gratingMaskTex = Screen('MakeTexture', cfg.win, mask); + % Loop until a key is pressed + while ~KbCheck + % Calculate the xoffset for our window through which to sample our + % grating + xoffset = mod(frameCounter * shiftPerFrame, pixPerCycle); -% Make a black and white noise mask half the size of our grating. This will -% be scaled upon drawing to make a "chunky" noise texture which our grating -% will mask. Note the round function in here. For this demo we are simply -% rounding the size to the nearest pixel, leaving PTB to do some scaling. -noise = rand(round(visibleSize / 2)) .* white; + % Now increment the frame counter for the next loop + frameCounter = frameCounter + 1; -% Make our noise texture -noiseTexture = Screen('MakeTexture', cfg.win, noise); + % Define our source rectangle for grating sampling + srcRect = [xoffset 0 xoffset + visibleSize visibleSize]; + % Draw noise texture to the screen + Screen('DrawTexture', cfg.win, noiseTexture, [], dstRect, []); -% Make a destination rectangle for our textures and center this on the -% screen -dstRect = [0 0 visibleSize visibleSize]; -dstRect = CenterRect(dstRect, cfg.winRect); + % Draw grating mask + Screen('DrawTexture', cfg.win, gratingMaskTex, srcRect, dstRect, []); -% Calculate the wait duration -waitDuration = waitframes * cfg.ifi; + % Flip to the screen on the next vertical retrace + vbl = Screen('Flip', cfg.win, vbl + (waitframes - 0.5) * cfg.ifi); -% Recompute pixPerCycle, this time without the ceil() operation from above. -% Otherwise we will get wrong drift speed due to rounding errors -pixPerCycle = 1 / freqCyclesPerPix; + end -% Translate requested speed of the grating (in cycles per second) into -% a shift value in "pixels per frame" -shiftPerFrame = cyclesPerSecond * pixPerCycle * waitDuration; + % ------------------------------------------------------------------------- + %% -% Sync us to the vertical retrace -vbl = Screen('Flip', cfg.win); + % Close PTB, see the Sub-Functions below + devSandbox_cleanUp; -% Set the frame counter to zero, we need this to 'drift' our grating -frameCounter = 0; + catch -% Loop until a key is pressed -while ~KbCheck - - % Calculate the xoffset for our window through which to sample our - % grating - xoffset = mod(frameCounter * shiftPerFrame, pixPerCycle); - - % Now increment the frame counter for the next loop - frameCounter = frameCounter + 1; - - % Define our source rectangle for grating sampling - srcRect = [xoffset 0 xoffset + visibleSize visibleSize]; - - % Draw noise texture to the screen - Screen('DrawTexture', cfg.win, noiseTexture, [], dstRect, []); - - % Draw grating mask - Screen('DrawTexture', cfg.win, gratingMaskTex, srcRect, dstRect, []); - - % Flip to the screen on the next vertical retrace - vbl = Screen('Flip', cfg.win, vbl + (waitframes - 0.5) * cfg.ifi); - -end - - - -% ------------------------------------------------------------------------- -%% - -% Close PTB, see the Sub-Functions below -devSandbox_cleanUp - -catch - - devSandbox_cleanUp - psychrethrow(psychlasterror); - -end + devSandbox_cleanUp; + psychrethrow(psychlasterror); + end end %% Sub-Functions function cfg = devSandbox_initPTB(cfg) -% Shorter version of `initPTB.m` + % Shorter version of `initPTB.m` -% Skip the PTB sync test -Screen('Preference', 'SkipSyncTests', 2); + % Skip the PTB sync test + Screen('Preference', 'SkipSyncTests', 2); -% Open a transparent window -PsychDebugWindowConfiguration + % Open a transparent window + PsychDebugWindowConfiguration; -% Here we call some default settings for setting up Psychtoolbox -PsychDefaultSetup(2); + % Here we call some default settings for setting up Psychtoolbox + PsychDefaultSetup(2); -% Get the screen numbers and draw to the external screen if avaliable -cfg.screen = max(Screen('Screens')); + % Get the screen numbers and draw to the external screen if avaliable + cfg.screen = max(Screen('Screens')); -% Open an on screen window -[cfg.win, cfg.winRect] = Screen('OpenWindow', cfg.screen, cfg.backgroundColor); + % Open an on screen window + [cfg.win, cfg.winRect] = Screen('OpenWindow', cfg.screen, cfg.backgroundColor); -% Get the size of the on screen window -[cfg.winWidth, cfg.winHeight] = WindowSize(cfg.win); + % Get the size of the on screen window + [cfg.winWidth, cfg.winHeight] = WindowSize(cfg.win); -% Query the frame duration -cfg.ifi = Screen('GetFlipInterval', cfg.win); + % Query the frame duration + cfg.ifi = Screen('GetFlipInterval', cfg.win); -% Get the Center of the Screen -cfg.center = [cfg.winRect(3), cfg.winRect(4)]/2; + % Get the Center of the Screen + cfg.center = [cfg.winRect(3), cfg.winRect(4)] / 2; -% Set up alpha-blending for smooth (anti-aliased) lines -Screen('BlendFunction', cfg.win, 'GL_SRC_ALPHA', 'GL_ONE_MINUS_SRC_ALPHA'); + % Set up alpha-blending for smooth (anti-aliased) lines + Screen('BlendFunction', cfg.win, 'GL_SRC_ALPHA', 'GL_ONE_MINUS_SRC_ALPHA'); end function devSandbox_cleanUp -% A wrapper function to close all windows, ports, show mouse cursor, close keyboard queues -% and give access back to the keyboards. - -WaitSecs(0.5); + % A wrapper function to close all windows, ports, show mouse cursor, close keyboard queues + % and give access back to the keyboards. -Priority(0); + WaitSecs(0.5); -ListenChar(0); -KbQueueRelease(); + Priority(0); -ShowCursor + ListenChar(0); + KbQueueRelease(); -% Screen Close All -sca; + ShowCursor; -% Close Psychportaudio if open -if PsychPortAudio('GetOpenDeviceCount') ~= 0 - PsychPortAudio('Close'); -end + % Screen Close All + sca; -if ~ismac - % remove PsychDebugWindowConfiguration - clear Screen -end + % Close Psychportaudio if open + if PsychPortAudio('GetOpenDeviceCount') ~= 0 + PsychPortAudio('Close'); + end -close all + if ~ismac + % remove PsychDebugWindowConfiguration + clear Screen; + end + close all; end diff --git a/errorAbort.m b/errorAbort.m index 178cda0..c67f7f9 100644 --- a/errorAbort.m +++ b/errorAbort.m @@ -1,6 +1,6 @@ function errorAbort errorStruct.message = 'Escape key press detected: aborting experiment.'; errorStruct.identifier = 'checkAbort:abortRequested'; - - error(errorStruct) -end \ No newline at end of file + + error(errorStruct); +end diff --git a/errorAbortGetReponse.m b/errorAbortGetReponse.m index e563dc9..78ec208 100644 --- a/errorAbortGetReponse.m +++ b/errorAbortGetReponse.m @@ -1,7 +1,7 @@ function errorAbortGetReponse - + errorStruct.message = 'Escape key press detected by getResponse: aborting experiment.'; errorStruct.identifier = 'getResponse:abortRequested'; - error(errorStruct) -end \ No newline at end of file + error(errorStruct); +end diff --git a/errorRestrictedKeysGetReponse.m b/errorRestrictedKeysGetReponse.m index 81cc77e..b0c0a5a 100644 --- a/errorRestrictedKeysGetReponse.m +++ b/errorRestrictedKeysGetReponse.m @@ -1,7 +1,7 @@ function errorRestrictedKeysGetReponse - + errorStruct.message = 'getResponse reported a key press on a restricted key'; errorStruct.identifier = 'getResponse:restrictedKey'; - - error(errorStruct) -end \ No newline at end of file + + error(errorStruct); +end diff --git a/getResponse.m b/getResponse.m index e77032e..c028a9d 100644 --- a/getResponse.m +++ b/getResponse.m @@ -1,208 +1,203 @@ function responseEvents = getResponse(action, deviceNumber, cfg, getOnlyPress) -% Wrapper function to use KbQueue -% -% The queue will be listening to key presses on a keyboard device: -% cfg.keyboard.responseBox or cfg.keyboard.keyboard are 2 main examples. -% -% When no deviceNumber is set then it will listen to the default device. -% -% Check the CPP_getResponseDemo for a quick script on how to use it. -% -% -% INPUT -% -% - action: Defines what we want the function to do -% - init: to initialise the queue -% - start: to start listening to keypresses -% - check: checks all the key presses events since 'start', or since last -% 'check' or 'flush' (whichever was the most recent) -% - flush: empties the queue of events in case you want to restart from a clean -% queue -% - stop: stops listening to key presses -% -% - getOnlyPress: if set to true the function will only return the key presses and -% will not return when the keys were released (default=true) -% See the section on OUTPUT below for more info -% -% -% -% OUTPUT -% -% responseEvents: returns all the keypresses and return them as a structure -% with field names that make it easier to save the output of in a BIDS -% format -% -% responseEvents.onset : this is an absolute value and you should -% substract the "experiment start time" to get a value relative to when the -% experiment was started. -% -% responseEvents.trial_type = 'response'; -% -% responseEvents.duration = 0; -% -% responseEvents.keyName : the name of the key pressed -% -% responseEvents(iEvent,1).pressed : if -% pressed == 1 --> the key was pressed -% pressed == 0 --> the key was released - - -if nargin < 2 || isempty(deviceNumber) - deviceNumber = -1; -end + % Wrapper function to use KbQueue + % + % The queue will be listening to key presses on a keyboard device: + % cfg.keyboard.responseBox or cfg.keyboard.keyboard are 2 main examples. + % + % When no deviceNumber is set then it will listen to the default device. + % + % Check the CPP_getResponseDemo for a quick script on how to use it. + % + % + % INPUT + % + % - action: Defines what we want the function to do + % - init: to initialise the queue + % - start: to start listening to keypresses + % - check: checks all the key presses events since 'start', or since last + % 'check' or 'flush' (whichever was the most recent) + % - flush: empties the queue of events in case you want to restart from a clean + % queue + % - stop: stops listening to key presses + % + % - getOnlyPress: if set to true the function will only return the key presses and + % will not return when the keys were released (default=true) + % See the section on OUTPUT below for more info + % + % + % + % OUTPUT + % + % responseEvents: returns all the keypresses and return them as a structure + % with field names that make it easier to save the output of in a BIDS + % format + % + % responseEvents.onset : this is an absolute value and you should + % substract the "experiment start time" to get a value relative to when the + % experiment was started. + % + % responseEvents.trial_type = 'response'; + % + % responseEvents.duration = 0; + % + % responseEvents.keyName : the name of the key pressed + % + % responseEvents(iEvent,1).pressed : if + % pressed == 1 --> the key was pressed + % pressed == 0 --> the key was released + + if nargin < 2 || isempty(deviceNumber) + deviceNumber = -1; + end -if nargin < 3 - cfg = struct(... - 'keyboard', struct('responseKey', {})... - ); -end + if nargin < 3 + cfg = struct( ... + 'keyboard', struct('responseKey', {}) ... + ); + end -if nargin < 4 - getOnlyPress = true; -end + if nargin < 4 + getOnlyPress = true; + end -responseEvents = struct; - -switch action - - case 'init' - - % Prevent spilling of keystrokes into console. - % If you use ListenChar(2), this will prevent you from using KbQueue. - ListenChar(-1); - - % Clean and realease any queue that might be opened - KbQueueRelease(deviceNumber); - - keysOfInterest = setKeysOfInterest(cfg.keyboard); - - % Create the keyboard queue to collect responses. - KbQueueCreate(deviceNumber, keysOfInterest); - - case 'start' - KbQueueStart(deviceNumber); - - case 'check' - responseEvents = getAllKeyEvents(responseEvents, deviceNumber, getOnlyPress); - - checkAbortGetResponse(responseEvents, cfg); - - case 'flush' - KbQueueFlush(deviceNumber); - - case 'stop' - KbQueueStop(deviceNumber) - - case 'release' - KbQueueRelease(deviceNumber); - - % Give me my keyboard back... Pretty please. - ListenChar(0); - -end + responseEvents = struct; -talkToMe(action); + switch action -end + case 'init' + % Prevent spilling of keystrokes into console. + % If you use ListenChar(2), this will prevent you from using KbQueue. + ListenChar(-1); -function keysOfInterest = setKeysOfInterest(keyboard) -% list all the response keys we want KbQueue to listen to -% by default we listen to all keys -% but if responseKey is set in the parameters we override this + % Clean and realease any queue that might be opened + KbQueueRelease(deviceNumber); + + keysOfInterest = setKeysOfInterest(cfg.keyboard); + + % Create the keyboard queue to collect responses. + KbQueueCreate(deviceNumber, keysOfInterest); + + case 'start' + KbQueueStart(deviceNumber); + + case 'check' + responseEvents = getAllKeyEvents(responseEvents, deviceNumber, getOnlyPress); -keysOfInterest = zeros(1,256); + checkAbortGetResponse(responseEvents, cfg); -fprintf('\n Will be listening for key presses on : '); + case 'flush' + KbQueueFlush(deviceNumber); -if ~isempty(keyboard.responseKey) + case 'stop' + KbQueueStop(deviceNumber); + + case 'release' + KbQueueRelease(deviceNumber); + + % Give me my keyboard back... Pretty please. + ListenChar(0); - responseTargetKeys = nan(1,numel(keyboard.responseKey)); - - for iKey = 1:numel(keyboard.responseKey) - fprintf('\n - %s ', keyboard.responseKey{iKey}); - responseTargetKeys(iKey) = KbName(keyboard.responseKey(iKey)); end - - keysOfInterest(responseTargetKeys) = 1; - -else - - keysOfInterest = ones(1,256); - - fprintf('ALL KEYS.'); - -end -fprintf('\n\n'); + talkToMe(action); + end +function keysOfInterest = setKeysOfInterest(keyboard) + % list all the response keys we want KbQueue to listen to + % by default we listen to all keys + % but if responseKey is set in the parameters we override this -function responseEvents = getAllKeyEvents(responseEvents, deviceNumber, getOnlyPress) + keysOfInterest = zeros(1, 256); -iEvent = 1; + fprintf('\n Will be listening for key presses on : '); + + if ~isempty(keyboard.responseKey) + + responseTargetKeys = nan(1, numel(keyboard.responseKey)); + + for iKey = 1:numel(keyboard.responseKey) + fprintf('\n - %s ', keyboard.responseKey{iKey}); + responseTargetKeys(iKey) = KbName(keyboard.responseKey(iKey)); + end + + keysOfInterest(responseTargetKeys) = 1; -while KbEventAvail(deviceNumber) - - event = KbEventGet(deviceNumber); - - % we only return the pressed keys by default - if getOnlyPress==true && event.Pressed==0 else - - responseEvents(iEvent,1).onset = event.Time; - responseEvents(iEvent,1).trial_type = 'response'; - responseEvents(iEvent,1).duration = 0; - responseEvents(iEvent,1).keyName = KbName(event.Keycode); - responseEvents(iEvent,1).pressed = event.Pressed; - - iEvent = iEvent + 1; - + + keysOfInterest = ones(1, 256); + + fprintf('ALL KEYS.'); + end - + + fprintf('\n\n'); end +function responseEvents = getAllKeyEvents(responseEvents, deviceNumber, getOnlyPress) + + iEvent = 1; + + while KbEventAvail(deviceNumber) + + event = KbEventGet(deviceNumber); + + % we only return the pressed keys by default + if getOnlyPress == true && event.Pressed == 0 + else + + responseEvents(iEvent, 1).onset = event.Time; + responseEvents(iEvent, 1).trial_type = 'response'; + responseEvents(iEvent, 1).duration = 0; + responseEvents(iEvent, 1).keyName = KbName(event.Keycode); + responseEvents(iEvent, 1).pressed = event.Pressed; + + iEvent = iEvent + 1; + + end + + end + end function checkAbortGetResponse(responseEvents, cfg) - + if isfield(responseEvents, 'keyName') > 0 && ... any( ... - strcmpi({responseEvents(:).keyName}, cfg.keyboard.escapeKey)... + strcmpi({responseEvents(:).keyName}, cfg.keyboard.escapeKey) ... ) - errorAbortGetReponse(responseEvents) + errorAbortGetReponse(responseEvents); end end - function talkToMe(action) + switch action -switch action - - case 'init' - msg = 'Initialising KbQueue.'; - - case 'start' - msg = 'Starting to listen to keypresses.'; - - case 'check' - msg = 'Checking recent keypresses.'; - - case 'flush' - msg = 'Reinitialising keyboard queue.'; - - case 'stop' - msg = 'Stopping to listen to keypresses.'; - - case 'release' - msg = 'Releasing KbQueue.'; - - otherwise - msg = ''; - -end + case 'init' + msg = 'Initialising KbQueue.'; + + case 'start' + msg = 'Starting to listen to keypresses.'; + + case 'check' + msg = 'Checking recent keypresses.'; + + case 'flush' + msg = 'Reinitialising keyboard queue.'; -fprintf('\n %s\n\n', msg); + case 'stop' + msg = 'Stopping to listen to keypresses.'; -end \ No newline at end of file + case 'release' + msg = 'Releasing KbQueue.'; + + otherwise + msg = ''; + + end + + fprintf('\n %s\n\n', msg); + +end diff --git a/initPTB.m b/initPTB.m index 20932d4..4844917 100644 --- a/initPTB.m +++ b/initPTB.m @@ -1,205 +1,193 @@ function [cfg] = initPTB(cfg) -% This will initialize PsychToolBox -% - screen -% - the windon opened takes the whole screen unless -% cfg.testingSmallScreen is set to true -% - debug mode : skips synch test and warnings -% - window transparency enabled by cfg.testingTranspScreen set to true -% - gets the flip interval -% - computes the pixel per degree of visual angle: -% the computation for ppd assumes the windows takes the whole screenDistance -% - set font details -% - keyboard -% - hides cursor -% - sound -% -% OUTPUT: -% cfg.keyboard = []; -% cfg.responseBox = []; -% -% cfg.debug = true; -% cfg.testingTranspScreen = true; -% cfg.testingSmallScreen = true; -% -% cfg.screen : screen numbers where drawing the stimulation (external screen if available) -% cfg.win : window opened by PTB -% cfg.winRect : window rectangule positiona and dimensions in pixel coordinates -% cfg.winWidth : window width in pixels -% cfg.winHeight : window height in pixels -% cfg.center : coordinate of the window center -% cfg.ppd : pixels per degree assuming the window fills the whole screen -% cfg.ifi : estimate of the monitor flip interval -% cfg.monRefresh : monitor refresh rate -% cfg.vbl : (I don't think this output is useful) -% cfg.textFont = 'Courier New'; -% cfg.textSize = 18; -% cfg.textStyle = 1; -% cfg.backgroundColor = [0 0 0]; -% cfg.monitorWidth = 42; -% cfg.screenDistance = 134; - -% TO DO -% - We might want to add a couple of IF in case the experiment does not use audio for example. - - -checkDependencies() - -% For octave: to avoid displaying messenging one screen at a time -more off - -% check for OpenGL compatibility, abort otherwise: -AssertOpenGL; - -cfg = setDefaultsPTB(cfg); - -initDebug(cfg); - - -%% Keyboard -initKeyboard - - -%% Mouse -HideCursor; - + % This will initialize PsychToolBox + % - screen + % - the windon opened takes the whole screen unless + % cfg.testingSmallScreen is set to true + % - debug mode : skips synch test and warnings + % - window transparency enabled by cfg.testingTranspScreen set to true + % - gets the flip interval + % - computes the pixel per degree of visual angle: + % the computation for ppd assumes the windows takes the whole screenDistance + % - set font details + % - keyboard + % - hides cursor + % - sound + % + % OUTPUT: + % cfg.keyboard = []; + % cfg.responseBox = []; + % + % cfg.debug = true; + % cfg.testingTranspScreen = true; + % cfg.testingSmallScreen = true; + % + % cfg.screen : screen numbers where drawing the stimulation (external screen if available) + % cfg.win : window opened by PTB + % cfg.winRect : window rectangule positiona and dimensions in pixel coordinates + % cfg.winWidth : window width in pixels + % cfg.winHeight : window height in pixels + % cfg.center : coordinate of the window center + % cfg.ppd : pixels per degree assuming the window fills the whole screen + % cfg.ifi : estimate of the monitor flip interval + % cfg.monRefresh : monitor refresh rate + % cfg.vbl : (I don't think this output is useful) + % cfg.textFont = 'Courier New'; + % cfg.textSize = 18; + % cfg.textStyle = 1; + % cfg.backgroundColor = [0 0 0]; + % cfg.monitorWidth = 42; + % cfg.screenDistance = 134; + + % TO DO + % - We might want to add a couple of IF in case the experiment does not use audio for example. + + checkDependencies(); + + % For octave: to avoid displaying messenging one screen at a time + more off; + + % check for OpenGL compatibility, abort otherwise: + AssertOpenGL; + + cfg = setDefaultsPTB(cfg); + + initDebug(cfg); + + %% Keyboard + initKeyboard; + + %% Mouse + HideCursor; %% Audio cfg = initAudio(cfg); - - -%% Visual + %% Visual -% Get the screen numbers and draw to the external screen if avaliable -cfg.screen = max(Screen('Screens')); + % Get the screen numbers and draw to the external screen if avaliable + cfg.screen = max(Screen('Screens')); -cfg = openWindow(cfg); + cfg = openWindow(cfg); -% window size info -[cfg.winWidth, cfg.winHeight] = WindowSize(cfg.win); - -if strcmpi(cfg.stimPosition,'scanner') - cfg.winRect(1,4) = cfg.winRect(1,4)*2/3; -end + % window size info + [cfg.winWidth, cfg.winHeight] = WindowSize(cfg.win); -% Get the Center of the Screen -cfg.center = [cfg.winRect(3), cfg.winRect(4)]/2; - -% Computes the number of pixels per degree given the distance to screen and -% monitor width -% This assumes that the window fills the whole screen -FOV = computeFOV(cfg); -cfg.ppd = cfg.winRect(3)/FOV; + if strcmpi(cfg.stimPosition, 'scanner') + cfg.winRect(1, 4) = cfg.winRect(1, 4) * 2 / 3; + end + % Get the Center of the Screen + cfg.center = [cfg.winRect(3), cfg.winRect(4)] / 2; -%% Select specific text font, style and size -initText(cfg) + % Computes the number of pixels per degree given the distance to screen and + % monitor width + % This assumes that the window fills the whole screen + FOV = computeFOV(cfg); + cfg.ppd = cfg.winRect(3) / FOV; + %% Select specific text font, style and size + initText(cfg); -%% Timing -% Query frame duration -cfg.ifi = Screen('GetFlipInterval', cfg.win); -cfg.monRefresh = 1/cfg.ifi; + %% Timing + % Query frame duration + cfg.ifi = Screen('GetFlipInterval', cfg.win); + cfg.monRefresh = 1 / cfg.ifi; -% Set priority for script execution to realtime priority: -Priority(MaxPriority(cfg.win)); + % Set priority for script execution to realtime priority: + Priority(MaxPriority(cfg.win)); + %% Warm up some functions + % Do dummy calls to GetSecs, WaitSecs, KbCheck to make sure + % they are loaded and ready when we need them - without delays + % in the wrong moment: + KbCheck; + WaitSecs(0.1); + GetSecs; -%% Warm up some functions -% Do dummy calls to GetSecs, WaitSecs, KbCheck to make sure -% they are loaded and ready when we need them - without delays -% in the wrong moment: -KbCheck; -WaitSecs(0.1); -GetSecs; + %% Initial flip to get a first time stamp + % Initially sync us to VBL at start of animation loop. + cfg.vbl = Screen('Flip', cfg.win); +end -%% Initial flip to get a first time stamp -% Initially sync us to VBL at start of animation loop. -cfg.vbl = Screen('Flip', cfg.win); +function initDebug(cfg) + % init PTB with different options in concordance to the debug Parameters + Screen('Preference', 'SkipSyncTests', 0); + if cfg.debug -end + Screen('Preference', 'SkipSyncTests', 2); + Screen('Preference', 'Verbosity', 0); + Screen('Preferences', 'SuppressAllWarnings', 2); + fprintf('\n\n\n\n'); + fprintf('########################################\n'); + fprintf('## DEBUG MODE. TIMING WILL BE OFF. ##\n'); + fprintf('########################################'); + fprintf('\n\n\n\n'); -function initDebug(cfg) + testKeyboards(cfg); -% init PTB with different options in concordance to the debug Parameters -Screen('Preference','SkipSyncTests', 0); -if cfg.debug - - Screen('Preference', 'SkipSyncTests', 2); - Screen('Preference', 'Verbosity', 0); - Screen('Preferences', 'SuppressAllWarnings', 2); - - fprintf('\n\n\n\n') - fprintf('########################################\n') - fprintf('## DEBUG MODE. TIMING WILL BE OFF. ##\n') - fprintf('########################################') - fprintf('\n\n\n\n') - - testKeyboards(cfg) - -end + end -if cfg.testingTranspScreen - PsychDebugWindowConfiguration -end + if cfg.testingTranspScreen + PsychDebugWindowConfiguration; + end end function initKeyboard -% Make sure keyboard mapping is the same on all supported operating systems -% Apple MacOS/X, MS-Windows and GNU/Linux: -KbName('UnifyKeyNames'); + % Make sure keyboard mapping is the same on all supported operating systems + % Apple MacOS/X, MS-Windows and GNU/Linux: + KbName('UnifyKeyNames'); -% Don't echo keypresses to Matlab window -ListenChar(-1); + % Don't echo keypresses to Matlab window + ListenChar(-1); end function cfg = initAudio(cfg) - + if cfg.initAudio InitializePsychSound(1); - cfg.audio.devIdx= []; + cfg.audio.devIdx = []; cfg.audio.playbackMode = 1; - + if isfield(cfg.audio, 'useDevice') - + % get audio device list audioDev = PsychPortAudio('GetDevices'); - + % find output device to use - idx = find(... + idx = find( ... audioDev.NrInputChannels == cfg.audio.inputChannels && ... audioDev.NrOutputChannels == cfg.audio.channels && ... ~cellfun(@isempty, regexp({audioDev.HostAudioAPIName}, cfg.audio.deviceName))); - + % save device ID cfg.audio.devIdx = audioDev(idx).DeviceIndex; - + % get device's sampling rate cfg.audio.fs = audioDev(idx).DefaultSampleRate; end - + cfg.audio.pahandle = PsychPortAudio('Open', ... cfg.audio.devIdx, ... cfg.audio.playbackMode, ... cfg.audio.requestedLatency, ... cfg.audio.fs, ... cfg.audio.channels); - + % set initial PTB volume for safety (participants can adjust this manually % at the begining of the experiment) PsychPortAudio('Volume', cfg.audio.pahandle, cfg.audio.initVolume); - - cfg.audio.pushSize = cfg.audio.fs * 0.010; %! push N ms only - + + cfg.audio.pushSize = cfg.audio.fs * 0.010; % ! push N ms only + cfg.audio.requestOffsetTime = 1; % offset 1 sec cfg.audio.reqsSampleOffset = cfg.audio.requestOffsetTime * cfg.audio.fs; @@ -208,30 +196,29 @@ function initDebug(cfg) function cfg = openWindow(cfg) -if cfg.testingSmallScreen - [cfg.win, cfg.winRect] = Screen('OpenWindow', cfg.screen, cfg.backgroundColor, [0,0, 480, 270]); -else - [cfg.win, cfg.winRect] = Screen('OpenWindow', cfg.screen, cfg.backgroundColor); -end + if cfg.testingSmallScreen + [cfg.win, cfg.winRect] = Screen('OpenWindow', cfg.screen, cfg.backgroundColor, [0, 0, 480, 270]); + else + [cfg.win, cfg.winRect] = Screen('OpenWindow', cfg.screen, cfg.backgroundColor); + end -% Enable alpha-blending, set it to a blend equation useable for linear -% superposition with alpha-weighted source. -Screen('BlendFunction', cfg.win, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + % Enable alpha-blending, set it to a blend equation useable for linear + % superposition with alpha-weighted source. + Screen('BlendFunction', cfg.win, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); end function FOV = computeFOV(cfg) -% computes the number of degrees of visual angle in the whole field of view -FOV = 2 *( 180 * ( atan( cfg.monitorWidth / (2*cfg.screenDistance) ) / pi)); + % computes the number of degrees of visual angle in the whole field of view + FOV = 2 * (180 * (atan(cfg.monitorWidth / (2 * cfg.screenDistance)) / pi)); end - function initText(cfg) -Screen('TextFont', cfg.win, cfg.text.Font); -Screen('TextSize', cfg.win, cfg.text.Size); -Screen('TextStyle', cfg.win, cfg.text.Style); + Screen('TextFont', cfg.win, cfg.text.Font); + Screen('TextSize', cfg.win, cfg.text.Size); + Screen('TextStyle', cfg.win, cfg.text.Style); -end \ No newline at end of file +end diff --git a/setDefaultsPTB.m b/setDefaultsPTB.m index 733eedc..1b801f4 100644 --- a/setDefaultsPTB.m +++ b/setDefaultsPTB.m @@ -1,69 +1,68 @@ function cfg = setDefaultsPTB(cfg) - - if nargin<1 + + if nargin < 1 cfg = struct; end -%% list the default values -fieldsToSet.testingDevice = 'pc'; - -% keyboard defaults -fieldsToSet.keyboard.keyboard = []; -fieldsToSet.keyboard.responseBox = []; -fieldsToSet.keyboard.responseKey = {}; -fieldsToSet.keyboard.escapeKey = 'ESCAPE'; - -fieldsToSet.debug = true; -fieldsToSet.testingTranspScreen = true; -fieldsToSet.testingSmallScreen = true; - -fieldsToSet.backgroundColor = [0 0 0]; - -% text defaults -fieldsToSet.text.font = 'Courier New'; -fieldsToSet.text.size = 18; -fieldsToSet.text.style = 1; - -fieldsToSet.monitorWidth = 42; -fieldsToSet.screenDistance = 134; - -if isfield(cfg, 'initAudio') && cfg.initAudio - - fieldsToSet.audio.fs = 44800; - fieldsToSet.audio.channels = 2; - fieldsToSet.audio.initVolume = 1; - fieldsToSet.audio.requestedLatency = 3; - - % playing parameters - % sound repetition - fieldsToSet.audio.repeat = 1; - - % Start immediately (0 = immediately) - fieldsToSet.audio.startCue = 0; - - % Should we wait for the device to really start? - fieldsToSet.audio.waitForDevice = 1; + %% list the default values + fieldsToSet.testingDevice = 'pc'; -end + % keyboard defaults + fieldsToSet.keyboard.keyboard = []; + fieldsToSet.keyboard.responseBox = []; + fieldsToSet.keyboard.responseKey = {}; + fieldsToSet.keyboard.escapeKey = 'ESCAPE'; -if isfield(cfg, 'testingDevice') && strcmpi(cfg.testingDevice, 'scanner') - fieldsToSet.MRI.repetitionTime = []; -end + fieldsToSet.debug = true; + fieldsToSet.testingTranspScreen = true; + fieldsToSet.testingSmallScreen = true; -%% set the defaults -% loop through the defaults and set them in cfg if they don't exist -names = fieldnames(fieldsToSet); + fieldsToSet.backgroundColor = [0 0 0]; -for i = 1:numel(names) - cfg = setFieldToIfNotPresent(... - cfg, ... - names{i}, ... - getfield(fieldsToSet, names{i})); %#ok -end + % text defaults + fieldsToSet.text.font = 'Courier New'; + fieldsToSet.text.size = 18; + fieldsToSet.text.style = 1; + + fieldsToSet.monitorWidth = 42; + fieldsToSet.screenDistance = 134; -%% sort fields alphabetically -cfg = orderfields(cfg); + if isfield(cfg, 'initAudio') && cfg.initAudio + fieldsToSet.audio.fs = 44800; + fieldsToSet.audio.channels = 2; + fieldsToSet.audio.initVolume = 1; + fieldsToSet.audio.requestedLatency = 3; + + % playing parameters + % sound repetition + fieldsToSet.audio.repeat = 1; + + % Start immediately (0 = immediately) + fieldsToSet.audio.startCue = 0; + + % Should we wait for the device to really start? + fieldsToSet.audio.waitForDevice = 1; + + end + + if isfield(cfg, 'testingDevice') && strcmpi(cfg.testingDevice, 'scanner') + fieldsToSet.MRI.repetitionTime = []; + end + + %% set the defaults + % loop through the defaults and set them in cfg if they don't exist + names = fieldnames(fieldsToSet); + + for i = 1:numel(names) + cfg = setFieldToIfNotPresent( ... + cfg, ... + names{i}, ... + getfield(fieldsToSet, names{i})); %#ok + end + + %% sort fields alphabetically + cfg = orderfields(cfg); end @@ -71,4 +70,4 @@ if ~isfield(struct, fieldName) struct = setfield(struct, fieldName, value); %#ok end -end \ No newline at end of file +end diff --git a/testKeyboards.m b/testKeyboards.m index 99dc71c..68b6ed4 100644 --- a/testKeyboards.m +++ b/testKeyboards.m @@ -1,68 +1,63 @@ function testKeyboards(cfg) -% Checks that the keyboards asked for properly connected. -% If no key is pressed on the correct keyboard after the timeOut time this exits with an error. + % Checks that the keyboards asked for properly connected. + % If no key is pressed on the correct keyboard after the timeOut time this exits with an error. -timeOut = 5; + timeOut = 5; - -% Main keyboard used by the experimenter to quit the experiment if it is necessary -% cfg.keyboard -fprintf('\n This is a test: press any key on the main keyboard\n'); -t = GetSecs; -[~, keyCode, ~] = KbPressWait(cfg.keyboard.keyboard, t+timeOut); -throwError(keyCode, cfg.keyboard.keyboard, 1) - -if ~isequal(cfg.keyboard.keyboard, cfg.keyboard.responseBox) - - % For key presses for the subject - % cfg.keyboard.responseBox - fprintf('\n Thiscfg.keyboard is a test: press any key on the participant response box\n'); + % Main keyboard used by the experimenter to quit the experiment if it is necessary + % cfg.keyboard + fprintf('\n This is a test: press any key on the main keyboard\n'); t = GetSecs; - [~, keyCode, ~] = KbPressWait(cfg.keyboard.responseBox, t+timeOut); - throwError(keyCode, cfg.keyboard.responseBox, 2) - -end + [~, keyCode, ~] = KbPressWait(cfg.keyboard.keyboard, t + timeOut); + throwError(keyCode, cfg.keyboard.keyboard, 1); -end + if ~isequal(cfg.keyboard.keyboard, cfg.keyboard.responseBox) + % For key presses for the subject + % cfg.keyboard.responseBox + fprintf('\n Thiscfg.keyboard is a test: press any key on the participant response box\n'); + t = GetSecs; + [~, keyCode, ~] = KbPressWait(cfg.keyboard.responseBox, t + timeOut); + throwError(keyCode, cfg.keyboard.responseBox, 2); -function throwError(keyCode, deviceNumber, keyboardType) + end -switch keyboardType - case 1 - keyboardType = 'main keyboard'; - case 2 - keyboardType = 'response box'; end +function throwError(keyCode, deviceNumber, keyboardType) -text1 = ['\nYou asked for this keyboard device number to be used as ' keyboardType '.\n\n']; - -text2 = '\nThese are the devices currently connected.\n\n'; + switch keyboardType + case 1 + keyboardType = 'main keyboard'; + case 2 + keyboardType = 'response box'; + end -errorText = 'No key was pressed. Did you configure the keyboards properly? See message above for more info.'; + text1 = ['\nYou asked for this keyboard device number to be used as ' keyboardType '.\n\n']; + text2 = '\nThese are the devices currently connected.\n\n'; -if all(keyCode==0) + errorText = 'No key was pressed. Did you configure the keyboards properly? See message above for more info.'; - % Give me my keyboard back... Pretty please. - ListenChar(); + if all(keyCode == 0) - fprintf(text1); + % Give me my keyboard back... Pretty please. + ListenChar(); - if isempty(deviceNumber) - disp('No keyboard selected, default is the main keyboard') - else - disp(deviceNumber) - end + fprintf(text1); - fprintf(text2); + if isempty(deviceNumber) + disp('No keyboard selected, default is the main keyboard'); + else + disp(deviceNumber); + end - [keyboardNumbers, keyboardNames] = GetKeyboardIndices %#ok<*NOPRT,*ASGLU> + fprintf(text2); - error(errorText) + [keyboardNumbers, keyboardNames] = GetKeyboardIndices; %#ok<*NOPRT,*ASGLU> -end + error(errorText); + end end diff --git a/tests/getResponseTest.m b/tests/getResponseTest.m index 4d69670..78a3bad 100644 --- a/tests/getResponseTest.m +++ b/tests/getResponseTest.m @@ -1,10 +1,10 @@ -addpath(fullfile(pwd, '..')) +addpath(fullfile(pwd, '..')); clear; clc; if IsOctave - more off % for a better display experience + more off; % for a better display experience end cfg = setDefaultsPTB; @@ -15,8 +15,7 @@ deviceNumber = []; % default device (PTB will find it for you) cfg.keyboard.responseKey = {'a', 'b'}; -cfg.keyboard.responseKey{end+1} = cfg.keyboard.escapeKey; - +cfg.keyboard.responseKey{end + 1} = cfg.keyboard.escapeKey; %% Final checks @@ -29,53 +28,51 @@ if isempty(cfg.keyboard.responseKey) fprintf('\nALL KEYS\n\n'); else - for iKey=1:numel(cfg.keyboard.responseKey) + for iKey = 1:numel(cfg.keyboard.responseKey) fprintf('\n%s', cfg.keyboard.responseKey{iKey}); end fprintf('\n\n'); end - %% Run manual test try - + getResponse('init', deviceNumber, cfg); - + getResponse('start', deviceNumber); - + WaitSecs(5); - + responseEvents = getResponse('check', deviceNumber, cfg, getOnlyPress); - + getResponse('release', deviceNumber); - - - %if some keys were pressed and that we are supposed to listen to only some - %keys, we make sure that only those keys were listened to - + + % if some keys were pressed and that we are supposed to listen to only some + % keys, we make sure that only those keys were listened to + if ~isempty(cfg.keyboard.responseKey) && isfield(responseEvents, 'keyName') - + for iEvent = 1:size(responseEvents, 1) fprintf(' %s was pressed\n ', ... responseEvents(iEvent).keyName); - + if ~any(strcmp({responseEvents(iEvent).keyName}, cfg.keyboard.responseKey)) errorRestrictedKeysGetReponse(); end end - + end - + catch ME - + switch ME.identifier case 'getResponse:restrictedKey' - rethrow(ME) - + rethrow(ME); + otherwise getResponse('release', deviceNumber); - - rethrow(ME) + + rethrow(ME); end - + end diff --git a/tests/runtests.m b/tests/runtests.m index a1b006f..6facd13 100644 --- a/tests/runtests.m +++ b/tests/runtests.m @@ -1,51 +1,53 @@ function results = runtests(pth) -% Run tests -% List all the 'test_*.m' files located in the same directory as this -% function, run them and keep track of how many passed, failed or are -% incomplete. -%__________________________________________________________________________ - -% Copyright (C) 2019, Guillaume Flandin, Wellcome Centre for Human Neuroimaging -% Copyright (C) 2020--, CPP_BIDS developers - -%-Get the path of where this file is located -if ~nargin, pth = fileparts(mfilename('fullpath')); end - -%-List all the 'test_*.m' files located in the same directory as this -% function -d = dir(pth); -d([d.isdir]) = []; -d(arrayfun(@(x) isempty(regexp(x.name,'^test_.*\.m$','once')),d)) = []; - -results = struct('Passed',{},'Failed',{},'Incomplete',{},'Duration',{}); -for i=1:numel(d) - - results(i).Failed = false; - results(i).Passed = false; - results(i).Incomplete = false; - - tstart = tic; - - %-Run each test file and catch error message in case of failure - try - - fprintf('%s',d(i).name(1:end-2)); - feval(d(i).name(1:end-2)); - results(i).Passed = true; - - catch err - results(i).Failed = true; - fprintf('\n%s',err.message); + % Run tests + % List all the 'test_*.m' files located in the same directory as this + % function, run them and keep track of how many passed, failed or are + % incomplete. + % __________________________________________________________________________ + + % Copyright (C) 2019, Guillaume Flandin, Wellcome Centre for Human Neuroimaging + % Copyright (C) 2020--, CPP_BIDS developers + + % -Get the path of where this file is located + if ~nargin + pth = fileparts(mfilename('fullpath')); end - results(i).Duration = toc(tstart); + % -List all the 'test_*.m' files located in the same directory as this + % function + d = dir(pth); + d([d.isdir]) = []; + d(arrayfun(@(x) isempty(regexp(x.name, '^test_.*\.m$', 'once')), d)) = []; - fprintf('\n'); + results = struct('Passed', {}, 'Failed', {}, 'Incomplete', {}, 'Duration', {}); + for i = 1:numel(d) -end + results(i).Failed = false; + results(i).Passed = false; + results(i).Incomplete = false; -if ~nargout - fprintf(['Totals (%d tests):\n\t%d Passed, %d Failed, %d Incomplete.\n' ... - '\t%f seconds testing time.\n\n'],numel(results),nnz([results.Passed]),... - nnz([results.Failed]),nnz([results.Incomplete]),sum([results.Duration])); -end + tstart = tic; + + % -Run each test file and catch error message in case of failure + try + + fprintf('%s', d(i).name(1:end - 2)); + feval(d(i).name(1:end - 2)); + results(i).Passed = true; + + catch err + results(i).Failed = true; + fprintf('\n%s', err.message); + end + + results(i).Duration = toc(tstart); + + fprintf('\n'); + + end + + if ~nargout + fprintf(['Totals (%d tests):\n\t%d Passed, %d Failed, %d Incomplete.\n' ... + '\t%f seconds testing time.\n\n'], numel(results), nnz([results.Passed]), ... + nnz([results.Failed]), nnz([results.Incomplete]), sum([results.Duration])); + end diff --git a/tests/test_setDefaultsPTB.m b/tests/test_setDefaultsPTB.m index 2dd7d3a..d8eb08b 100644 --- a/tests/test_setDefaultsPTB.m +++ b/tests/test_setDefaultsPTB.m @@ -1,7 +1,7 @@ %% test basic cfg creation % set up -cfgToTest = struct(... +cfgToTest = struct( ... 'testingDevice', 'pc', ... 'debug', true, ... 'testingTranspScreen', true, ... @@ -16,22 +16,20 @@ cfgToTest.keyboard.responseKey = {}; cfgToTest.keyboard.escapeKey = 'ESCAPE'; - cfgToTest = orderfields(cfgToTest); % test cfg = setDefaultsPTB; -assert(isequal(cfg, cfgToTest)) - +assert(isequal(cfg, cfgToTest)); %% test that values are not overwritten -clear cfg +clear cfg; cfg = struct('monitorWidth', 36); cfgToTest.monitorWidth = 36; cfg = setDefaultsPTB(cfg); -assert(isequal(cfg, cfgToTest)) +assert(isequal(cfg, cfgToTest)); cfgToTest.monitorWidth = 42; @@ -39,7 +37,7 @@ % set up cfgToTest.initAudio = 1; -cfgToTest.audio = struct(... +cfgToTest.audio = struct( ... 'fs', 44800, ... 'channels', 2, ... 'initVolume', 1, ... @@ -50,9 +48,9 @@ cfgToTest = orderfields(cfgToTest); -clear cfg +clear cfg; cfg.initAudio = 1; % test cfg = setDefaultsPTB(cfg); -assert(isequal(cfg, cfgToTest)) \ No newline at end of file +assert(isequal(cfg, cfgToTest)); diff --git a/wait4Trigger.m b/wait4Trigger.m index 94c3eba..6629662 100644 --- a/wait4Trigger.m +++ b/wait4Trigger.m @@ -1,82 +1,80 @@ function wait4Trigger(cfg, deviceNumber) -% Counts a certain number of triggers coming from the scanner before returning. -% -% Will print the count down in the command line and on the PTB window if one is -% opened. -% -% If the fMRI sequence RT is provided (cgf.MRI.repetitionTime) then it will wait -% for half a RT before starting to check for next trigger, otherwise it will -% wait 500 ms. -% -% When no deviceNumber is set then it will check the default device: this is -% probably only useful in debug as you will want to make sure you get the -% triggers coming from the scanner in a real case scenario. - -if nargin < 1 || isempty(cfg) - error('I need at least one input.') -end - -if nargin < 2 || isempty(deviceNumber) - deviceNumber = -1; - fprintf('Will wait for triggers on the main keyboard device.\n'); -end - -triggerCounter = 0; - -if strcmpi(cfg.testingDevice, 'scanner') - - msg = 'Waiting for trigger'; - talkToMe(cfg, msg) - - while triggerCounter < cfg.numTriggers - - keyCode = []; %#ok - - [~, keyCode] = KbPressWait(deviceNumber); - - if strcmp(KbName(keyCode), cfg.triggerKey) - - triggerCounter = triggerCounter + 1 ; - - msg = sprintf(' Trigger %i', triggerCounter); - talkToMe(cfg, msg) - - % we only wait if this is not the last trigger - if triggerCounter < cfg.numTriggers - pauseBetweenTriggers(cfg) + % Counts a certain number of triggers coming from the scanner before returning. + % + % Will print the count down in the command line and on the PTB window if one is + % opened. + % + % If the fMRI sequence RT is provided (cgf.MRI.repetitionTime) then it will wait + % for half a RT before starting to check for next trigger, otherwise it will + % wait 500 ms. + % + % When no deviceNumber is set then it will check the default device: this is + % probably only useful in debug as you will want to make sure you get the + % triggers coming from the scanner in a real case scenario. + + if nargin < 1 || isempty(cfg) + error('I need at least one input.'); + end + + if nargin < 2 || isempty(deviceNumber) + deviceNumber = -1; + fprintf('Will wait for triggers on the main keyboard device.\n'); + end + + triggerCounter = 0; + + if strcmpi(cfg.testingDevice, 'scanner') + + msg = 'Waiting for trigger'; + talkToMe(cfg, msg); + + while triggerCounter < cfg.numTriggers + + keyCode = []; %#ok + + [~, keyCode] = KbPressWait(deviceNumber); + + if strcmp(KbName(keyCode), cfg.triggerKey) + + triggerCounter = triggerCounter + 1 ; + + msg = sprintf(' Trigger %i', triggerCounter); + talkToMe(cfg, msg); + + % we only wait if this is not the last trigger + if triggerCounter < cfg.numTriggers + pauseBetweenTriggers(cfg); + end + end - end end end -end - function talkToMe(cfg, msg) -fprintf([msg , ' \n']); + fprintf([msg, ' \n']); -if isfield(cfg, 'win') - - DrawFormattedText(cfg.win, msg,... - 'center', 'center', cfg.textColor); - - Screen('Flip', cfg.win); - -end + if isfield(cfg, 'win') -end + DrawFormattedText(cfg.win, msg, ... + 'center', 'center', cfg.textColor); + Screen('Flip', cfg.win); -function pauseBetweenTriggers(cfg) -% we pause between triggers otherwise KbWait and KbPressWait might be too fast and could -% catch several triggers in one go. + end -waitTime = 0.5; -if ~isempty(cfg.MRI.repetitionTime) - waitTime = cfg.MRI.repetitionTime / 2; end -WaitSecs(waitTime); +function pauseBetweenTriggers(cfg) + % we pause between triggers otherwise KbWait and KbPressWait might be too fast and could + % catch several triggers in one go. -end \ No newline at end of file + waitTime = 0.5; + if ~isempty(cfg.MRI.repetitionTime) + waitTime = cfg.MRI.repetitionTime / 2; + end + + WaitSecs(waitTime); + +end From 670b0f1c0aa115b7737feabcdaac50664092e09a Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Wed, 22 Jul 2020 19:17:19 +0200 Subject: [PATCH 4/5] linting issues --- CPP_getResponseDemo.m | 106 ------------------ ...ace4meDemo.m => CPP_pressSpaceForMeDemo.m} | 2 +- demos/CPP_wait4TriggerDemo.m | 11 -- drawFixationCross.m | 2 +- initPTB.m | 3 +- testKeyboards.m | 2 +- wait4Trigger.m => waitForTrigger.m | 2 +- 7 files changed, 6 insertions(+), 122 deletions(-) delete mode 100644 CPP_getResponseDemo.m rename demos/{CPP_pressSpace4meDemo.m => CPP_pressSpaceForMeDemo.m} (92%) delete mode 100644 demos/CPP_wait4TriggerDemo.m rename wait4Trigger.m => waitForTrigger.m (97%) diff --git a/CPP_getResponseDemo.m b/CPP_getResponseDemo.m deleted file mode 100644 index 8c181a9..0000000 --- a/CPP_getResponseDemo.m +++ /dev/null @@ -1,106 +0,0 @@ -%% Demo showing how to use the getResponse function - -% This small script shows how to use the getReponse function -% (a wrapper around the KbQueue function from PTB) - -% start with a clean slate -clear; -clc; -if IsOctave - more off; % for a better display experience -end - -%% set parameters - -% cfg.responseBox would be the device number of the device used by the participant to give his/her -% response: like the button box in the scanner or a separate keyboard for a behavioral experiment -% -% cfg.keyboard would be the device number of the keyboard on which the experimenter will type or -% press the keys necessary to start or abort the experiment. - -% cfg.responseBox and cfg.keyboard can be different or the same. - -% If you want to know the device number of all the keyboards and responses -% boxes connected to the computer you can use the following code. -% [cfg.keyboardNumbers, cfg.keyboardNames] = GetKeyboardIndices - -% Using empty vectors should work for linux when to select the "main" -% keyboard. You might have to try some other values for Windows. To -% assigne a specific keyboard input the kb assigned value (see README) -cfg.keyboard = []; -cfg.responseBox = []; - -% We set which keys are "valid", any keys other than those will be ignored -expParameters.responseKey = {}; - -%% init - -% Keyboard -% Make sure keyboard mapping is the same on all supported operating systems -% Apple MacOS/X, MS-Windows and GNU/Linux: -KbName('UnifyKeyNames'); - -% we ask PTB to tell us which keyboard devices are connected to the computer -[cfg.keyboardNumbers, cfg.keyboardNames] = GetKeyboardIndices; - -cfg.keyboardNumbers; -cfg.keyboardNames; - -% Test that the keyboards are correctly configured -testKeyboards(cfg); - -% Give the time to the test key to be released and not listened -WaitSecs(1); - -fprintf('\nDuring the next 5 seconds we will collect responses on the following keys: \n\n'); -if isempty(expParameters.responseKey) - fprintf('\nALL KEYS\n\n'); -else - for iKey = 1:numel(expParameters.responseKey) - fprintf('\n%s', expParameters.responseKey{iKey}); - end - fprintf('\n\n'); -end - -%% Run demo - -% Create the keyboard queue to collect responses. -getResponse('init', cfg, expParameters, 1); - -% Start collecting responses for 5 seconds -% Each new key press is added to the queue of events recorded by KbQueue -startSecs = GetSecs(); -getResponse('start', cfg, expParameters, 1); - -% Here we wait for 5 seconds but are still collecting responses. -% So you could still be doing something else (presenting audio and visual stim) and -% still collect responses. -WaitSecs(5); - -% Check what keys were pressed (all of them) -responseEvents = getResponse('check', cfg, expParameters, 0); - -% The following line would only return key presses and not releases -% responseEvents = getResponse('check', cfg, expParameters, 1); - -% This can be used to flush the queue: empty all events that are still present in the queue -getResponse('flush', cfg, expParameters, 1); - -% If you wan to stop listening to key presses. -getResponse('stop', cfg, expParameters, 1); - -%% Now we look what keys were pressed and when -for iEvent = 1:size(responseEvents, 1) - - if responseEvents(iEvent).pressed - eventType = 'pressed'; - else - eventType = 'released'; - end - - fprintf('%s was %s at time %.3f seconds\n', ... - responseEvents(iEvent).key_name, ... - eventType, ... - responseEvents(iEvent).onset - startSecs); - -end diff --git a/demos/CPP_pressSpace4meDemo.m b/demos/CPP_pressSpaceForMeDemo.m similarity index 92% rename from demos/CPP_pressSpace4meDemo.m rename to demos/CPP_pressSpaceForMeDemo.m index 60e07d5..636ad77 100644 --- a/demos/CPP_pressSpace4meDemo.m +++ b/demos/CPP_pressSpaceForMeDemo.m @@ -6,4 +6,4 @@ KbName('UnifyKeyNames'); % press the key "space" to "start" the experiment -pressSpace4me; +pressSpaceForMe; diff --git a/demos/CPP_wait4TriggerDemo.m b/demos/CPP_wait4TriggerDemo.m deleted file mode 100644 index 55d0098..0000000 --- a/demos/CPP_wait4TriggerDemo.m +++ /dev/null @@ -1,11 +0,0 @@ -cd ..; - -cfg.device = 'Scanner'; - -cfg.numTriggers = 4; - -cfg.triggerKey = 'space'; - -KbName('UnifyKeyNames'); - -wait4Trigger(cfg); diff --git a/drawFixationCross.m b/drawFixationCross.m index 96cde03..fcd1a06 100644 --- a/drawFixationCross.m +++ b/drawFixationCross.m @@ -1,5 +1,5 @@ function drawFixationCross(cfg, expParameters, color) - % Define the parameters of the fixation cross in `cfg` and `expParameters` and this does the rest. + % Define the parameters of the fixation cross in `cfg` and `expParameters` Screen('DrawLines', ... cfg.win, ... diff --git a/initPTB.m b/initPTB.m index 4844917..9111802 100644 --- a/initPTB.m +++ b/initPTB.m @@ -197,7 +197,8 @@ function initDebug(cfg) function cfg = openWindow(cfg) if cfg.testingSmallScreen - [cfg.win, cfg.winRect] = Screen('OpenWindow', cfg.screen, cfg.backgroundColor, [0, 0, 480, 270]); + [cfg.win, cfg.winRect] = Screen('OpenWindow', cfg.screen, cfg.backgroundColor, ... + [0, 0, 480, 270]); else [cfg.win, cfg.winRect] = Screen('OpenWindow', cfg.screen, cfg.backgroundColor); end diff --git a/testKeyboards.m b/testKeyboards.m index 68b6ed4..00102a3 100644 --- a/testKeyboards.m +++ b/testKeyboards.m @@ -37,7 +37,7 @@ function throwError(keyCode, deviceNumber, keyboardType) text2 = '\nThese are the devices currently connected.\n\n'; - errorText = 'No key was pressed. Did you configure the keyboards properly? See message above for more info.'; + errorText = 'No key was pressed. Did you configure the keyboards properly? See above for info.'; if all(keyCode == 0) diff --git a/wait4Trigger.m b/waitForTrigger.m similarity index 97% rename from wait4Trigger.m rename to waitForTrigger.m index 6629662..a4ff96a 100644 --- a/wait4Trigger.m +++ b/waitForTrigger.m @@ -1,4 +1,4 @@ -function wait4Trigger(cfg, deviceNumber) +function waitForTrigger(cfg, deviceNumber) % Counts a certain number of triggers coming from the scanner before returning. % % Will print the count down in the command line and on the PTB window if one is From ae0b503a68f8b4e5d8ec3ae48fa5f9144cb03db2 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Wed, 22 Jul 2020 19:23:02 +0200 Subject: [PATCH 5/5] update name runTests --- tests/{runtests.m => runTests.m} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename tests/{runtests.m => runTests.m} (98%) diff --git a/tests/runtests.m b/tests/runTests.m similarity index 98% rename from tests/runtests.m rename to tests/runTests.m index 6facd13..3c9e3a3 100644 --- a/tests/runtests.m +++ b/tests/runTests.m @@ -1,4 +1,4 @@ -function results = runtests(pth) +function results = runTests(pth) % Run tests % List all the 'test_*.m' files located in the same directory as this % function, run them and keep track of how many passed, failed or are