From b4af2baf12aef3114efd12affe8ea8aace189e4c Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Tue, 28 Jul 2020 23:21:21 +0200 Subject: [PATCH 01/11] mh_lint --- collectAndSaveResponses.m | 12 ++-- eyeTracker.m | 102 +++++++++++++++++----------------- getResponse.m | 114 +++++++++++++++++++------------------- initPTB.m | 8 +-- setDefaultsPTB.m | 2 +- waitForTrigger.m | 34 ++++++------ 6 files changed, 135 insertions(+), 137 deletions(-) diff --git a/collectAndSaveResponses.m b/collectAndSaveResponses.m index c8f48c9..22e3411 100644 --- a/collectAndSaveResponses.m +++ b/collectAndSaveResponses.m @@ -1,17 +1,17 @@ function responseEvents = collectAndSaveResponses(cfg, expParameters, logFile, experimentStart) - + responseEvents = getResponse('check', cfg.keyboard.responseBox, cfg); - + if isfield(responseEvents(1), 'onset') && ~isempty(responseEvents(1).onset) - + for iResp = 1:size(responseEvents, 1) responseEvents(iResp).onset = ... responseEvents(iResp).onset - experimentStart; end - + responseEvents.fileID = logFile.fileID; responseEvents.extraColumns = logFile.extraColumns; saveEventsFile('save', expParameters, responseEvents); - + end -end \ No newline at end of file +end diff --git a/eyeTracker.m b/eyeTracker.m index fa9c392..5f47502 100755 --- a/eyeTracker.m +++ b/eyeTracker.m @@ -1,22 +1,22 @@ function [el, edfFile] = eyeTracker(input, cfg, expParameters, varargin) - + if ~cfg.eyeTracker - + el = []; - + else - + switch input - + 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); - + % calibration has silver background with black targets, sound and smaller % targets el.backgroundcolour = [192 192 192, (cfg.win)]; @@ -25,15 +25,15 @@ el.calibrationtargetsize = 1; el.calibrationtargetwidth = 0.5; el.displayCalResults = 1; - + % 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. - + % make sure EL is initialized. ELinit = Eyelink('Initialize'); if ELinit ~= 0 @@ -42,7 +42,7 @@ Screen('CloseAll'); return end - + % make sure we're still connected. ELconnection = Eyelink('IsConnected'); if ELconnection ~= 1 @@ -51,45 +51,45 @@ 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'''); - + % 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) @@ -120,17 +120,17 @@ % 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); @@ -142,13 +142,13 @@ % % 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'); @@ -157,7 +157,7 @@ % % 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 @@ -166,17 +166,17 @@ 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 STIMULATION 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 @@ -185,20 +185,20 @@ 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); @@ -215,28 +215,28 @@ catch fprintf('Problem receiving data file ''%s''\n', edfFileName); end - + Eyelink('shutdown'); - + end - + end - + end %% subfunctions for iView function ivx = eyeTrackInit(expParameters) % initialize iView eye tracker - + ivx = []; - + if cfg.eyeTracker - + host = expParameters.Eyetracker.Host; port = expParameters.Eyetracker.Port; window = expParameters.Eyetracker.Window; - + % original: ivx=iviewxinitdefaults(window, 9 , host, port); ivx = iviewxinitdefaults2(window, 9, [], host, port); ivx.backgroundColour = 0; @@ -268,12 +268,12 @@ function eyeTrackStart(ivx, expParameters) function eyeTrackStop(ivx, expParameters) % stop iView eye tracker - + if cfg.eyeTracker - + % stop tracker iViewX('stoprecording', ivx); - + % save data file thedatestr = datestr(now, 'yyyy-mm-dd_HH.MM'); strFile = fullfile(OutputDir, ... @@ -283,8 +283,8 @@ function eyeTrackStop(ivx, expParameters) expParameters.Direction, '_', ... thedatestr, '.idf"']); iViewX('datafile', ivx, strFile); - + % close iView connection iViewX('closeconnection', ivx); end -end \ No newline at end of file +end diff --git a/getResponse.m b/getResponse.m index 595513c..82823b5 100644 --- a/getResponse.m +++ b/getResponse.m @@ -16,12 +16,12 @@ % - 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) - % -- can check for demand to abort if the escapeKey is listed in the + % -- can check for demand to abort if the escapeKey is listed in the % Keys of interest. - % -- can only check for demands to abort when getResponse('check') is called: + % -- can only check for demands to abort when getResponse('check') is called: % so there will be a delay between the key press and the experiment stopping - % -- abort errors send specific signals that allow the catch to get - % them and allows us to "close" nicely + % -- abort errors send specific signals that allow the catch to get + % them and allows us to "close" nicely % - flush: empties the queue of events in case you want to restart from a clean % queue % - stop: stops listening to key presses @@ -51,124 +51,124 @@ % 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 < 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 - + talkToMe(action, cfg); - + 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 - + keysOfInterest = zeros(1, 256); - + 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; - + else - + 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) ... @@ -178,40 +178,40 @@ function checkAbortGetResponse(responseEvents, cfg) end function talkToMe(action, cfg) - + verbose = false; if isfield(cfg, 'verbose') verbose = cfg.verbose; end 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 - + if verbose fprintf('\n %s\n\n', msg); - + end - + end diff --git a/initPTB.m b/initPTB.m index 05306a5..da7fbc7 100644 --- a/initPTB.m +++ b/initPTB.m @@ -70,9 +70,9 @@ % window size info [cfg.winWidth, cfg.winHeight] = WindowSize(cfg.win); -% if strcmpi(cfg.stimPosition, 'mri') -% cfg.winRect(1, 4) = cfg.winRect(1, 4) * 2 / 3; -% end + % if strcmpi(cfg.stimPosition, 'mri') + % 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; @@ -116,7 +116,7 @@ function initDebug(cfg) Screen('Preference', 'SkipSyncTests', 2); Screen('Preference', 'Verbosity', 0); - Screen('Preference', 'SuppressAllWarnings', 1); + Screen('Preference', 'SuppressAllWarnings', 1); fprintf('\n\n\n\n'); fprintf('########################################\n'); diff --git a/setDefaultsPTB.m b/setDefaultsPTB.m index 78d3275..c2a222c 100644 --- a/setDefaultsPTB.m +++ b/setDefaultsPTB.m @@ -47,7 +47,7 @@ end if isfield(cfg, 'testingDevice') && strcmpi(cfg.testingDevice, 'mri') - fieldsToSet.MRI.repetitionTime = []; + fieldsToSet.bids.MRI.RepetitionTime = []; end cfg = setDefaults(cfg, fieldsToSet); diff --git a/waitForTrigger.m b/waitForTrigger.m index de8f4c1..cf4892c 100644 --- a/waitForTrigger.m +++ b/waitForTrigger.m @@ -71,41 +71,39 @@ function pauseBetweenTriggers(cfg) % catch several triggers in one go. waitTime = 0.5; - if ~isempty(cfg.MRI.repetitionTime) - waitTime = cfg.MRI.repetitionTime / 2; + if ~isempty(cfg.bids.MRI.repetitionTime) + waitTime = cfg.bids.MRI.repetitionTime / 2; end WaitSecs(waitTime); end - - % function [MyPort] = WaitForScanTrigger(Parameters) -% +% % %% Opening IOPort % PortSettings = sprintf('BaudRate=115200 InputBufferSize=10000 ReceiveTimeout=60'); % PortSpec = FindSerialPort([], 1); -% +% % % Open port portSpec with portSettings, return handle: % MyPort = IOPort('OpenSerialPort', PortSpec, PortSettings); -% +% % % Start asynchronous background data collection and timestamping. Use % % blocking mode for reading data -- easier on the system: % AsyncSetup = sprintf('BlockingBackgroundRead=1 ReadFilterFlags=0 StartBackgroundRead=1'); % IOPort('ConfigureSerialPort', MyPort, AsyncSetup); -% +% % % Read once to warm up % WaitSecs(1); % IOPort('Read', MyPort); -% +% % nTrig = 0; -% +% % %% waiting for dummie triggers from the scanner % while nTrig <= Parameters.Dummies -% +% % [PktData, TReceived] = IOPort('Read', MyPort); -% +% % % it is checked if something was received via trigger_port % % oldtrigger is there so 'number' is only updated when something new is % % received via trigger_port (normally you receive a "small series" of data at @@ -113,21 +111,21 @@ function pauseBetweenTriggers(cfg) % if isempty(PktData) % TReceived = 0; % end -% +% % if TReceived && (oldtrigger == 0) % Number = 1; % else % Number = 0; % end -% +% % oldtrigger = TReceived; -% +% % if Number % nTrig = nTrig + 1; % Number = 0; %#ok % end -% +% % end -% +% % end -% +% From f32dcbfc9f5f4d088e9d0d6fdb999895a5addb36 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Wed, 29 Jul 2020 08:11:06 +0200 Subject: [PATCH 02/11] replace expParameters by cfg --- collectAndSaveResponses.m | 4 ++-- drawFixationCross.m | 4 ++-- eyeTracker.m | 38 +++++++++++++++++++------------------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/collectAndSaveResponses.m b/collectAndSaveResponses.m index 22e3411..c26677f 100644 --- a/collectAndSaveResponses.m +++ b/collectAndSaveResponses.m @@ -1,4 +1,4 @@ -function responseEvents = collectAndSaveResponses(cfg, expParameters, logFile, experimentStart) +function responseEvents = collectAndSaveResponses(cfg, logFile, experimentStart) responseEvents = getResponse('check', cfg.keyboard.responseBox, cfg); @@ -11,7 +11,7 @@ responseEvents.fileID = logFile.fileID; responseEvents.extraColumns = logFile.extraColumns; - saveEventsFile('save', expParameters, responseEvents); + saveEventsFile('save', cfg, responseEvents); end end diff --git a/drawFixationCross.m b/drawFixationCross.m index fcd1a06..4bdb911 100644 --- a/drawFixationCross.m +++ b/drawFixationCross.m @@ -1,10 +1,10 @@ -function drawFixationCross(cfg, expParameters, color) +function drawFixationCross(cfg, color) % Define the parameters of the fixation cross in `cfg` and `expParameters` Screen('DrawLines', ... cfg.win, ... cfg.allCoords, ... - expParameters.lineWidthPix, ... + cfg.lineWidthPix, ... color, ... [cfg.center(1) cfg.center(2)], 1); diff --git a/eyeTracker.m b/eyeTracker.m index 5f47502..eb3149e 100755 --- a/eyeTracker.m +++ b/eyeTracker.m @@ -1,4 +1,4 @@ -function [el, edfFile] = eyeTracker(input, cfg, expParameters, varargin) +function [el, edfFile] = eyeTracker(input, cfg, varargin) if ~cfg.eyeTracker @@ -188,7 +188,7 @@ case 'Shutdown' - edfFileName = expParameters.fileName.eyetracker; + edfFileName = cfg.fileName.eyetracker; edfFile = 'demo.edf'; % STEP 6 @@ -203,14 +203,14 @@ try fprintf('Receiving data file ''%s''\n', edfFileName); status = Eyelink('ReceiveFile', '', ... - [expParameters.outputDir, filesep, 'eyetracker', filesep, edfFileName]); + [cfg.outputDir, filesep, 'eyetracker', filesep, edfFileName]); if status > 0 fprintf('ReceiveFile status %d\n', status); end - if 2 == exist([expParameters.outputDir, filesep, 'eyetracker', ... + if 2 == exist([cfg.outputDir, filesep, 'eyetracker', ... filesep, edfFileName], 'file') fprintf('Data file ''%s'' can be found in ''%s''\n', edfFileName, ... - [expParameters.outputDir, filesep, 'eyetracker', filesep]); + [cfg.outputDir, filesep, 'eyetracker', filesep]); end catch fprintf('Problem receiving data file ''%s''\n', edfFileName); @@ -226,16 +226,16 @@ %% subfunctions for iView -function ivx = eyeTrackInit(expParameters) +function ivx = eyeTrackInit(cfg) % initialize iView eye tracker ivx = []; if cfg.eyeTracker - host = expParameters.Eyetracker.Host; - port = expParameters.Eyetracker.Port; - window = expParameters.Eyetracker.Window; + host = cfg.eyetracker.Host; + port = cfg.eyetracker.Port; + window = cfg.eyetracker.Window; % original: ivx=iviewxinitdefaults(window, 9 , host, port); ivx = iviewxinitdefaults2(window, 9, [], host, port); @@ -248,7 +248,7 @@ end end -function eyeTrackStart(ivx, expParameters) +function eyeTrackStart(ivx, cfg) % start iView eye tracker if cfg.eyeTracker % to clear data buffer @@ -258,15 +258,15 @@ function eyeTrackStart(ivx, expParameters) iViewX('message', ivx, ... [ ... 'Start_Ret_', ... - 'Subj_', expParameters.Subj, '_', ... - 'Run', num2str(expParameters.Session(end)), '_', ... - expParameters.Apperture, '_', ... - expParameters.Direction]); + 'Subj_', cfg.Subj, '_', ... + 'Run', num2str(cfg.Session(end)), '_', ... + cfg.Apperture, '_', ... + cfg.Direction]); iViewX('incrementsetnumber', ivx, 0); end end -function eyeTrackStop(ivx, expParameters) +function eyeTrackStop(ivx, cfg) % stop iView eye tracker if cfg.eyeTracker @@ -277,10 +277,10 @@ function eyeTrackStop(ivx, expParameters) % save data file thedatestr = datestr(now, 'yyyy-mm-dd_HH.MM'); strFile = fullfile(OutputDir, ... - [expParameters.Subj, ... - '_run', num2str(expParameters.Session(end)), '_', ... - expParameters.Apperture, '_', ... - expParameters.Direction, '_', ... + [cfg.Subj, ... + '_run', num2str(cfg.Session(end)), '_', ... + cfg.Apperture, '_', ... + cfg.Direction, '_', ... thedatestr, '.idf"']); iViewX('datafile', ivx, strFile); From 4d4642c3907177bb61948b0e7f4057c0176e0528 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Wed, 29 Jul 2020 13:13:35 +0200 Subject: [PATCH 03/11] update setDefaultsPTB to new cfg --- setDefaultsPTB.m | 47 +++++++---- tests/test_setDefaultsPTB.m | 157 +++++++++++++++++++++++------------- 2 files changed, 134 insertions(+), 70 deletions(-) diff --git a/setDefaultsPTB.m b/setDefaultsPTB.m index c2a222c..0dfda94 100644 --- a/setDefaultsPTB.m +++ b/setDefaultsPTB.m @@ -1,5 +1,8 @@ function cfg = setDefaultsPTB(cfg) - + % cfg = setDefaultsPTB(cfg) + % + % Set some defaults values if none have been set before. + if nargin < 1 cfg = struct; end @@ -13,21 +16,21 @@ fieldsToSet.keyboard.responseKey = {}; fieldsToSet.keyboard.escapeKey = 'ESCAPE'; - fieldsToSet.debug = true; - fieldsToSet.testingTranspScreen = true; - fieldsToSet.testingSmallScreen = true; + fieldsToSet.debug.do = true; + fieldsToSet.debug.transpWin = true; + fieldsToSet.debug.smallWin = true; - fieldsToSet.backgroundColor = [0 0 0]; + fieldsToSet.color.background = [0 0 0]; % text defaults fieldsToSet.text.font = 'Courier New'; fieldsToSet.text.size = 18; fieldsToSet.text.style = 1; - fieldsToSet.monitorWidth = 42; - fieldsToSet.screenDistance = 134; + fieldsToSet.screen.monitorWidth = 42; + fieldsToSet.screen.monitorDistance = 134; - if isfield(cfg, 'initAudio') && cfg.initAudio + if isfield(cfg, 'audio') && cfg.audio.do fieldsToSet.audio.fs = 44800; fieldsToSet.audio.channels = 2; @@ -47,7 +50,7 @@ end if isfield(cfg, 'testingDevice') && strcmpi(cfg.testingDevice, 'mri') - fieldsToSet.bids.MRI.RepetitionTime = []; + fieldsToSet.bids.mri.RepetitionTime = []; end cfg = setDefaults(cfg, fieldsToSet); @@ -58,20 +61,36 @@ end function structure = setDefaults(structure, fieldsToSet) - % loop through the defaults fiels to set and update if they don't exist + % structure = setDefaultFields(structure, fieldsToSet) + % + % recursively loop through the fields of a structure and sets a value if they don't exist + % + + fieldsToSet = orderfields(fieldsToSet); names = fieldnames(fieldsToSet); for i = 1:numel(names) thisField = fieldsToSet.(names{i}); - structure = setFieldToIfNotPresent( ... - structure, ... - names{i}, ... - thisField); + + if isfield(structure, names{i}) && isstruct(structure.(names{i})) + + structure.(names{i}) = ... + setDefaultFields(structure.(names{i}), fieldsToSet.(names{i})); + + else + + structure = setFieldToIfNotPresent( ... + structure, ... + names{i}, ... + thisField); + end end + structure = orderfields(structure); + end function structure = setFieldToIfNotPresent(structure, fieldName, value) diff --git a/tests/test_setDefaultsPTB.m b/tests/test_setDefaultsPTB.m index d8eb08b..79a991f 100644 --- a/tests/test_setDefaultsPTB.m +++ b/tests/test_setDefaultsPTB.m @@ -1,56 +1,101 @@ -%% test basic cfg creation - -% set up -cfgToTest = struct( ... - 'testingDevice', 'pc', ... - 'debug', true, ... - 'testingTranspScreen', true, ... - 'testingSmallScreen', true, ... - 'backgroundColor', [0 0 0], ... - 'text', struct('font', 'Courier New', 'size', 18, 'style', 1), ... - 'monitorWidth', 42, ... - 'screenDistance', 134); - -cfgToTest.keyboard.keyboard = []; -cfgToTest.keyboard.responseBox = []; -cfgToTest.keyboard.responseKey = {}; -cfgToTest.keyboard.escapeKey = 'ESCAPE'; - -cfgToTest = orderfields(cfgToTest); - -% test -cfg = setDefaultsPTB; -assert(isequal(cfg, cfgToTest)); - -%% test that values are not overwritten -clear cfg; -cfg = struct('monitorWidth', 36); - -cfgToTest.monitorWidth = 36; - -cfg = setDefaultsPTB(cfg); -assert(isequal(cfg, cfgToTest)); - -cfgToTest.monitorWidth = 42; - -%% test with audio init - -% set up -cfgToTest.initAudio = 1; -cfgToTest.audio = struct( ... - 'fs', 44800, ... - 'channels', 2, ... - 'initVolume', 1, ... - 'requestedLatency', 3, ... - 'repeat', 1, ... - 'startCue', 0, ... - 'waitForDevice', 1); - -cfgToTest = orderfields(cfgToTest); - -clear cfg; -cfg.initAudio = 1; - -% test -cfg = setDefaultsPTB(cfg); -assert(isequal(cfg, cfgToTest)); +function test_setDefaultsPTB() + + %% test basic cfg creation + + % test data + expectedCFG = returnExpectedCFG(); + + % test + cfg = setDefaultsPTB; + testSubFields(expectedCFG, cfg) + + %% test that values are not overwritten + + clear cfg + + % test data + expectedCFG = returnExpectedCFG(); + expectedCFG.screen.monitorWidth = 36; + + % set up + cfg.screen.monitorWidth = 36; + + % test + cfg = setDefaultsPTB(cfg); + testSubFields(expectedCFG, cfg) + + %% test with audio init + + clear cfg + + % test data + expectedCFG = returnExpectedCFG(); + expectedCFG.audio = struct( ... + 'do', true, ... + 'fs', 44800, ... + 'channels', 2, ... + 'initVolume', 1, ... + 'requestedLatency', 3, ... + 'repeat', 1, ... + 'startCue', 0, ... + 'waitForDevice', 1); + + % set up + cfg.audio.do = 1; + + % test + cfg = setDefaultsPTB(cfg); + testSubFields(expectedCFG, cfg) + + +end + + +function expectedCFG = returnExpectedCFG() + + expectedCFG = struct( ... + 'testingDevice', 'pc', ... + 'debug', struct('do', true, 'transpWin', true, 'smallWin', true), ... + 'color', struct( ... + 'background', [0 0 0]), ... + 'text', struct('font', 'Courier New', 'size', 18, 'style', 1), ... + 'screen', struct( ... + 'monitorWidth', 42, ... + 'monitorDistance', 134)); + + expectedCFG.keyboard.keyboard = []; + expectedCFG.keyboard.responseBox = []; + expectedCFG.keyboard.responseKey = {}; + expectedCFG.keyboard.escapeKey = 'ESCAPE'; + +end + +function testSubFields(expectedStructure, cfg) + % check that that the structures match + % if it fails it check from which subfield the error comes from + + try + + assert(isequal(expectedStructure, cfg)); + + catch ME + + if isstruct(expectedStructure) + + names = fieldnames(expectedStructure); + + for i = 1:numel(names) + + disp(names{i}); + testSubFields(expectedStructure.(names{i}), cfg.(names{i})); + + end + + end + + expectedStructure; + cfg; + + rethrow(ME); + end +end From 8e046e1d9bcf1a5690e625b7c2577586be1dd94a Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Wed, 29 Jul 2020 13:43:52 +0200 Subject: [PATCH 04/11] update functions to new cfg --- computeFOV.m | 8 +++++ degToPix.m | 2 +- drawFixationCross.m | 4 +-- eyeTracker.m | 2 +- initPTB.m | 71 ++++++++++++--------------------------------- waitForTrigger.m | 14 ++++----- 6 files changed, 37 insertions(+), 64 deletions(-) create mode 100644 computeFOV.m diff --git a/computeFOV.m b/computeFOV.m new file mode 100644 index 0000000..7cb8c17 --- /dev/null +++ b/computeFOV.m @@ -0,0 +1,8 @@ +function FOV = computeFOV(cfg) + % 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)); + +end \ No newline at end of file diff --git a/degToPix.m b/degToPix.m index 00d0367..d21fec1 100644 --- a/degToPix.m +++ b/degToPix.m @@ -6,6 +6,6 @@ deg = getfield(structure, fieldName); %#ok structure = setfield(structure, [fieldName 'Pix'], ... - floor(cfg.ppd * deg)) ; %#ok + floor(cfg.screen.ppd * deg)) ; %#ok end diff --git a/drawFixationCross.m b/drawFixationCross.m index 4bdb911..38edc47 100644 --- a/drawFixationCross.m +++ b/drawFixationCross.m @@ -2,10 +2,10 @@ function drawFixationCross(cfg, color) % Define the parameters of the fixation cross in `cfg` and `expParameters` Screen('DrawLines', ... - cfg.win, ... + cfg.screen.win, ... cfg.allCoords, ... cfg.lineWidthPix, ... color, ... - [cfg.center(1) cfg.center(2)], 1); + [cfg.screen.center(1) cfg.screen.center(2)], 1); end diff --git a/eyeTracker.m b/eyeTracker.m index eb3149e..bca25b7 100755 --- a/eyeTracker.m +++ b/eyeTracker.m @@ -1,6 +1,6 @@ function [el, edfFile] = eyeTracker(input, cfg, varargin) - if ~cfg.eyeTracker + if ~cfg.eyeTracker.do el = []; diff --git a/initPTB.m b/initPTB.m index da7fbc7..f857338 100644 --- a/initPTB.m +++ b/initPTB.m @@ -14,34 +14,13 @@ % - 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. checkPtbVersion(); + + pth = fileparts(mfilename('fullpath')); + addpath(fullfile(pth, 'subfun')); % For octave: to avoid displaying messenging one screen at a time more off; @@ -63,36 +42,32 @@ %% Visual % Get the screen numbers and draw to the external screen if avaliable - cfg.screen = max(Screen('Screens')); + cfg.screen.idx = max(Screen('Screens')); cfg = openWindow(cfg); % window size info - [cfg.winWidth, cfg.winHeight] = WindowSize(cfg.win); - - % if strcmpi(cfg.stimPosition, 'mri') - % cfg.winRect(1, 4) = cfg.winRect(1, 4) * 2 / 3; - % end + [cfg.screen.winWidth, cfg.screen.winHeight] = WindowSize(cfg.screen.win); % Get the Center of the Screen - cfg.center = [cfg.winRect(3), cfg.winRect(4)] / 2; + cfg.screen.center = [cfg.screen.winRect(3), cfg.screen.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 - cfg.FOV = computeFOV(cfg); - cfg.ppd = cfg.winRect(3) / cfg.FOV; + cfg.screen.FOV = computeFOV(cfg); + cfg.screen.ppd = cfg.winRect(3) / cfg.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; + cfg.screen.ifi = Screen('GetFlipInterval', cfg.win); + cfg.screen.monRefresh = 1 / cfg.ifi; % Set priority for script execution to realtime priority: - Priority(MaxPriority(cfg.win)); + Priority(MaxPriority(cfg.screen.win)); %% Warm up some functions % Do dummy calls to GetSecs, WaitSecs, KbCheck to make sure @@ -102,9 +77,6 @@ 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 @@ -112,7 +84,7 @@ function initDebug(cfg) % init PTB with different options in concordance to the debug Parameters Screen('Preference', 'SkipSyncTests', 0); - if cfg.debug + if cfg.debug.do Screen('Preference', 'SkipSyncTests', 2); Screen('Preference', 'Verbosity', 0); @@ -128,7 +100,7 @@ function initDebug(cfg) end - if cfg.testingTranspScreen + if cfg.debug.transpWin PsychDebugWindowConfiguration; end @@ -147,7 +119,7 @@ function initDebug(cfg) function cfg = initAudio(cfg) - if cfg.initAudio + if cfg.audio.do InitializePsychSound(1); @@ -194,23 +166,16 @@ function initDebug(cfg) function cfg = openWindow(cfg) - if cfg.testingSmallScreen - [cfg.win, cfg.winRect] = Screen('OpenWindow', cfg.screen, cfg.backgroundColor, ... + if cfg.debug.smallWin + [cfg.screen.win, cfg.screen.winRect] = Screen('OpenWindow', cfg.screen.idx, cfg.color.background, ... [0, 0, 480, 270]); else - [cfg.win, cfg.winRect] = Screen('OpenWindow', cfg.screen, cfg.backgroundColor); + [cfg.screen.win, cfg.screen.winRect] = Screen('OpenWindow', cfg.screen.idx, cfg.color.background); 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); - -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)); + Screen('BlendFunction', cfg.screen.win, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); end diff --git a/waitForTrigger.m b/waitForTrigger.m index cf4892c..1f8ae3f 100644 --- a/waitForTrigger.m +++ b/waitForTrigger.m @@ -28,7 +28,7 @@ function waitForTrigger(cfg, deviceNumber) msg = 'Waiting for trigger'; talkToMe(cfg, msg); - while triggerCounter < cfg.numTriggers + while triggerCounter < cfg.triggerNb keyCode = []; %#ok @@ -42,7 +42,7 @@ function waitForTrigger(cfg, deviceNumber) talkToMe(cfg, msg); % we only wait if this is not the last trigger - if triggerCounter < cfg.numTriggers + if triggerCounter < cfg.triggerNb pauseBetweenTriggers(cfg); end @@ -55,12 +55,12 @@ function talkToMe(cfg, msg) fprintf([msg, ' \n']); - if isfield(cfg, 'win') + if isfield(cfg, 'screen') && isfield(cfg.screen, 'win') - DrawFormattedText(cfg.win, msg, ... + DrawFormattedText(cfg.screen.win, msg, ... 'center', 'center', cfg.text.color); - Screen('Flip', cfg.win); + Screen('Flip', cfg.screen.win); end @@ -71,8 +71,8 @@ function pauseBetweenTriggers(cfg) % catch several triggers in one go. waitTime = 0.5; - if ~isempty(cfg.bids.MRI.repetitionTime) - waitTime = cfg.bids.MRI.repetitionTime / 2; + if ~isempty(cfg.mri.repetitionTime) + waitTime = cfg.mri.repetitionTime / 2; end WaitSecs(waitTime); From a11f1544c1695e3da42a2d43134848514c4b5722 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Wed, 29 Jul 2020 13:44:04 +0200 Subject: [PATCH 05/11] move errors in subfolder --- errorAbort.m => errors/errorAbort.m | 0 errorAbortGetReponse.m => errors/errorAbortGetReponse.m | 0 .../errorRestrictedKeysGetReponse.m | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename errorAbort.m => errors/errorAbort.m (100%) rename errorAbortGetReponse.m => errors/errorAbortGetReponse.m (100%) rename errorRestrictedKeysGetReponse.m => errors/errorRestrictedKeysGetReponse.m (100%) diff --git a/errorAbort.m b/errors/errorAbort.m similarity index 100% rename from errorAbort.m rename to errors/errorAbort.m diff --git a/errorAbortGetReponse.m b/errors/errorAbortGetReponse.m similarity index 100% rename from errorAbortGetReponse.m rename to errors/errorAbortGetReponse.m diff --git a/errorRestrictedKeysGetReponse.m b/errors/errorRestrictedKeysGetReponse.m similarity index 100% rename from errorRestrictedKeysGetReponse.m rename to errors/errorRestrictedKeysGetReponse.m From cd2357b07af9bc7663b819f1fe699b05ac1b942c Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Wed, 29 Jul 2020 13:44:25 +0200 Subject: [PATCH 06/11] create manual test folder --- tests/manualTests/miss_hit.cfg | 3 +++ tests/{getResponseTest.m => manualTests/test_getResponse.m} | 0 2 files changed, 3 insertions(+) create mode 100644 tests/manualTests/miss_hit.cfg rename tests/{getResponseTest.m => manualTests/test_getResponse.m} (100%) diff --git a/tests/manualTests/miss_hit.cfg b/tests/manualTests/miss_hit.cfg new file mode 100644 index 0000000..30aa3b8 --- /dev/null +++ b/tests/manualTests/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 diff --git a/tests/getResponseTest.m b/tests/manualTests/test_getResponse.m similarity index 100% rename from tests/getResponseTest.m rename to tests/manualTests/test_getResponse.m From 6d73c938cdfaf8369abbdc7ce707cc5699e908d4 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Wed, 29 Jul 2020 13:44:46 +0200 Subject: [PATCH 07/11] move dev and template material in separate folder --- devSandbox.m => dev/devSandbox.m | 38 +++++++++++++------------------- 1 file changed, 15 insertions(+), 23 deletions(-) rename devSandbox.m => dev/devSandbox.m (85%) diff --git a/devSandbox.m b/dev/devSandbox.m similarity index 85% rename from devSandbox.m rename to dev/devSandbox.m index cc6642f..29165b3 100644 --- a/devSandbox.m +++ b/dev/devSandbox.m @@ -23,13 +23,8 @@ % 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; + cfg.color.background = [127 127 27]; % Init PTB, see the Sub-Functions below cfg = devSandbox_initPTB(cfg); @@ -40,7 +35,7 @@ % ------------------------------------------------------------------------- % Define black and white - white = WhiteIndex(cfg.screen); + white = WhiteIndex(cfg.screen.idx); grey = white / 2; inc = white - grey; @@ -95,7 +90,7 @@ mask(:, :, 2) = grating .* contrast; % Make our grating mask texture - gratingMaskTex = Screen('MakeTexture', cfg.win, mask); + gratingMaskTex = Screen('MakeTexture', cfg.screen.win, mask); % 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 @@ -104,15 +99,15 @@ noise = rand(round(visibleSize / 2)) .* white; % Make our noise texture - noiseTexture = Screen('MakeTexture', cfg.win, noise); + noiseTexture = Screen('MakeTexture', cfg.screen.win, noise); % Make a destination rectangle for our textures and center this on the % screen dstRect = [0 0 visibleSize visibleSize]; - dstRect = CenterRect(dstRect, cfg.winRect); + dstRect = CenterRect(dstRect, cfg.screen.winRect); % Calculate the wait duration - waitDuration = waitframes * cfg.ifi; + waitDuration = waitframes * cfg.screen.ifi; % Recompute pixPerCycle, this time without the ceil() operation from above. % Otherwise we will get wrong drift speed due to rounding errors @@ -123,7 +118,7 @@ shiftPerFrame = cyclesPerSecond * pixPerCycle * waitDuration; % Sync us to the vertical retrace - vbl = Screen('Flip', cfg.win); + vbl = Screen('Flip', cfg.screen.win); % Set the frame counter to zero, we need this to 'drift' our grating frameCounter = 0; @@ -142,13 +137,13 @@ srcRect = [xoffset 0 xoffset + visibleSize visibleSize]; % Draw noise texture to the screen - Screen('DrawTexture', cfg.win, noiseTexture, [], dstRect, []); + Screen('DrawTexture', cfg.screen.win, noiseTexture, [], dstRect, []); % Draw grating mask - Screen('DrawTexture', cfg.win, gratingMaskTex, srcRect, dstRect, []); + Screen('DrawTexture', cfg.screen.win, gratingMaskTex, srcRect, dstRect, []); % Flip to the screen on the next vertical retrace - vbl = Screen('Flip', cfg.win, vbl + (waitframes - 0.5) * cfg.ifi); + vbl = Screen('Flip', cfg.screen.win, vbl + (waitframes - 0.5) * cfg.screen.ifi); end @@ -182,22 +177,19 @@ PsychDefaultSetup(2); % Get the screen numbers and draw to the external screen if avaliable - cfg.screen = max(Screen('Screens')); + cfg.screen.idx = max(Screen('Screens')); % Open an on screen window - [cfg.win, cfg.winRect] = Screen('OpenWindow', cfg.screen, cfg.backgroundColor); + [cfg.screen.win, cfg.screen.winRect] = Screen('OpenWindow', cfg.screen.idx, cfg.color.background); % Get the size of the on screen window - [cfg.winWidth, cfg.winHeight] = WindowSize(cfg.win); + [cfg.screen.winWidth, cfg.screen.winHeight] = WindowSize(cfg.screen.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; + cfg.screen.ifi = Screen('GetFlipInterval', cfg.screen.win); % Set up alpha-blending for smooth (anti-aliased) lines - Screen('BlendFunction', cfg.win, 'GL_SRC_ALPHA', 'GL_ONE_MINUS_SRC_ALPHA'); + Screen('BlendFunction', cfg.screen.win, 'GL_SRC_ALPHA', 'GL_ONE_MINUS_SRC_ALPHA'); end From 250d9a80342f8dd4ee54687c00ad04bacb49e335 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Wed, 29 Jul 2020 13:50:45 +0200 Subject: [PATCH 08/11] mh linter fixes --- computeFOV.m | 4 ++-- dev/miss_hit.cfg | 3 +++ initPTB.m | 9 ++++---- setDefaultsPTB.m | 4 ++-- tests/test_setDefaultsPTB.m | 46 ++++++++++++++++++------------------- 5 files changed, 34 insertions(+), 32 deletions(-) create mode 100644 dev/miss_hit.cfg diff --git a/computeFOV.m b/computeFOV.m index 7cb8c17..3044a85 100644 --- a/computeFOV.m +++ b/computeFOV.m @@ -2,7 +2,7 @@ % 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)); -end \ No newline at end of file +end diff --git a/dev/miss_hit.cfg b/dev/miss_hit.cfg new file mode 100644 index 0000000..d282920 --- /dev/null +++ b/dev/miss_hit.cfg @@ -0,0 +1,3 @@ +line_length: 120 +suppress_rule: "copyright_notice" +suppress_rule: "naming_functions" diff --git a/initPTB.m b/initPTB.m index f857338..7f55b45 100644 --- a/initPTB.m +++ b/initPTB.m @@ -18,7 +18,7 @@ % checkPtbVersion(); - + pth = fileparts(mfilename('fullpath')); addpath(fullfile(pth, 'subfun')); @@ -77,7 +77,6 @@ WaitSecs(0.1); GetSecs; - end function initDebug(cfg) @@ -167,10 +166,12 @@ function initDebug(cfg) function cfg = openWindow(cfg) if cfg.debug.smallWin - [cfg.screen.win, cfg.screen.winRect] = Screen('OpenWindow', cfg.screen.idx, cfg.color.background, ... + [cfg.screen.win, cfg.screen.winRect] = ... + Screen('OpenWindow', cfg.screen.idx, cfg.color.background, ... [0, 0, 480, 270]); else - [cfg.screen.win, cfg.screen.winRect] = Screen('OpenWindow', cfg.screen.idx, cfg.color.background); + [cfg.screen.win, cfg.screen.winRect] = ... + Screen('OpenWindow', cfg.screen.idx, cfg.color.background); end % Enable alpha-blending, set it to a blend equation useable for linear diff --git a/setDefaultsPTB.m b/setDefaultsPTB.m index 0dfda94..a8a8073 100644 --- a/setDefaultsPTB.m +++ b/setDefaultsPTB.m @@ -1,8 +1,8 @@ function cfg = setDefaultsPTB(cfg) % cfg = setDefaultsPTB(cfg) - % + % % Set some defaults values if none have been set before. - + if nargin < 1 cfg = struct; end diff --git a/tests/test_setDefaultsPTB.m b/tests/test_setDefaultsPTB.m index 79a991f..fa19428 100644 --- a/tests/test_setDefaultsPTB.m +++ b/tests/test_setDefaultsPTB.m @@ -1,33 +1,33 @@ function test_setDefaultsPTB() - + %% test basic cfg creation - + % test data expectedCFG = returnExpectedCFG(); - + % test cfg = setDefaultsPTB; - testSubFields(expectedCFG, cfg) - + testSubFields(expectedCFG, cfg); + %% test that values are not overwritten - - clear cfg - + + clear cfg; + % test data expectedCFG = returnExpectedCFG(); expectedCFG.screen.monitorWidth = 36; - + % set up cfg.screen.monitorWidth = 36; - + % test cfg = setDefaultsPTB(cfg); - testSubFields(expectedCFG, cfg) - + testSubFields(expectedCFG, cfg); + %% test with audio init - - clear cfg - + + clear cfg; + % test data expectedCFG = returnExpectedCFG(); expectedCFG.audio = struct( ... @@ -39,20 +39,18 @@ function test_setDefaultsPTB() 'repeat', 1, ... 'startCue', 0, ... 'waitForDevice', 1); - + % set up cfg.audio.do = 1; - + % test cfg = setDefaultsPTB(cfg); - testSubFields(expectedCFG, cfg) - - -end + testSubFields(expectedCFG, cfg); +end function expectedCFG = returnExpectedCFG() - + expectedCFG = struct( ... 'testingDevice', 'pc', ... 'debug', struct('do', true, 'transpWin', true, 'smallWin', true), ... @@ -62,12 +60,12 @@ function test_setDefaultsPTB() 'screen', struct( ... 'monitorWidth', 42, ... 'monitorDistance', 134)); - + expectedCFG.keyboard.keyboard = []; expectedCFG.keyboard.responseBox = []; expectedCFG.keyboard.responseKey = {}; expectedCFG.keyboard.escapeKey = 'ESCAPE'; - + end function testSubFields(expectedStructure, cfg) From bdac9914a3504c7354d865ecbf0957c13dbd5d9c Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Wed, 29 Jul 2020 14:08:01 +0200 Subject: [PATCH 09/11] fix minor bugs --- demos/CPP_waitForTriggerDemo.m | 4 ++-- initPTB.m | 2 +- setDefaultsPTB.m | 2 +- waitForTrigger.m | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/demos/CPP_waitForTriggerDemo.m b/demos/CPP_waitForTriggerDemo.m index 60e17ef..e53e6eb 100644 --- a/demos/CPP_waitForTriggerDemo.m +++ b/demos/CPP_waitForTriggerDemo.m @@ -2,9 +2,9 @@ cfg.testingDevice = 'mri'; -cfg.numTriggers = 4; +cfg.mri.triggerNb = 4; -cfg.triggerKey = 'space'; +cfg.mri.triggerKey = 'space'; KbName('UnifyKeyNames'); diff --git a/initPTB.m b/initPTB.m index 7f55b45..fba35ed 100644 --- a/initPTB.m +++ b/initPTB.m @@ -2,7 +2,7 @@ % This will initialize PsychToolBox % - screen % - the windon opened takes the whole screen unless - % cfg.testingSmallScreen is set to true + % cfg.screen.smallWin is set to true % - debug mode : skips synch test and warnings % - window transparency enabled by cfg.testingTranspScreen set to true % - gets the flip interval diff --git a/setDefaultsPTB.m b/setDefaultsPTB.m index a8a8073..abddbd5 100644 --- a/setDefaultsPTB.m +++ b/setDefaultsPTB.m @@ -77,7 +77,7 @@ if isfield(structure, names{i}) && isstruct(structure.(names{i})) structure.(names{i}) = ... - setDefaultFields(structure.(names{i}), fieldsToSet.(names{i})); + setDefaults(structure.(names{i}), fieldsToSet.(names{i})); else diff --git a/waitForTrigger.m b/waitForTrigger.m index 1f8ae3f..c11f926 100644 --- a/waitForTrigger.m +++ b/waitForTrigger.m @@ -28,13 +28,13 @@ function waitForTrigger(cfg, deviceNumber) msg = 'Waiting for trigger'; talkToMe(cfg, msg); - while triggerCounter < cfg.triggerNb + while triggerCounter < cfg.mri.triggerNb keyCode = []; %#ok [~, keyCode] = KbPressWait(deviceNumber); - if strcmp(KbName(keyCode), cfg.triggerKey) + if strcmp(KbName(keyCode), cfg.mri.triggerKey) triggerCounter = triggerCounter + 1 ; @@ -42,7 +42,7 @@ function waitForTrigger(cfg, deviceNumber) talkToMe(cfg, msg); % we only wait if this is not the last trigger - if triggerCounter < cfg.triggerNb + if triggerCounter < cfg.mri.triggerNb pauseBetweenTriggers(cfg); end @@ -71,7 +71,7 @@ function pauseBetweenTriggers(cfg) % catch several triggers in one go. waitTime = 0.5; - if ~isempty(cfg.mri.repetitionTime) + if isfield(cfg, 'mri') && isfield(cfg.mri, 'repetitionTime') && ~isempty(cfg.mri.repetitionTime) waitTime = cfg.mri.repetitionTime / 2; end From ab1d950e0f078f99965239c20f0d822138342218 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Wed, 29 Jul 2020 15:47:31 +0200 Subject: [PATCH 10/11] few more updates --- computeFOV.m | 4 +++- initPTB.m | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/computeFOV.m b/computeFOV.m index 3044a85..1d12fef 100644 --- a/computeFOV.m +++ b/computeFOV.m @@ -1,8 +1,10 @@ function FOV = computeFOV(cfg) % 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)); + FOV = 2 * ... + (180 * (atan(cfg.screen.monitorWidth / (2 * cfg.screen.monitorDistance)) / pi)); end diff --git a/initPTB.m b/initPTB.m index fba35ed..f633eb8 100644 --- a/initPTB.m +++ b/initPTB.m @@ -182,8 +182,8 @@ function initDebug(cfg) 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.screen.win, cfg.text.font); + Screen('TextSize', cfg.screen.win, cfg.text.size); + Screen('TextStyle', cfg.screen.win, cfg.text.style); end From aac1eb5366f8562f6c6b0d4badb63d2549d32a76 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Wed, 29 Jul 2020 15:47:42 +0200 Subject: [PATCH 11/11] update doc and help sections --- README.md | 61 +++++++++++++++++++++++++++++++++++++++++++++-- checkAbort.m | 2 ++ cleanUp.m | 4 +++- degToPix.m | 2 ++ eyeTracker.m | 3 +++ getResponse.m | 2 ++ initPTB.m | 5 +++- pressSpaceForMe.m | 5 +++- testKeyboards.m | 2 ++ waitForTrigger.m | 2 ++ 10 files changed, 83 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ba54491..da29616 100644 --- a/README.md +++ b/README.md @@ -98,9 +98,66 @@ press the keys necessary to start or abort the experiment. Using empty vectors (ie `[]`) or a negative value for those means that you will let PTB find and use the default device. -## Structure and function details +## Structure + +```matlab + +cfg.testingDevice = 'pc'; + +% cfg.color +cfg.keyboard.keyboard = []; +cfg.keyboard.responseBox = []; +cfg.keyboard.responseKey = {}; +cfg.keyboard.escapeKey = 'ESCAPE'; + +% cfg.debug +cfg.debug.do = true; +cfg.debug.transpWin = true; +cfg.debug.smallWin = true; + +% cfg.text +cfg.text.font +cfg.text.size +cfg.text.style + +% cfg.color +cfg.color.background + +% cfg.screen +cfg.screen.monitorWidth +cfg.screen.monitorDistance +cfg.screen.idx +cfg.screen.win +cfg.screen.winRect +cfg.screen.winWidth +cfg.screen.winHeight +cfg.screen.center +cfg.screen.FOV +cfg.screen.ppd +cfg.screen.ifi +cfg.screen.monRefresh + +% cfg.audio +cfg.audio.do +cfg.audio.pahandle +cfg.audio.devIdx +cfg.audio.playbackMode +cfg.audio.requestedLatency +cfg.audio.fs +cfg.audio.channels +cfg.audio.initVolume +cfg.audio.pushSize +cfg.audio.requestOffsetTime +cfg.audio.reqsSampleOffset + +% cfg.mri +cfg.mri.repetitionTime +cfg.mri.triggerNb +cfg.mri.triggerKey +``` + +## function details - ### initPTB diff --git a/checkAbort.m b/checkAbort.m index 0fde93e..7d987c3 100644 --- a/checkAbort.m +++ b/checkAbort.m @@ -1,4 +1,6 @@ function checkAbort(cfg, deviceNumber) + % 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 diff --git a/cleanUp.m b/cleanUp.m index 3560d56..04fe5ce 100644 --- a/cleanUp.m +++ b/cleanUp.m @@ -1,4 +1,6 @@ -function cleanUp +function cleanUp() + % cleanUp() + % % A wrapper function to close all windows, ports, show mouse cursor, close keyboard queues % and give access back to the keyboards. diff --git a/degToPix.m b/degToPix.m index d21fec1..9dbf832 100644 --- a/degToPix.m +++ b/degToPix.m @@ -1,4 +1,6 @@ function structure = degToPix(fieldName, structure, cfg) + % 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. diff --git a/eyeTracker.m b/eyeTracker.m index bca25b7..cca35a1 100755 --- a/eyeTracker.m +++ b/eyeTracker.m @@ -1,4 +1,7 @@ function [el, edfFile] = eyeTracker(input, cfg, varargin) + % [el, edfFile] = eyeTracker(input, cfg, varargin) + % + % if ~cfg.eyeTracker.do diff --git a/getResponse.m b/getResponse.m index 82823b5..1233093 100644 --- a/getResponse.m +++ b/getResponse.m @@ -1,4 +1,6 @@ function responseEvents = getResponse(action, deviceNumber, cfg, getOnlyPress) + % responseEvents = getResponse(action, deviceNumber, cfg, getOnlyPress) + % % Wrapper function to use KbQueue % % The queue will be listening to key presses on a keyboard device: diff --git a/initPTB.m b/initPTB.m index f633eb8..533164d 100644 --- a/initPTB.m +++ b/initPTB.m @@ -1,4 +1,6 @@ function [cfg] = initPTB(cfg) + % [cfg] = initPTB(cfg) + % % This will initialize PsychToolBox % - screen % - the windon opened takes the whole screen unless @@ -13,7 +15,8 @@ % - hides cursor % - sound % - % OUTPUT: + % See the Readme for more details on the content of cfg + % % % diff --git a/pressSpaceForMe.m b/pressSpaceForMe.m index f23f548..b6c209d 100644 --- a/pressSpaceForMe.m +++ b/pressSpaceForMe.m @@ -1,5 +1,8 @@ -function pressSpaceForMe +function pressSpaceForMe() + % pressSpaceForMe() + % % Use that to stop your script and only restart when the space bar is pressed. + % fprintf('\nPress space to continue.\n'); diff --git a/testKeyboards.m b/testKeyboards.m index 00102a3..6ce8e7a 100644 --- a/testKeyboards.m +++ b/testKeyboards.m @@ -1,4 +1,6 @@ function testKeyboards(cfg) + % 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. diff --git a/waitForTrigger.m b/waitForTrigger.m index c11f926..f4ccede 100644 --- a/waitForTrigger.m +++ b/waitForTrigger.m @@ -1,4 +1,6 @@ function waitForTrigger(cfg, deviceNumber) + % 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