diff --git a/checkPtbVersion.m b/checkPtbVersion.m index 97fe5d0..45904d2 100644 --- a/checkPtbVersion.m +++ b/checkPtbVersion.m @@ -1,7 +1,7 @@ function checkPtbVersion() % Checks that the right dependencies are installed. - printCredits(); + printCreditsCppPtb(); PTB.major = 3; PTB.minor = 0; diff --git a/collectAndSaveResponses.m b/collectAndSaveResponses.m new file mode 100644 index 0000000..c8f48c9 --- /dev/null +++ b/collectAndSaveResponses.m @@ -0,0 +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 diff --git a/eyeTracker.m b/eyeTracker.m index f7dd0bf..fa9c392 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 + + %% 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 + % 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) @@ -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 STIMALTION OF THE BLOCK + + % 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,10 +215,76 @@ catch fprintf('Problem receiving data file ''%s''\n', edfFileName); end - + Eyelink('shutdown'); - Screen('CloseAll'); - + 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; + [~, ivx] = iViewX('openconnection', ivx); + [success, ivx] = iViewX('checkconnection', ivx); + if success ~= 1 + error('connection to eye tracker failed'); + end + end +end + +function eyeTrackStart(ivx, expParameters) + % start iView eye tracker + if cfg.eyeTracker + % to clear data buffer + iViewX('clearbuffer', ivx); + % start recording + iViewX('startrecording', ivx); + iViewX('message', ivx, ... + [ ... + 'Start_Ret_', ... + 'Subj_', expParameters.Subj, '_', ... + 'Run', num2str(expParameters.Session(end)), '_', ... + expParameters.Apperture, '_', ... + expParameters.Direction]); + iViewX('incrementsetnumber', ivx, 0); + end +end + +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, ... + [expParameters.Subj, ... + '_run', num2str(expParameters.Session(end)), '_', ... + expParameters.Apperture, '_', ... + expParameters.Direction, '_', ... + thedatestr, '.idf"']); + iViewX('datafile', ivx, strFile); + + % close iView connection + iViewX('closeconnection', ivx); end +end \ No newline at end of file diff --git a/getResponse.m b/getResponse.m index c028a9d..ba72acf 100644 --- a/getResponse.m +++ b/getResponse.m @@ -45,159 +45,167 @@ % 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); - + + 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( ... + any( ... strcmpi({responseEvents(:).keyName}, cfg.keyboard.escapeKey) ... ) errorAbortGetReponse(responseEvents); end end -function talkToMe(action) +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 - - fprintf('\n %s\n\n', msg); - + + if verbose + fprintf('\n %s\n\n', msg); + + end + end diff --git a/initPTB.m b/initPTB.m index 26ffbad..05306a5 100644 --- a/initPTB.m +++ b/initPTB.m @@ -80,8 +80,8 @@ % 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; + cfg.FOV = computeFOV(cfg); + cfg.ppd = cfg.winRect(3) / cfg.FOV; %% Select specific text font, style and size initText(cfg); diff --git a/printCredits.m b/printCreditsCppPtb.m similarity index 61% rename from printCredits.m rename to printCreditsCppPtb.m index 3e4e9cf..97a4021 100644 --- a/printCredits.m +++ b/printCreditsCppPtb.m @@ -1,4 +1,4 @@ -function printCredits() +function printCreditsCppPtb() version = '0.0.1'; @@ -11,13 +11,13 @@ function printCredits() repoURL = 'https://github.com/cpp-lln-lab/CPP_PTB'; - disp('________________________________________'); - disp('________________________________________'); + disp('___________________________________________________'); + disp('___________________________________________________'); disp(' '); - disp(' __ ____ ____ ____ _____ _ '); - disp(' / _)( _ \( _ \ ( _ \ |_ _| | ) '); - disp('( (_ )___/ )___/ )___/ | | | \ '); - disp(' \__)(__) (__) (__) |_| |__)'); + disp(' __ ____ ____ ____ _____ _ '); + disp(' / _)( _ \( _ \ ( _ \ |_ _| | ) '); + disp(' ( (_ )___/ )___/ )___/ | | | \ '); + disp(' \__)(__) (__) (__) |_| |__)'); disp(' '); splash = 'Thank you for using the CPP PTB - version %s. '; @@ -34,8 +34,8 @@ function printCredits() fprintf('For bug report, suggestions or contributions see our repo: \n %s\n\n', repoURL); - disp('________________________________________'); - disp('________________________________________'); + disp('___________________________________________________'); + disp('___________________________________________________'); fprintf('\n\n'); diff --git a/waitForTrigger.m b/waitForTrigger.m index 59797da..de8f4c1 100644 --- a/waitForTrigger.m +++ b/waitForTrigger.m @@ -58,7 +58,7 @@ function talkToMe(cfg, msg) if isfield(cfg, 'win') DrawFormattedText(cfg.win, msg, ... - 'center', 'center', cfg.textColor); + 'center', 'center', cfg.text.color); Screen('Flip', cfg.win); @@ -78,3 +78,56 @@ function pauseBetweenTriggers(cfg) 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 +% % a time) +% 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 +%