Click here to download this file

function [] = CogentExample_fMRIExperiment()
% --- Clear and close all and make new random seed:
clc;
close all;
clear all; %#ok<CLFUN>
rng(2,'twister');

% --- Get Subject number:
UserInput = inputdlg({'Enter Subject number (double):'},'Subject number',1);
SubjectNum = str2double(UserInput{1,1});
if (SubjectNum < 0) || (SubjectNum > 99)
    error('Invalid input for subject number!');
end
SubjectId = sprintf('Subject-%02d',SubjectNum);

% --- Request Day Ordering:
UserInput = inputdlg({'Which StimSetID was trained on the FIRST DAY (X or Y):'},'Specify training set order',1);
StimSetIdOnDay1 = UserInput{1,1};
if ~(strcmp(StimSetIdOnDay1,'X') || strcmp(StimSetIdOnDay1,'Y'))
    error('Invalid input for training set ID!');
end

% --- Get StimulusOrder and generate LvrMatrix:
StimulusOrder = dlmread('ScannerTask_StimOrder');
StimOccurrences = zeros(size(StimulusOrder,1),(max(StimulusOrder,[],1)+1));
for i_RawOrder = 1:1:size(StimulusOrder,1)
    StimOccurrences(i_RawOrder,(StimulusOrder(i_RawOrder,1)+1)) = 1;
end
TestBool = sum(double(sum(StimOccurrences,1) == [16,(ones(1,24).*8)]),2) == size(StimOccurrences,2);
if ~TestBool
    error('Something is wrong with the ''StimOrder'' file!');
end
LvrMatrix = nan(mean(sum(StimOccurrences(:,2:end),1),2),size(StimOccurrences(:,2:end),2));
for j_StimOccurrences = 2:1:size(StimOccurrences,2)
    nOccurrences = sum(StimOccurrences(:,j_StimOccurrences),1);
    LvrMatrix(:,(j_StimOccurrences-1)) = mod(randperm(nOccurrences)',2) + 1;
end
clear *timOccurrence*;

% --- Create AssetStructure:
cd Assets;
DvI = {'D','I'}';
if strcmp(StimSetIdOnDay1,'X')
    XvY = {'X','Y'}';
else
    XvY = {'Y','X'}';
end
Num = (1:1:6)';
LvR = {'L','R'}';
AssetStructure = struct;
PairNumber = 0;
for i_DvI = 1:1:size(DvI,1)
    for i_XvY = 1:1:size(XvY,1)
        for i_Num = 1:1:size(Num,1)
            for i_LvR = 1:1:size(LvR,1)

                PairNumber = PairNumber + 1;
                PairId = sprintf('%s%s%02d%s',...
                    DvI{i_DvI},...
                    XvY{i_XvY,1},...
                    Num(i_Num,1),...
                    LvR{i_LvR,1});
                AssetStructure(PairNumber,1).IdStr = PairId;
                AssetStructure(PairNumber,1).StartImg = sprintf('%s%s%s_S.bmp',pwd,filesep,PairId);

            end
        end
    end
end
cd ..;

% --- Create StudyIO:
global StudyIO;
StudyIO = struct;
i_LvrMatrix = zeros(1,size(LvrMatrix,2));
for i_StudyIO = 1:1:size(StimulusOrder,1)
    if StimulusOrder(i_StudyIO,1) == 0
        StudyIO(i_StudyIO,1).Condition = 'Null';
        StudyIO(i_StudyIO,1).PairId = 'N/A';
        StudyIO(i_StudyIO,1).TestImg = 'N/A';
        StudyIO(i_StudyIO,1).CorrectResponse = 'N/A';
        StudyIO(i_StudyIO,1).ActualResponse = 'N/A';
        StudyIO(i_StudyIO,1).CogentTime_Start = [];
        StudyIO(i_StudyIO,1).CogentTime_Response = NaN;
    else
        i_LvrMatrix(1,StimulusOrder(i_StudyIO,1)) = i_LvrMatrix(1,StimulusOrder(i_StudyIO,1)) + 1;
        i_AssetStructure = ((StimulusOrder(i_StudyIO,1) - 1) * 2) + LvrMatrix(i_LvrMatrix(1,StimulusOrder(i_StudyIO,1)),StimulusOrder(i_StudyIO,1));
        if strcmp(AssetStructure(i_AssetStructure,1).IdStr(1,2),StimSetIdOnDay1)
            DayCondition = 1;
        else
            DayCondition = 2;
        end
        StudyIO(i_StudyIO,1).Condition = sprintf('%s%d',AssetStructure(i_AssetStructure,1).IdStr(1,1),DayCondition);
        StudyIO(i_StudyIO,1).PairId = AssetStructure(i_AssetStructure,1).IdStr;
        StudyIO(i_StudyIO,1).TestImg = AssetStructure(i_AssetStructure,1).StartImg;
        StudyIO(i_StudyIO,1).CorrectResponse = StudyIO(i_StudyIO,1).PairId(1,5);
        StudyIO(i_StudyIO,1).ActualResponse = '';
        StudyIO(i_StudyIO,1).CogentTime_Start = [];
        StudyIO(i_StudyIO,1).CogentTime_Response = [];
    end
end

% --- Confirm details
ConfStr = sprintf(...
    'Experiment ready to begin...%cSubject ID: %s%cStimSetID trained on FIRST DAY: %s%cAre these details correct and do you wish to continue?',...
    10,SubjectId,10,StimSetIdOnDay1,10);
ConfirmButton = questdlg(ConfStr,'Confirm details','YES','NO','NO');
if ~strcmp(ConfirmButton,'YES')
    error('Script terminated by user!');
end

% --- Initialise Cogent:
Double_TimeOfTest = clock;
Year = sprintf('%04d',Double_TimeOfTest(1,1));
Month = sprintf('%02d',Double_TimeOfTest(1,2));
Day = sprintf('%02d',Double_TimeOfTest(1,3));
Hour = sprintf('%02d',Double_TimeOfTest(1,4));
Min = sprintf('%02d',Double_TimeOfTest(1,5));
Sec = sprintf('%02d',round(Double_TimeOfTest(1,6)));
Str_TimeOfTest = sprintf('%s%s%s-%s%s%s',Year,Month,Day,Hour,Min,Sec);
logname = sprintf('LOG_%s',Str_TimeOfTest);
resname = sprintf('RES_%s',Str_TimeOfTest);
config_log(logname);
config_results(resname);
ScreenDims = get(0,'ScreenSize'); % Get screen resolution.
ScreenDims = ScreenDims(1,3:4);
if (ScreenDims(1,1) < 1280) || (ScreenDims(1,2) < 720)
    error('Your screen%cs screen resolution is too low for this task!%cCurrent screen resolution:    %03d x %03d%cMiniumum resolution required: 1280 x 0720',39,10,ScreenDims(1,1),ScreenDims(1,2),10);
end
config_keyboard(100,5,'exclusive'); %%% Change last argument to 'exclusive' for accurate timeing - HOWEVER, you cannot brake out of this mode!

% --- Start experiment:
cgloadlib;
cgopen(ScreenDims(1,1),ScreenDims(1,2),0,0,1); % Open window.
start_cogent; % Start Cogent.

cgfont('Arial',60);
cgpencol(0,0.5,1);
cgtext('Waiting for scanner',0,0);
cgflip(0,0,0); % Waiting for scanner messgae.

cgfont('Arial',80); % Prep CgPen for ITIs and nulls.
cgpencol(0.8,0.8,0.8);
cgtext('+',0,0);

i_StudyIO = 0;
PrepTrial((i_StudyIO + 1)); % Prep first trial.

[~,t_SCAN0,~] = waitkeydown(inf,19); % Wait for scanner and set t_SCAN0.

% --- Trial loop:
for i_StudyIO = 1:1:size(StudyIO,1)
    if strcmp(StudyIO(i_StudyIO,1).Condition,'Null')
        StudyIO(i_StudyIO,1).CogentTime_Start = cgflip(0,0,0) * 1000; % Flip display.
        PrepTrial((i_StudyIO + 1)); % Prep the next trial.
        readkeys;
        logkeys;
        clearkeys;
        waituntil((t_SCAN0 + (i_StudyIO * 6500)));
    else
        StudyIO(i_StudyIO,1).CogentTime_Start = cgflip(0,0,0) * 1000; % Flip display.
        waitkeydown(3000,[97,98]); % Check for a response.
        [Key, TimeOfKey] = lastkeydown;
        waituntil((StudyIO(i_StudyIO,1).CogentTime_Start + 3000));
        cgtext('+',0,0); % Draw and flip to ITI.
        cgflip(0,0,0);
        % Load ActualResponse and CogentTime_Response into StudyIO:
        if Key == 97
            StudyIO(i_StudyIO,1).ActualResponse = 'L';
        elseif Key == 98
            StudyIO(i_StudyIO,1).ActualResponse = 'R';
        end
        StudyIO(i_StudyIO,1).CogentTime_Response = TimeOfKey;
        PrepTrial((i_StudyIO + 1)); % Prep the next trial.
        readkeys;
        logkeys;
        clearkeys;
        waituntil((t_SCAN0 + (i_StudyIO * 6500)));
    end
end

% --- End experiment:
cgfont('Arial',60);
cgpencol(0,0.5,1);
cgtext('Relax',0,0);
cgflip(0,0,0);
wait(5100);
readkeys;
logkeys;
clearkeys;

% --- Stop cogent:
cgshut;
stop_cogent;

% --- Replace 0 RTs with NaNs:
for i_StudyIO = 1:1:size(StudyIO)
    if StudyIO(i_StudyIO,1).CogentTime_Response == 0
        StudyIO(i_StudyIO,1).CogentTime_Response = NaN;
    end
end

% --- Save vars:
[~,SystemName] = system('hostname');
ScriptName = mfilename('fullpath');
SystemRuntimeInfo = ...
    {SystemName;
    ScriptName;
    Double_TimeOfTest;};
clear SystemName ScriptName;
save(Str_TimeOfTest,...
    'SubjectId', 'StimSetIdOnDay1',...
    'StimulusOrder', 'LvrMatrix', 'AssetStructure',...
    'StudyIO', 't_SCAN0', 'SystemRuntimeInfo');

% --- Make output folder and move saved data:
MatName = sprintf('%s%s%s.mat',pwd,filesep,Str_TimeOfTest);
LogName = sprintf('%s%sLOG_%s',pwd,filesep,Str_TimeOfTest);
ResName = sprintf('%s%sRES_%s',pwd,filesep,Str_TimeOfTest);
delete(ResName);
cd OutputData;
mkdir(Str_TimeOfTest);
cd(Str_TimeOfTest);
movefile(MatName,pwd);
movefile(LogName,pwd);

% --- Read log file and genarate ScanTS:
LogName = sprintf('LOG_%s',Str_TimeOfTest);
LogData = ReadLogFile(LogName);
ScansLoggedByCogent = zeros(537,1); % Number of EPI measurements go here!
nScansLogged = 0;
for i_Entry = 1:size(LogData,1)
    if strcmp(LogData{i_Entry,4},'Key')...
            && (LogData{i_Entry,5} == 19)...
            && strcmp(LogData{i_Entry,6},'DOWN')
        nScansLogged = nScansLogged + 1;
        if nScansLogged == 1
            PredictedScanTs = (((0:1:(size(ScansLoggedByCogent,1)-1))').*2520) + LogData{i_Entry,8}; % TR goes in here!
            ScansLoggedByCogent(1,1) = 1;
        else
            % Search for the scan in "PredictedScanTs":
            FoundIt = false;
            for i_PredictedScanTs = 1:1:size(PredictedScanTs,1)
                CogentTime = LogData{i_Entry,8};
                PredictedTime = PredictedScanTs(i_PredictedScanTs,1);
                PredictionError = abs((CogentTime-PredictedTime));
                if PredictionError < 50
                    FoundIt = true;
                    ScansLoggedByCogent(i_PredictedScanTs,1) = 1;
                    PredictedScanTs(i_PredictedScanTs,1) = CogentTime; % Replace prediction (indexed by i_PredictedScanTs) by actual cogent time.
                    % Now update the prediction for all subsequent scans:
                    for i_Update = (1+i_PredictedScanTs):1:size(PredictedScanTs,1)
                        PredictedScanTs(i_Update,1) = CogentTime + ((i_Update-i_PredictedScanTs) * 2520); % TR goes in here!
                    end
                    break
                end
            end
            if ~FoundIt
                error('We have encountered a cogent logged EPI that has not met prediction error tolerance of 50 ms!');
            end
        end
    end
end
Scans = PredictedScanTs;

% --- Covert StudyIO.CogentTime_Start to SPM scan metric by making StudyIO.ScanTime_Start:
for i_StudyIO = 1:1:size(StudyIO,1)
    EventTime_Cognet = StudyIO(i_StudyIO,1).CogentTime_Start;
    for i_Scans = 1:1:size(Scans,1)
        if EventTime_Cognet >= Scans(i_Scans,1)
            ScanN = i_Scans - 1;
        else
            TimeOfScanN_Cogent = Scans(i_Scans,1) - 2520;
            break
        end
    end
    EventTime_Scan = ScanN + ((EventTime_Cognet - TimeOfScanN_Cogent) / 2520);
    StudyIO(i_StudyIO,1).ScanTime_Start = EventTime_Scan;
end

% --- Covert StudyIO.CogentTime_Response to SPM scan metric by making StudyIO.ScanTime_Response:
for i_StudyIO = 1:1:size(StudyIO,1)
    EventTime_Cognet = StudyIO(i_StudyIO,1).CogentTime_Response;
    for i_Scans = 1:1:size(Scans,1)
        if EventTime_Cognet >= Scans(i_Scans,1)
            ScanN = i_Scans - 1;
        else
            TimeOfScanN_Cogent = Scans(i_Scans,1) - 2520;
            break
        end
    end
    EventTime_Scan = ScanN + ((EventTime_Cognet - TimeOfScanN_Cogent) / 2520);
    StudyIO(i_StudyIO,1).ScanTime_Response = EventTime_Scan;
end

% --- Resave all that is needed:
save(Str_TimeOfTest,...
    'SubjectId', 'StimSetIdOnDay1',...
    'StimulusOrder', 'LvrMatrix', 'AssetStructure',...
    'StudyIO', 't_SCAN0', 'SystemRuntimeInfo',...
    'Scans','ScansLoggedByCogent');

% --- Cd out, clear all and end:
cd ..;
cd ..;
clear all; %#ok<CLFUN>
return

function [] = PrepTrial(i)
global StudyIO;
if (i <= size(StudyIO,1))
    if strcmp(StudyIO(i,1).Condition,'Null')
        cgtext('+',0,0);
    else
        cgloadbmp(1,StudyIO(i,1).TestImg,1280,0);
        cgdrawsprite(1,0,0);
    end
end
return

function [LogData] = ReadLogFile(FileNameIn)
% Function adapted from "importfile.m" by "MathWorks Internet of Things Team".
% --- Read ACSII file:
Delimiter = '\t';
StartRow = 4;
FormatSpec = '%s%s%s%s%s%s%s%s%[^\n\r]';
FileID = fopen(FileNameIn,'r');

% -- Read columns of data according to format string.
DataArray = textscan(FileID, FormatSpec, 'Delimiter', Delimiter, 'HeaderLines' ,StartRow-1, 'ReturnOnError', false);
fclose(FileID); % Close the text file.

% --- Convert the contents of columns containing numeric strings to numbers.
% Replace non-numeric strings with NaN.
Raw = [DataArray{:,1:end-1}];
NumericData = NaN(size(DataArray{1},1),size(DataArray,2));
for Col = [1,2,5,8]
    % Converts strings in the input cell array to numbers.
    RawData = DataArray{Col};
    for Row = 1:size(RawData,1);
        % Create a regular expression to detect and remove non-numeric prefixes and suffixes.
        Regexstr = '(?<prefix>.*?)(?<numbers>([-]*(\d+[\,]*)+[\.]{0,1}\d*[eEdD]{0,1}[-+]*\d*[i]{0,1})|([-]*(\d+[\,]*)*[\.]{1,1}\d+[eEdD]{0,1}[-+]*\d*[i]{0,1}))(?<suffix>.*)';
        try
            Result = regexp(RawData{Row}, Regexstr, 'names');
            Numbers = Result.numbers;
            % Detected commas in non-thousand locations.
            InvalidThousandsSeparator = false;
            if any(Numbers==',');
                thousandsRegExp = '^\d+?(\,\d{3})*\.{0,1}\d*$';
                if isempty(regexp(thousandsRegExp, ',', 'once'));
                    Numbers = NaN;
                    InvalidThousandsSeparator = true;
                end
            end
            % Convert numeric strings to numbers.
            if ~InvalidThousandsSeparator;
                Numbers = textscan(strrep(Numbers, ',', ''), '%f');
                NumericData(Row, Col) = Numbers{1};
                Raw{Row, Col} = Numbers{1};
            end
        catch me
        end
    end
end

% --- Split data into numeric and cell columns.
RawNumericColumns = Raw(:, [1,2,5,8]);
RawCellColumns = Raw(:, [3,4,6,7]); %#ok<*NASGU>

% --- Replace non-numeric cells with NaN
R = cellfun(@(x) ~isnumeric(x) && ~islogical(x),RawNumericColumns); % Find non-numeric cells.
RawNumericColumns(R) = {NaN}; % Replace non-numeric cells.

% --- Create output variable
LogData = Raw;

return