123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397 |
- %% ery_4a_prep_s1_write_events_tsv
- %
- % This script reads logfiles, extracts the onsets, durations, and ratings for
- % different conditions, and writes events.tsv files to the BIDS dir for
- % each subject
- % It also contains an option to write a single phenotype file with
- % trial-by-trial ratings for all subjects
- %
- % USAGE
- %
- % Script should be run from the root directory of the superdataset, e.g.
- % /data/proj_discoverie
- % The script is highly study-specific, as logfiles will vary with design,
- % stimulus presentation software used, etc
- % Hence, it is provided in LaBGAScore as an example and needs to be
- % downloaded and adapted to the code subdataset for your study/project
- % This example is from LaBGAS proj_erythritol_4a
- % (https://gin.g-node.org/labgas/proj_erythritol_4a)
- %
- %
- % DEPENDENCIES
- %
- % LaBGAScore Github repo on Matlab path, with subfolders
- % https://github.com/labgas/LaBGAScore
- %
- %
- % INPUTS
- %
- % Presentation .log files in sourcedata dir for each subject
- %
- %
- % OUTPUTS
- %
- % events.tsv files for each run in BIDS dir for each subject
- % phenotype.tsv file in BIDS/phenotype dir (optional)
- %
- %__________________________________________________________________________
- %
- % author: Lukas Van Oudenhove
- % date: December, 2021
- %
- %__________________________________________________________________________
- % @(#)% LaBGAScore_prep_s1_write_events_tsv.m v1.2
- % last modified: 2022/04/21
- %% DEFINE DIRECTORIES, SUBJECTS, RUNS, CONDITIONS, AND IMPORT OPTIONS
- %--------------------------------------------------------------------------
- ery_4a_prep_s0_define_directories; % lukasvo edited from original LaBGAScore script to enable standalone functioning of proj_ery_4a dataset
- subjs2write = {}; % enter subjects separated by comma if you only want to write files for selected subjects e.g. {'sub-01','sub-02'}
- pheno_tsv = true; % turn to false if you do not wish to generate a phenotype.tsv file with trial-by-trial ratings; will only work if subjs2write is empty (i.e. when you loop over all your subjects)
- pheno_name = 'ratings_online.tsv';
- runnames = {'run-1','run-2','run-3','run-4','run-5','run-6'};
- logfilenames = {'*_run1.log','*_run2.log','*_run3.log','*_run4.log','*_run5.log','*_run6.log'};
- taskname = 'sweettaste_';
- sweet_labels = {'sucrose delivery';'erythritol delivery';'sucralose delivery';'control delivery'}; % labels for sweet substance delivery in Code var of logfile
- swallow_rinse_labels = {'sucrose_swallowing';'erythritol_swallowing';'sucralose_swallowing';'control_swallowing'}; % labels for swallowing cue presentation after sweet substance delivery in Code var of logfile
- rating_labels = {'Sucrose','Erythritol','Sucralose','Control'}; % labels for start of rating period in Code var of logfile
- fixation_labels = {'fixation_cross','Sucrose fixation cross','Erythritol fixation cross','Sucralose fixation cross','Control fixation cross'}; % labels for fixation cross in Code var of logfile
- events_interest = {'sucrose','erythritol','sucralose','water'}; % names of events of interest to be written to events.tsv
- events_nuisance = {'swallow_rinse','rating'}; % names of nuisance events to be written to events.tsv
- varNames = {'Trial','Event Type','Code','Time','TTime','Uncertainty','Duration','Uncertainty','ReqTime','ReqDur','Stim Type','Pair Index'}; % varnames of logfile
- selectedVarNames = [1:5 7 9]; % varnames we want to use in the script
- varTypes = {'double','categorical','categorical','double','double','double','double','double','double','char','char','double'}; % matlab vartypes to be used when importing log file as table
- delimiter = '\t';
- dataStartLine = 5; % line on which actual data starts in logfile
- extraColRule = 'ignore';
- opts = delimitedTextImportOptions('VariableNames',varNames,...
- 'SelectedVariableNames',selectedVarNames,...
- 'VariableTypes',varTypes,...
- 'Delimiter',delimiter,...
- 'DataLines', dataStartLine,...
- 'ExtraColumnsRule',extraColRule);
- %% LOOP OVER SUBJECTS TO READ LOGFILES, CREATE TABLE WITH ONSETS AND DURATIONS, AND SAVE AS EVENTS.TSV TO BIDSSUBJDIRS
- %-------------------------------------------------------------------------------------------------------------------
- if ~isempty(subjs2write)
- [C,ia,~] = intersect(sourcesubjs,subjs2write);
-
- if ~isequal(C',subjs2write)
- error('\nsubject %s present in subjs2smooth not present in %s, please check before proceeding',subjs2smooth{~ismember(subjs2smooth,C)},derivdir);
-
- else
-
- for sub = ia'
-
- % DEFINE SUBJECT LEVEL DIRS
- subjsourcedir = sourcesubjdirs{sub};
- subjBIDSdir = fullfile(BIDSsubjdirs{sub},'func');
- % LOOP OVER RUNS
- for run = 1:size(logfilenames,2)
-
- logfilename = dir(fullfile(subjsourcedir,'logfiles',logfilenames{run}));
- logfilename = char(logfilename(:).name);
- logfilepath = fullfile(subjsourcedir,'logfiles',logfilename);
-
- if ~isfile(logfilepath)
- warning('\nlogfile missing for run %d in %s, please check before proceeding',run,logfilepath);
- continue
-
- elseif size(logfilepath,1) > 1
- error('\nmore than one logfile with run index %s for %s, please check before proceeding',run,sourcesubjs{sub})
-
- else
- log = readtable(logfilepath,opts);
- log = log(~isnan(log.Trial),:);
- time_zero = log.Time(log.Trial == 0 & log.EventType == 'Pulse'); % time for onsets and durations is counted from the first scanner pulse onwards
-
- if size(time_zero,1) > 1
- error('\nambiguity about time zero in %s%s, please check logfile',subjs{sub},logfilenames{run});
- end
-
- log.TimeZero = log.Time - time_zero;
- log.onset = log.TimeZero ./ 10000; % convert to seconds
- log(log.EventType == 'Pulse',:) = [];
- log.trial_type = cell(height(log),1);
-
- for k = 1:height(log)
- if ismember(log.Code(k),swallow_rinse_labels)
- log.trial_type{k} = events_nuisance{1};
-
- elseif ismember(log.Code(k),rating_labels)
- log.trial_type{k} = events_nuisance{2};
- log.onset(k) = log.onset(k)+4;
-
- elseif ismember(log.Code(k),sweet_labels)
- idx = (log.Code(k) == sweet_labels);
- log.trial_type{k} = events_interest{idx'};
-
- elseif ismember(log.Code(k),fixation_labels)
- log.trial_type{k} = 'fixation';
-
- else
- log.trial_type{k} = '';
-
- end
- end
-
- log.rating = zeros(height(log),1);
-
- for l = 1:height(log)
-
- if contains(char(log.Code(l)),'score','IgnoreCase',true)
- scorestring = char(log.Code(l));
-
- if strcmp(scorestring(1,end-3:end),'-100')
- log.rating(l) = str2double(scorestring(1,end-3:end));
-
- elseif ~contains(scorestring(1,end-2:end),':')
- log.rating(l) = str2double(strtrim(scorestring(1,end-2:end)));
-
- else
- log.rating(l) = str2double(scorestring(1,end));
-
- end
-
- else
- log.rating(l) = NaN;
-
- end
-
- end
-
- log.trial_type = categorical(log.trial_type);
-
- log = log((~isundefined(log.trial_type) | ~isnan(log.rating)),:);
-
- for n = 1:height(log)
-
- if ismember(log.trial_type(n),events_interest)
- log.rating(n) = log.rating(n+3);
-
- end
-
- end
-
- log = removevars(log,{'Trial','EventType','Code','Time','TTime','Duration','ReqTime','TimeZero'}); % get rid of junk variables from logfile we don't need
-
- log = log(~isundefined(log.trial_type),:);
-
- log.duration = zeros(height(log),1);
-
- for m = 1:height(log)
-
- if ~isequal(log.trial_type(m),'fixation')
- log.duration(m) = log.onset(m+1) - log.onset(m);
-
- else
- log.duration(m) = NaN;
-
- end
- end
-
- log = log(~isnan(log.duration),:);
- log = log(log.rating~=-100,:); % lukasvo76 added to original LaBGAScore script - trials with -100 ratings need to be removed since subjects were instructed to use this in case of failed solution delivery
-
- filename = fullfile(subjBIDSdir,[sourcesubjs{sub},'_task-',taskname,runnames{run},'_events.tsv']);
- writetable(log,filename,'Filetype','text','Delimiter','\t');
- clear logfile log time_zero filename
- end % if loop checking whether logfile exists
- end % for loop runs
-
- end % for loop subjects
-
- end % if loop checking subjs2write present in sourcesubjs
-
- else
-
- if ~isequal(sourcesubjs, BIDSsubjs)
- [D,~,~] = intersect(sourcesubjs,BIDSsubjs);
- error('\nsubject %s present in %s not present in %s, please check before proceeding',BIDSsubjs{~ismember(BIDSsubjs,D)},BIDSdir,derivdir);
-
- else
-
- if pheno_tsv
- pheno_file = table();
- pheno_dir = fullfile(BIDSdir,'phenotype');
- if ~isfolder(pheno_dir)
- mkdir(pheno_dir);
- end
- end
-
- for sub = 1:size(sourcesubjs,1)
-
- if pheno_tsv
- pheno_file_subj = table();
- end
- % DEFINE SUBJECT LEVEL DIRS),':')
- subjsourcedir = sourcesubjdirs{sub};
- subjBIDSdir = fullfile(BIDSsubjdirs{sub},'func');
- % LOOP OVER RUNS
- for run = 1:size(logfilenames,2)
-
- logfilename = dir(fullfile(subjsourcedir,'logfiles',logfilenames{run}));
- logfilename = char(logfilename(:).name);
- logfilepath = fullfile(subjsourcedir,'logfiles',logfilename);
-
- if ~isfile(logfilepath)
- warning('\nlogfile missing for run %d in %s, please check before proceeding',run,logfilepath);
- continue
-
- elseif size(logfilepath,1) > 1
- error('\nmore than one logfile with run index %s for %s, please check before proceeding',run,sourcesubjs{sub})
-
- else
- log = readtable(logfilepath,opts);
- log = log(~isnan(log.Trial),:);
- time_zero = log.Time(log.Trial == 0 & log.EventType == 'Pulse'); % time for onsets and durations is counted from the first scanner pulse onwards
-
- if size(time_zero,1) > 1
- error('\nambiguity about time zero in %s%s, please check logfile',subjs{sub},logfilenames{run});
- end
-
- log.TimeZero = log.Time - time_zero;
- log.onset = log.TimeZero ./ 10000; % convert to seconds
- log(log.EventType == 'Pulse',:) = [];
- log.trial_type = cell(height(log),1);
-
- for k = 1:height(log)
-
- if ismember(log.Code(k),swallow_rinse_labels)
- log.trial_type{k} = events_nuisance{1};
-
- elseif ismember(log.Code(k),rating_labels)
- log.trial_type{k} = events_nuisance{2};
- log.onset(k) = log.onset(k)+4;
-
- elseif ismember(log.Code(k),sweet_labels)
- idx = (log.Code(k) == sweet_labels);
- log.trial_type{k} = events_interest{idx'};
-
- elseif ismember(log.Code(k),fixation_labels)
- log.trial_type{k} = 'fixation';
-
- else
- log.trial_type{k} = '';
-
- end
- end
-
- log.rating = zeros(height(log),1);
-
- for l = 1:height(log)
-
- if contains(char(log.Code(l)),'score','IgnoreCase',true)
- scorestring = char(log.Code(l));
-
- if strcmp(scorestring(1,end-3:end),'-100')
- log.rating(l) = str2double(scorestring(1,end-3:end));
-
- elseif ~contains(scorestring(1,end-2:end),':')
- log.rating(l) = str2double(strtrim(scorestring(1,end-2:end)));
-
- else
- log.rating(l) = str2double(scorestring(1,end));
-
- end
-
- else
- log.rating(l) = NaN;
-
- end
-
- end
-
- log.trial_type = categorical(log.trial_type);
-
- log = log((~isundefined(log.trial_type) | ~isnan(log.rating)),:);
-
- for n = 1:height(log)
-
- if ismember(log.trial_type(n),events_interest)
- log.rating(n) = log.rating(n+3);
-
- end
-
- end
-
- log = removevars(log,{'Trial','EventType','Code','Time','TTime','Duration','ReqTime','TimeZero'}); % get rid of junk variables from logfile we don't need
-
- log = log(~isundefined(log.trial_type),:);
-
- log.duration = zeros(height(log),1);
-
- for m = 1:height(log)
-
- if ~isequal(log.trial_type(m),'fixation')
- log.duration(m) = log.onset(m+1) - log.onset(m);
-
- else
- log.duration(m) = NaN;
-
- end
- end
-
- log = log(~isnan(log.duration),:);
- log = log(log.rating~=-100,:); % lukasvo76 added to original LaBGAScore script - trials with -100 ratings need to be removed since subjects were instructed to use this in case of failed solution delivery
-
- filename = fullfile(subjBIDSdir,[sourcesubjs{sub},'_task-',taskname,runnames{run},'_events.tsv']);
- writetable(log,filename,'Filetype','text','Delimiter','\t');
-
- if pheno_tsv
-
- log2 = log(~isnan(log.rating),:);
- log2 = removevars(log2,{'onset','duration'});
- for n = 1:height(log2)
- log2.participant_id(n,:) = BIDSsubjs{sub};
- log2.run_id(n) = run;
- log2.trial_id_run(n) = n; % generates consecutive trial numbers within each run
- log2.trial_id_concat(n) = height(pheno_file_subj) + n; % generates consecutive trial numbers over all conditions & runs
- end
-
- for o = 1:size(events_interest,2)
- idx_run = log2.trial_type == events_interest{o};
- log2.trial_id_cond_run(idx_run) = 1:sum(idx_run);
- if height(pheno_file_subj) > 0
- idx_sub = pheno_file_subj.trial_type == events_interest{o};
- log2.trial_id_cond_concat(idx_run) = sum(idx_sub)+1:(sum(idx_sub)+sum(idx_run));
- else
- log2.trial_id_cond_concat = log2.trial_id_cond_run;
- end
- clear idx_run idx_sub
- end
-
- pheno_file_subj = [pheno_file_subj;log2];
- end
-
- clear logfile log time_zero filename log2
- end % if loop checking whether logfile exists
- end % for loop runs
-
- pheno_file = [pheno_file;pheno_file_subj];
- clear pheno_file_subj;
- end % for loop subjects
-
- pheno_filename = fullfile(pheno_dir,pheno_name);
- writetable(pheno_file,pheno_filename,'Filetype','text','Delimiter','\t');
-
- end % if loop checking sourcesubjs == BIDSsubjs
-
- end % if loop checking writing option
|