diff --git a/bids_export.m b/bids_export.m index 2e1bfa5..4368808 100644 --- a/bids_export.m +++ b/bids_export.m @@ -265,6 +265,8 @@ function bids_export(files, varargin) 'chanlookup' 'string' {} ''; 'defaced' 'string' {'on' 'off'} 'on'; 'createids' 'string' {'on' 'off'} 'on'; + 'singleEventsJson' 'string' {'on' 'off'} 'on'; + 'exportext' 'string' { 'edf' 'eeglab' } 'eeglab'; 'README' 'string' {} ''; 'CHANGES' 'string' {} '' ; 'copydata' 'real' [0 1] 1 }, 'bids_export'); @@ -276,8 +278,20 @@ function bids_export(files, varargin) % deleting folder fprintf('Exporting data to %s...\n', opt.targetdir); if exist(opt.targetdir,'dir') - disp('Deleting folder...') - rmdir(opt.targetdir, 's'); + uilist = { ... + { 'Style', 'text', 'string', 'Output directory exists and all current files will be deleted if continue', 'fontweight', 'bold' }, ... + { 'Style', 'text', 'string', 'Would you want to proceed?'}, ... + }; + geometry = { [1] [1]}; + geomvert = [1 1 ]; + [results,userdata,isOk,restag] = inputgui( 'geometry', geometry, 'geomvert', geomvert, 'uilist', uilist, 'title', 'Warning'); + if isempty(isOk) + disp('BIDS export cancelled...') + return + else + disp('Deleting folder...') + rmdir(opt.targetdir, 's'); + end end disp('Creating sub-directories...') @@ -447,11 +461,20 @@ function bids_export(files, varargin) % prepare event file information (_events.json) % ---------------------------- +eInfoDescFields = { 'LongName' 'optional' 'char' ''; + 'Levels' 'optional' 'struct' struct([]); + 'Description' 'optional' 'char' ''; + 'Units' 'optional' 'char' ''; + 'TermURL' 'optional' 'char' ''; + 'HED' 'optional' 'struct' struct([])}; fields = fieldnames(opt.eInfoDesc); for iField = 1:length(fields) descFields{1,4} = fields{iField}; if ~isfield(opt.eInfoDesc, fields{iField}), opt.eInfoDesc(1).(fields{iField}) = struct([]); end - opt.eInfoDesc.(fields{iField}) = checkfields(opt.eInfoDesc.(fields{iField}), descFields, 'eInfoDesc'); + opt.eInfoDesc.(fields{iField}) = checkfields(opt.eInfoDesc.(fields{iField}), eInfoDescFields, 'eInfoDesc'); +end +if strcmpi(opt.singleEventsJson, 'on') + jsonwrite(fullfile(opt.targetdir, ['task-' opt.taskName '_events.json' ]), opt.eInfoDesc,struct('indent',' ')); end % Write README files (README) @@ -601,22 +624,25 @@ function bids_export(files, varargin) switch bidscase case 1 % Single-Session Single-Run - fileOut = fullfile(opt.targetdir, subjectStr, 'eeg', [ subjectStr '_task-' char(files(iSubj).task) '_eeg' files(iSubj).file{1}(end-3:end)]); + [~,~,fileExt] = fileparts(files(iSubj).file{1}); + fileOut = fullfile(opt.targetdir, subjectStr, 'eeg', [ subjectStr '_task-' char(files(iSubj).task) '_eeg' fileExt]); % copy_data_bids( files(iSubj).file{1}, fileOut, opt.eInfo, opt.tInfo, opt.trialtype, chanlocs{iSubj}, opt.copydata); - copy_data_bids( files(iSubj).file{1}, fileOut, files(iSubj).notes{1}, opt, files(iSubj).chanlocs{1}, opt.copydata); + copy_data_bids( files(iSubj).file{1}, fileOut, files(iSubj).notes{1}, opt, files(iSubj).chanlocs{1}, opt.copydata, opt.exportext); case 2 % Single-Session Mult-Run for iRun = 1:length(files(iSubj).run) - fileOut = fullfile(opt.targetdir, subjectStr, 'eeg', [ subjectStr '_task-' char(files(iSubj).task(iRun)) '_run-' num2str(files(iSubj).run(iRun)) '_eeg' files(iSubj).file{iRun}(end-3:end) ]); - copy_data_bids( files(iSubj).file{iRun}, fileOut, files(iSubj).notes{iRun}, opt, files(iSubj).chanlocs{iRun}, opt.copydata); + [~,~,fileExt] = fileparts(files(iSubj).file{iRun}); + fileOut = fullfile(opt.targetdir, subjectStr, 'eeg', [ subjectStr '_task-' char(files(iSubj).task(iRun)) '_run-' num2str(files(iSubj).run(iRun)) '_eeg' fileExt ]); + copy_data_bids( files(iSubj).file{iRun}, fileOut, files(iSubj).notes{iRun}, opt, files(iSubj).chanlocs{iRun}, opt.copydata, opt.exportext); end case 3 % Mult-Session Single-Run for iSess = 1:length(unique(files(iSubj).session)) - fileOut = fullfile(opt.targetdir, subjectStr, sprintf('ses-%2.2d', iSess), 'eeg', [ subjectStr sprintf('_ses-%2.2d', iSess) '_task-' char(files(iSubj).task) '_eeg' files(iSubj).file{iSess}(end-3:end)]); - copy_data_bids( files(iSubj).file{iSess}, fileOut, files(iSubj).notes{iSess}, opt, files(iSubj).chanlocs{iSess}, opt.copydata); + [~,~,fileExt] = fileparts(files(iSubj).file{iSess}); + fileOut = fullfile(opt.targetdir, subjectStr, sprintf('ses-%2.2d', iSess), 'eeg', [ subjectStr sprintf('_ses-%2.2d', iSess) '_task-' char(files(iSubj).task{iSess}) '_eeg' fileExt]); + copy_data_bids( files(iSubj).file{iSess}, fileOut, files(iSubj).notes{iSess}, opt, files(iSubj).chanlocs{iSess}, opt.copydata, opt.exportext); end case 4 % Mult-Session Mult-Run @@ -625,23 +651,26 @@ function bids_export(files, varargin) runindx = find(files(iSubj).session == iSess); for iSet = runindx iRun = files(iSubj).run(iSet); - fileOut = fullfile(opt.targetdir, subjectStr, sprintf('ses-%2.2d', iSess), 'eeg', [ subjectStr sprintf('_ses-%2.2d', iSess) '_task-' char(files(iSubj).task(iRun)) '_run-' num2str(files(iSubj).run(iRun)) '_eeg' files(iSubj).file{iSet}(end-3:end)]); - copy_data_bids(files(iSubj).file{iSet}, fileOut, files(iSubj).notes{iSet}, opt, files(iSubj).chanlocs{iSet}, opt.copydata); + [~,~,fileExt] = fileparts(files(iSubj).file{iSet}); + fileOut = fullfile(opt.targetdir, subjectStr, sprintf('ses-%2.2d', iSess), 'eeg', [ subjectStr sprintf('_ses-%2.2d', iSess) '_task-' char(files(iSubj).task(iRun)) '_run-' num2str(files(iSubj).run(iRun)) '_eeg' fileExt]); + copy_data_bids(files(iSubj).file{iSet}, fileOut, files(iSubj).notes{iSet}, opt, files(iSubj).chanlocs{iSet}, opt.copydata, opt.exportext); end end case 5 % Mult Task: Single-Session Single-Run for iTask = 1:length(files(iSubj).task) - fileOut = fullfile(opt.targetdir, subjectStr, 'eeg', [ subjectStr '_task-' char(files(iSubj).task(iTask)) '_eeg' files(iSubj).file{iTask}(end-3:end) ]); - copy_data_bids( files(iSubj).file{iTask}, fileOut, files(iSubj).notes{iTask}, opt, files(iSubj).chanlocs{iTask}, opt.copydata); + [~,~,fileExt] = fileparts(files(iSubj).file{iTask}); + fileOut = fullfile(opt.targetdir, subjectStr, 'eeg', [ subjectStr '_task-' char(files(iSubj).task(iTask)) '_eeg' fileExt ]); + copy_data_bids( files(iSubj).file{iTask}, fileOut, files(iSubj).notes{iTask}, opt, files(iSubj).chanlocs{iTask}, opt.copydata, opt.exportext); end case 6 % Mult Task: Mult-Session Single-Run for iSess = 1:length(unique(files(iSubj).session)) runindx = find(files(iSubj).session == iSess); for iSet = runindx - fileOut = fullfile(opt.targetdir, subjectStr, sprintf('ses-%2.2d', iSess), 'eeg', [ subjectStr sprintf('_ses-%2.2d', iSess) '_task-' char(files(iSubj).task(iSet)) '_eeg' files(iSubj).file{iSet}(end-3:end)]); - copy_data_bids(files(iSubj).file{iSet}, fileOut, files(iSubj).notes{iSet}, opt, files(iSubj).chanlocs{iSet}, opt.copydata); + [~,~,fileExt] = fileparts(files(iSubj).file{iSet}); + fileOut = fullfile(opt.targetdir, subjectStr, sprintf('ses-%2.2d', iSess), 'eeg', [ subjectStr sprintf('_ses-%2.2d', iSess) '_task-' char(files(iSubj).task(iSet)) '_eeg' fileExt]); + copy_data_bids(files(iSubj).file{iSet}, fileOut, files(iSubj).notes{iSet}, opt, files(iSubj).chanlocs{iSet}, opt.copydata, opt.exportext); end end end @@ -650,7 +679,7 @@ function bids_export(files, varargin) %-------------------------------------------------------------------------- %-------------------------------------------------------------------------- %function copy_data_bids(fileIn, fileOut, eInfo, tInfo, trialtype, chanlocs, copydata) -function copy_data_bids(fileIn, fileOut, notes, opt, chanlocs, copydata) +function copy_data_bids(fileIn, fileOut, notes, opt, chanlocs, copydata, exportExt) folderOut = fileparts(fileOut); if ~exist(folderOut) mkdir(folderOut); @@ -672,11 +701,19 @@ function copy_data_bids(fileIn, fileOut, notes, opt, chanlocs, copydata) tInfo.EEGReference = 'CMS/DRL'; tInfo.Manufacturer = 'BIOSEMI'; EEG = pop_biosig(fileOut); +elseif strcmpi(ext, '.vhdr') + rename_brainvision_files(fileIn, fileOut, 'rmf', 'off'); + [fpathin, fname, ext] = fileparts(fileIn); + EEG = pop_loadbv(fpathin, [fname ext]); elseif strcmpi(ext, '.set') [outfilepath, outfilename,outfileext] = fileparts(fileOut); if copydata EEGin = pop_loadset(fileIn); - EEG = pop_saveset(EEGin, 'filename',[outfilename outfileext], 'filepath', outfilepath); + if strcmp(exportExt,'edf') + pop_writeeeg(EEG, [outfilepath filesep outfilename '.edf'], 'TYPE','EDF'); + else + EEG = pop_saveset(EEGin, 'filename',[outfilename outfileext], 'filepath', outfilepath); + end else copyfile(fileIn, fileOut); EEG = pop_loadset([outfilename outfileext],outfilepath, 'loadmode', 'info'); @@ -724,229 +761,235 @@ function copy_data_bids(fileIn, fileOut, notes, opt, chanlocs, copydata) % write event file information % --- _events.json -jsonwrite([ fileOut(1:end-7) 'events.json' ], opt.eInfoDesc,struct('indent',' ')); - -% --- _events.tsv -fid = fopen( [ fileOut(1:end-7) 'events.tsv' ], 'w'); - -% -- parse eInfo -if isempty(opt.eInfo) - if isfield(EEG.event, 'onset') opt.eInfo(end+1,:) = { 'onset' 'onset' }; +[folderOut,fileOut,~] = fileparts(fileOut); +fileOut = fullfile(folderOut,fileOut); +if ~isempty(EEG.event) + if strcmpi(opt.singleEventsJson,'off') + jsonwrite([ fileOut(1:end-3) 'events.json' ], opt.eInfoDesc,struct('indent',' ')); + end + % --- _events.tsv + + fid = fopen( [ fileOut(1:end-3) 'events.tsv' ], 'w'); + + % -- parse eInfo + if isempty(opt.eInfo) + if isfield(EEG.event, 'onset') opt.eInfo(end+1,:) = { 'onset' 'onset' }; else opt.eInfo(end+1,:) = { 'onset' 'latency' }; end - opt.eInfo(end+1,:) = { 'sample' 'latency' }; - if isfield(EEG.event, 'trial_type') opt.eInfo(end+1,:) = { 'trial_type' 'trial_type' }; - elseif ~isempty(opt.trialtype) opt.eInfo(end+1,:) = { 'trial_type' 'xxxx' }; end % to be filled with event type based on opt.trialtype mapping - if isfield(EEG.event, 'duration') opt.eInfo(end+1,:) = { 'duration' 'duration' }; end - if isfield(EEG.event, 'value') opt.eInfo(end+1,:) = { 'value' 'value' }; + opt.eInfo(end+1,:) = { 'sample' 'latency' }; + if isfield(EEG.event, 'trial_type') opt.eInfo(end+1,:) = { 'trial_type' 'trial_type' }; + elseif ~isempty(opt.trialtype) opt.eInfo(end+1,:) = { 'trial_type' 'xxxx' }; end % to be filled with event type based on opt.trialtype mapping + if isfield(EEG.event, 'duration') opt.eInfo(end+1,:) = { 'duration' 'duration' }; end + if isfield(EEG.event, 'value') opt.eInfo(end+1,:) = { 'value' 'value' }; else opt.eInfo(end+1,:) = { 'value' 'type' }; end - if isfield(EEG.event, 'response_time'), opt.eInfo(end+1,:) = { 'response_time' 'response_time' }; end - if isfield(EEG.event, 'stim_file'), opt.eInfo(end+1,:) = { 'stim_file' 'stim_file' }; end - if isfield(EEG.event, 'usertags'), opt.eInfo(end+1,:) = { 'HED' 'usertags' }; end -else - bids_fields = opt.eInfo(:,1); - if ~any(strcmp(bids_fields,'onset')) - if isfield(EEG.event, 'onset') - opt.eInfo(end+1,:) = { 'onset' 'onset' }; - else - opt.eInfo(end+1,:) = { 'onset' 'latency' }; + if isfield(EEG.event, 'response_time'), opt.eInfo(end+1,:) = { 'response_time' 'response_time' }; end + if isfield(EEG.event, 'stim_file'), opt.eInfo(end+1,:) = { 'stim_file' 'stim_file' }; end + if isfield(EEG.event, 'HED'), opt.eInfo(end+1,:) = { 'HED' 'HED' }; end + else + bids_fields = opt.eInfo(:,1); + if ~any(strcmp(bids_fields,'onset')) + if isfield(EEG.event, 'onset') + opt.eInfo(end+1,:) = { 'onset' 'onset' }; + else + opt.eInfo(end+1,:) = { 'onset' 'latency' }; + end end - end - if ~any(strcmp(bids_fields,'sample')) && isfield(EEG.event, 'latency'), opt.eInfo(end+1,:) = { 'sample' 'latency' }; end - if ~any(strcmp(bids_fields,'value')) - if isfield(EEG.event, 'value') - opt.eInfo(end+1,:) = { 'value' 'value' }; - else - opt.eInfo(end+1,:) = { 'value' 'type' }; + if ~any(strcmp(bids_fields,'sample')) && isfield(EEG.event, 'latency'), opt.eInfo(end+1,:) = { 'sample' 'latency' }; end + if ~any(strcmp(bids_fields,'value')) + if isfield(EEG.event, 'value') + opt.eInfo(end+1,:) = { 'value' 'value' }; + else + opt.eInfo(end+1,:) = { 'value' 'type' }; + end end + if ~any(strcmp(bids_fields,'duration')) && isfield(EEG.event, 'duration'), opt.eInfo(end+1,:) = { 'duration' 'duration' }; end + if ~isempty(opt.trialtype), opt.eInfo(end+1,:) = { 'trial_type' 'xxxx' }; end end - if ~any(strcmp(bids_fields,'duration')) && isfield(EEG.event, 'duration'), opt.eInfo(end+1,:) = { 'duration' 'duration' }; end - if ~isempty(opt.trialtype), opt.eInfo(end+1,:) = { 'trial_type' 'xxxx' }; end -end -if ~isempty(opt.stimuli) - opt.eInfo(end+1,:) = { 'stim_file' '' }; -end - -% reorder fields so it matches BIDS -fieldOrder = { 'onset' 'duration' 'sample' 'trial_type' 'response_time' 'stim_file' 'value' 'HED' }; -newOrder = []; -for iField = 1:length(fieldOrder) - ind = strmatch(fieldOrder{iField}, opt.eInfo(:,1)', 'exact'); - if isempty(ind) % add unfound field to opt.eInfo, skipping HED - if ~strcmpi(fieldOrder{iField}, 'HED') % skip HED (create problem with validator) - opt.eInfo(end+1,1:2) = { fieldOrder{iField} 'n/a' }; % indicating that there's no column in eInfo matching fieldOrder{iField} - ind = size(opt.eInfo,1); - else - ind = []; + if ~isempty(opt.stimuli) + opt.eInfo(end+1,:) = { 'stim_file' '' }; + end + + % reorder fields so it matches BIDS + fieldOrder = { 'onset' 'duration' 'sample' 'trial_type' 'response_time' 'stim_file' 'value'}; % 'HED' }; % remove HED from default column in events.tsv as HED tags should be put in events.json instead + newOrder = []; + for iField = 1:length(fieldOrder) + ind = strmatch(fieldOrder{iField}, opt.eInfo(:,1)', 'exact'); + if isempty(ind) % add unfound field to opt.eInfo, skipping HED + if ~strcmpi(fieldOrder{iField}, 'HED') % skip HED (create problem with validator) + opt.eInfo(end+1,1:2) = { fieldOrder{iField} 'n/a' }; % indicating that there's no column in eInfo matching fieldOrder{iField} + ind = size(opt.eInfo,1); + else + ind = []; + end end + newOrder = [ newOrder ind ]; end - newOrder = [ newOrder ind ]; -end -remainingInd = setdiff([1:size(opt.eInfo,1)], newOrder); -newOrder = [ newOrder remainingInd]; -opt.eInfo = opt.eInfo(newOrder,:); -fprintf(fid,'onset%s\n', sprintf('\t%s', opt.eInfo{2:end,1})); - -% scan events -for iEvent = 1:length(EEG.event) + remainingInd = setdiff([1:size(opt.eInfo,1)], newOrder); + newOrder = [ newOrder remainingInd]; + opt.eInfo = opt.eInfo(newOrder,:); + fprintf(fid,'onset%s\n', sprintf('\t%s', opt.eInfo{2:end,1})); - str = {}; - for iField = 1:size(opt.eInfo,1) + % scan events + for iEvent = 1:length(EEG.event) - tmpField = opt.eInfo{iField,2}; - if strcmpi(tmpField, 'n/a') - str{end+1} = tmpField; - else - switch opt.eInfo{iField,1} - - case 'onset' - onset = (EEG.event(iEvent).(tmpField)-1)/EEG.srate; - str{end+1} = sprintf('%1.10f', onset); - - case 'duration' - if isfield(EEG.event, tmpField) && ~isempty(EEG.event(iEvent).(tmpField)) - duration = num2str(EEG.event(iEvent).(tmpField), '%1.10f'); - else - duration = 'n/a'; - end - if isempty(duration) || strcmpi(duration, 'NaN') - duration = 'n/a'; - end - str{end+1} = duration; - - case 'sample' - if isfield(EEG.event, tmpField) - sample = num2str(EEG.event(iEvent).(tmpField)-1); - else - sample = 'n/a'; - end - if isempty(sample) || strcmpi(sample, 'NaN') - sample = 'n/a'; - end - str{end+1} = sample; + str = {}; + for iField = 1:size(opt.eInfo,1) + + tmpField = opt.eInfo{iField,2}; + if strcmpi(tmpField, 'n/a') + str{end+1} = tmpField; + else + switch opt.eInfo{iField,1} - case 'trial_type' - % trial type (which is the experimental condition - not the same as EEGLAB) - if isfield(EEG.event(iEvent), tmpField) && ~isempty(EEG.event(iEvent).(tmpField)) - trialType = EEG.event(iEvent).(tmpField); - else - trialType = 'STATUS'; - eventVal = EEG.event(iEvent).type; - if ~isempty(opt.trialtype) - % mapping on event value - if ~isempty(eventVal) - indTrial = strmatch(num2str(eventVal), opt.trialtype(:,1), 'exact'); - if ~isempty(indTrial) - trialType = opt.trialtype{indTrial,2}; + case 'onset' + onset = (EEG.event(iEvent).(tmpField)-1)/EEG.srate; + str{end+1} = sprintf('%1.10f', onset); + + case 'duration' + if isfield(EEG.event, tmpField) && ~isempty(EEG.event(iEvent).(tmpField)) + duration = num2str(EEG.event(iEvent).(tmpField), '%1.10f'); + else + duration = 'n/a'; + end + if isempty(duration) || strcmpi(duration, 'NaN') + duration = 'n/a'; + end + str{end+1} = duration; + + case 'sample' + if isfield(EEG.event, tmpField) + sample = num2str(EEG.event(iEvent).(tmpField)-1); + else + sample = 'n/a'; + end + if isempty(sample) || strcmpi(sample, 'NaN') + sample = 'n/a'; + end + str{end+1} = sample; + + case 'trial_type' + % trial type (which is the experimental condition - not the same as EEGLAB) + if isfield(EEG.event(iEvent), tmpField) && ~isempty(EEG.event(iEvent).(tmpField)) + trialType = EEG.event(iEvent).(tmpField); + else + trialType = 'STATUS'; + eventVal = EEG.event(iEvent).type; + if ~isempty(opt.trialtype) + % mapping on event value + if ~isempty(eventVal) + indTrial = strmatch(num2str(eventVal), opt.trialtype(:,1), 'exact'); + if ~isempty(indTrial) + trialType = opt.trialtype{indTrial,2}; + end end end - end - if insertEpoch - if any(indtle == iEvent) - trialType = 'Epoch'; + if insertEpoch + if any(indtle == iEvent) + trialType = 'Epoch'; + end end end - end - if isnumeric(trialType) - trialType = num2str(trialType); - end - str{end+1} = trialType; - - case 'response_time' - if isfield(EEG.event, tmpField) - response_time = num2str(EEG.event(iEvent).(tmpField)); - else - response_time = 'n/a'; - end - if isempty(response_time) || strcmpi(response_time, 'NaN') - response_time = 'n/a'; - end - str{end+1} = response_time; - - case 'stim_file' - if isempty(tmpField) - indStim = strmatch(EEG.event(iEvent).type, opt.stimuli(:,1)); - if ~isempty(indStim) - stim_file = opt.stimuli{indStim, 2}; + if isnumeric(trialType) + trialType = num2str(trialType); + end + str{end+1} = trialType; + + case 'response_time' + if isfield(EEG.event, tmpField) + response_time = num2str(EEG.event(iEvent).(tmpField)); else - stim_file = 'n/a'; + response_time = 'n/a'; end - elseif isfield(EEG.event, tmpField) - if ~isempty(opt.stimuli) - error('Cannot use "stim_file" as a BIDS event field and use the "stimuli" option') + if isempty(response_time) || strcmpi(response_time, 'NaN') + response_time = 'n/a'; end - stim_file = num2str(EEG.event(iEvent).(tmpField)); - else - stim_file = 'n/a'; - end - if isempty(stim_file) || strcmpi(stim_file, 'NaN') - stim_file = 'n/a'; - end - str{end+1} = stim_file; - - case 'value' - if isfield(EEG.event, tmpField) && ~isempty(EEG.event(iEvent).(tmpField)) - if isempty(opt.renametype) - eventValue = num2str(EEG.event(iEvent).(tmpField)); - else - posType = strmatch(num2str(EEG.event(iEvent).(tmpField)), opt.renametype(:,1), 'exact'); - if ~isempty(posType) - eventValue = opt.renametype{posType,2}; + str{end+1} = response_time; + + case 'stim_file' + if isempty(tmpField) + indStim = strmatch(EEG.event(iEvent).type, opt.stimuli(:,1)); + if ~isempty(indStim) + stim_file = opt.stimuli{indStim, 2}; else - eventValue = num2str(EEG.event(iEvent).(tmpField)); + stim_file = 'n/a'; end - end - if ~isempty(opt.checkresponse) - if iEvent+1 <= length(EEG.event) && strcmpi(EEG.event(iEvent+1).type, opt.checkresponse) && ~strcmpi(EEG.event(iEvent).type, opt.checkresponse) - eventValue = [ eventValue '_with_reponse' ]; - response_time = (EEG.event(iEvent+1).latency - EEG.event(iEvent).latency)/EEG.srate; - str{end-1} = num2str(response_time*1000,'%1.0f'); + elseif isfield(EEG.event, tmpField) + if ~isempty(opt.stimuli) + error('Cannot use "stim_file" as a BIDS event field and use the "stimuli" option') end + stim_file = num2str(EEG.event(iEvent).(tmpField)); + else + stim_file = 'n/a'; end - else - eventValue = 'n/a'; - end - if isequal(eventValue, 'NaN') || isempty(eventValue) - eventValue = 'n/a'; - end - str{end+1} = eventValue; - - case 'HED' - hed = 'n/a'; - if isfield(EEG.event, tmpField) && ~isempty(EEG.event(iEvent).(tmpField)) - hed = EEG.event(iEvent).(tmpField); - else - if isfield(EEG.event, 'usertags') && ~isempty(EEG.event(iEvent).usertags) - hed = EEG.event(iEvent).usertags; - if isfield(EEG.event, 'hedtags') && ~isempty(EEG.event(iEvent).hedtags) - hed = [hed ',' EEG.event(iEvent).hedtags]; + if isempty(stim_file) || strcmpi(stim_file, 'NaN') + stim_file = 'n/a'; + end + str{end+1} = stim_file; + + case 'value' + if isfield(EEG.event, tmpField) && ~isempty(EEG.event(iEvent).(tmpField)) + if isempty(opt.renametype) + eventValue = num2str(EEG.event(iEvent).(tmpField)); + else + posType = strmatch(num2str(EEG.event(iEvent).(tmpField)), opt.renametype(:,1), 'exact'); + if ~isempty(posType) + eventValue = opt.renametype{posType,2}; + else + eventValue = num2str(EEG.event(iEvent).(tmpField)); + end + end + if ~isempty(opt.checkresponse) + if iEvent+1 <= length(EEG.event) && strcmpi(EEG.event(iEvent+1).type, opt.checkresponse) && ~strcmpi(EEG.event(iEvent).type, opt.checkresponse) + eventValue = [ eventValue '_with_reponse' ]; + response_time = (EEG.event(iEvent+1).latency - EEG.event(iEvent).latency)/EEG.srate; + str{end-1} = num2str(response_time*1000,'%1.0f'); + end end - elseif isfield(EEG.event, 'hedtags') && ~isempty(EEG.event(iEvent).hedtags) - hed = EEG.event(iEvent).hedtags; + else + eventValue = 'n/a'; end - end - str{end+1} = hed; - - otherwise - if isfield(EEG.event, opt.eInfo{iField,2}) - tmpVal = num2str(EEG.event(iEvent).(opt.eInfo{iField,2})); - if isequal(tmpVal, 'NaN') + if isequal(eventValue, 'NaN') || isempty(eventValue) + eventValue = 'n/a'; + end + str{end+1} = eventValue; + +% case 'HED' +% hed = 'n/a'; +% if isfield(EEG.event, tmpField) && ~isempty(EEG.event(iEvent).(tmpField)) +% hed = EEG.event(iEvent).(tmpField); +% else +% if isfield(EEG.event, 'HED') && ~isempty(EEG.event(iEvent).usertags) +% hed = EEG.event(iEvent).usertags; +% if isfield(EEG.event, 'hedtags') && ~isempty(EEG.event(iEvent).hedtags) +% hed = [hed ',' EEG.event(iEvent).hedtags]; +% end +% elseif isfield(EEG.event, 'hedtags') && ~isempty(EEG.event(iEvent).hedtags) +% hed = EEG.event(iEvent).hedtags; +% end +% end +% str{end+1} = hed; + + otherwise + if isfield(EEG.event, opt.eInfo{iField,2}) + tmpVal = num2str(EEG.event(iEvent).(opt.eInfo{iField,2})); + if isequal(tmpVal, 'NaN') + tmpVal = 'n/a'; + end + else tmpVal = 'n/a'; end - else - tmpVal = 'n/a'; - end - str{end+1} = tmpVal; - end % switch + str{end+1} = tmpVal; + end % switch + end end + strConcat = sprintf('%s\t', str{:}); + fprintf(fid, '%s\n', strConcat(1:end-1)); end - strConcat = sprintf('%s\t', str{:}); - fprintf(fid, '%s\n', strConcat(1:end-1)); + fclose(fid); end -fclose(fid); % Write channel file information (channels.tsv) % Note: Consider using here electrodes_to_tsv.m -fid = fopen( [ fileOut(1:end-7) 'channels.tsv' ], 'w'); +fid = fopen( [ fileOut(1:end-3) 'channels.tsv' ], 'w'); miscChannels = 0; if ~isempty(chanlocs) @@ -973,7 +1016,8 @@ function copy_data_bids(fileIn, fileOut, notes, opt, chanlocs, copydata) else fprintf(fid, 'name\ttype\tunits\n'); acceptedChannelTypes = { 'AUDIO' 'EEG' 'EOG' 'ECG' 'EMG' 'EYEGAZE' 'GSR' 'HEOG' 'MISC' 'PUPIL' 'REF' 'RESP' 'SYSCLOCK' 'TEMP' 'TRIG' 'VEOG' }; - channelsCount = containers.Map(acceptedChannelTypes, zeros(1, numel(acceptedChannelTypes))); + channelsCount = []; + channelsCount.EEG = 0; for iChan = 1:EEG.nbchan % Type if ~isfield(EEG.chanlocs, 'type') || isempty(EEG.chanlocs(iChan).type) @@ -991,12 +1035,15 @@ function copy_data_bids(fileIn, fileOut, notes, opt, chanlocs, copydata) end % Count channels by type (for use later in eeg.json) - if strcmp(type, 'n/a') || strcmp(type, 'MISC') - channelsCount('MISC') = channelsCount('MISC') + 1; - elseif strcmp(type, 'HEOG') || strcmp(type,'VEOG') - channelsCount('EOG') = channelsCount('EOG') + 1; + if strcmp(type, 'n/a') + channelsCount.('EEG') = channelsCount.('EEG') + 1; else - channelsCount(type) = channelsCount(type) + 1; + if ~isfield(channelsCount, type), channelsCount.(type) = 0; end + if strcmp(type, 'HEOG') || strcmp(type,'VEOG') + channelsCount.('EOG') = channelsCount.('EOG') + 1; + else + channelsCount.(type) = channelsCount.(type) + 1; + end end %Write @@ -1017,11 +1064,11 @@ function copy_data_bids(fileIn, fileOut, notes, opt, chanlocs, copydata) end if ~isTemplate && ~isempty(EEG.chanlocs) && isfield(EEG.chanlocs, 'X') && ~isempty(EEG.chanlocs(2).X) - fid = fopen( [ fileOut(1:end-7) 'electrodes.tsv' ], 'w'); + fid = fopen( [ fileOut(1:end-3) 'electrodes.tsv' ], 'w'); fprintf(fid, 'name\tx\ty\tz\n'); for iChan = 1:EEG.nbchan - if isempty(EEG.chanlocs(iChan).X) + if isempty(EEG.chanlocs(iChan).X) || isnan(EEG.chanlocs(iChan).X) fprintf(fid, '%s\tn/a\tn/a\tn/a\n', EEG.chanlocs(iChan).labels ); else fprintf(fid, '%s\t%2.2f\t%2.2f\t%2.2f\n', EEG.chanlocs(iChan).labels, EEG.chanlocs(iChan).X, EEG.chanlocs(iChan).Y, EEG.chanlocs(iChan).Z ); @@ -1033,20 +1080,18 @@ function copy_data_bids(fileIn, fileOut, notes, opt, chanlocs, copydata) coordsystemStruct.EEGCoordinateUnits = 'mm'; coordsystemStruct.EEGCoordinateSystem = 'CTF'; % Change as soon as possible to EEGLAB coordsystemStruct.EEGCoordinateSystemDescription = 'EEGLAB'; - jsonwrite( [ fileOut(1:end-7) 'coordsystem.json' ], coordsystemStruct); + jsonwrite( [ fileOut(1:end-3) 'coordsystem.json' ], coordsystemStruct); end % Write task information (eeg.json) Note: depends on channels % requiredChannelTypes: 'EEG', 'EOG', 'ECG', 'EMG', 'MISC'. Other channel % types are currently not valid output for eeg.json. -nonEmptyChannelTypesIndices = find(cellfun(@(x) x(1),channelsCount.values)); -channelTypes = channelsCount.keys; -nonEmptyChannelTypes = channelTypes(nonEmptyChannelTypesIndices); +nonEmptyChannelTypes = fieldnames(channelsCount); for i=1:numel(nonEmptyChannelTypes) if strcmp(nonEmptyChannelTypes{i}, 'MISC') - tInfo.('MiscChannelCount') = channelsCount('MISC'); + tInfo.('MiscChannelCount') = channelsCount.('MISC'); else - tInfo.([nonEmptyChannelTypes{i} 'ChannelCount']) = channelsCount(nonEmptyChannelTypes{i}); + tInfo.([nonEmptyChannelTypes{i} 'ChannelCount']) = channelsCount.(nonEmptyChannelTypes{i}); end end @@ -1072,7 +1117,7 @@ function copy_data_bids(fileIn, fileOut, notes, opt, chanlocs, copydata) tInfo.SubjectArtefactDescription = notes; end % jsonStr = jsonencode(tInfo); -% fid = fopen( [fileOut(1:end-7) 'eeg.json' ], 'w'); +% fid = fopen( [fileOut(1:end-3) 'eeg.json' ], 'w'); % fprintf(fid, '%s', jsonStr); % fclose(fid); @@ -1096,7 +1141,7 @@ function copy_data_bids(fileIn, fileOut, notes, opt, chanlocs, copydata) 'EEGGround' 'RECOMMENDED ' 'char' ''; 'HeadCircumference' 'OPTIONAL ' '' 0; 'MiscChannelCount' ' OPTIONAL' '' ''; - 'TriggerChannelCount' 'RECOMMENDED' 'char' ''; + 'TriggerChannelCount' 'RECOMMENDED' 'char' ''; % double in Bucanl's fork 'EEGPlacementScheme' 'RECOMMENDED' 'char' ''; 'Manufacturer' 'RECOMMENDED' 'char' ''; 'ManufacturersModelName' 'OPTIONAL' 'char' ''; @@ -1111,7 +1156,7 @@ function copy_data_bids(fileIn, fileOut, notes, opt, chanlocs, copydata) 'SubjectArtefactDescription' 'OPTIONAL' 'char' '' }; tInfo = checkfields(tInfo, tInfoFields, 'tInfo'); -jsonwrite([fileOut(1:end-7) 'eeg.json' ], tInfo,struct('indent',' ')); +jsonwrite([fileOut(1:end-3) 'eeg.json' ], tInfo,struct('indent',' ')); % write channel information % cInfo.name.LongName = 'Channel name'; @@ -1121,7 +1166,7 @@ function copy_data_bids(fileIn, fileOut, notes, opt, chanlocs, copydata) % cInfo.units.LongName = 'Channel unit'; % cInfo.units.Description = 'Channel unit'; % jsonStr = jsonencode(cInfo); -% fid = fopen( [fileOut(1:end-7) 'channels.json' ], 'w'); +% fid = fopen( [fileOut(1:end-3) 'channels.json' ], 'w'); % fprintf(fid, '%s', jsonStr); % fclose(fid); @@ -1144,6 +1189,10 @@ function copy_data_bids(fileIn, fileOut, notes, opt, chanlocs, copydata) s = setfield(s, {1}, f{iRow,1}, f{iRow,4}); end elseif ~isempty(f{iRow,3}) && ~isa(s.(f{iRow,1}), f{iRow,3}) && ~strcmpi(s.(f{iRow,1}), 'n/a') + % if it's HED in eInfoDesc, allow string also + if strcmp(structName,'eInfoDesc') && strcmp(f{iRow,1}, 'HED') && isa(s.(f{iRow,1}), 'char') + return + end error(sprintf('Parameter %s.%s must be a %s', structName, f{iRow,1}, f{iRow,3})); end end @@ -1157,7 +1206,7 @@ function printEventHeader(eInfo) function writejson(fileName, matlabStruct) jsonStr = jsonencode(matlabStruct); -fid = fopen(fileName, 'w'); +fid = fopen(fileName, 'w', 'n', 'UTF-8'); if fid == -1, error('Cannot write file - make sure you have writing permission'); end fprintf(fid, '%s', jsonStr); fclose(fid); @@ -1165,7 +1214,7 @@ function writejson(fileName, matlabStruct) % write TSV file % -------------- function writetsv(fileName, matlabArray) -fid = fopen(fileName, 'w'); +fid = fopen(fileName, 'w', 'n', 'UTF-8'); if fid == -1, error('Cannot write file - make sure you have writing permission'); end for iRow=1:size(matlabArray,1) for iCol=1:size(matlabArray,2) diff --git a/bids_export_example.m b/bids_export_example.m index 2a9add0..ce5304e 100644 --- a/bids_export_example.m +++ b/bids_export_example.m @@ -1,126 +1,136 @@ % This function provides a comprehensive example of using the bids_export % function. Note that eventually, you may simply use bids_export({file1.set file2.set}) % and that all other parameters are highly recommended but optional. - -% You may not run this script because you do not have the associated data. -% It correspond to the actual dataset included in the BIDS EEG publication -% available at https://psyarxiv.com/63a4y. % -% The data itself is available at https://zenodo.org/record/1490922 +% You need the raw data files to run this script +% These can be downloaded from https://openneuro.org/datasets/ds001787 +% Then enter the path in this script to re-export the BDF files % -% A. Delorme - Jan 2019 +% Arnaud Delorme - Jan 2019 + +dataPath = '/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/'; % can be a Windows path +targetPath = '/Users/arno/temp/bids_meditation_export'; +nSubject = 24; % enter number of subject to export (for example 2 will only export the first 2 subjects) + +if ~exist(fullfile(dataPath, 'sub-001', 'ses-01', 'eeg', 'sub-001_ses-01_task-meditation_eeg.bdf'), 'file') + disp('You need the raw data files to run this script'); + disp('These can be downloaded from https://openneuro.org/datasets/ds001787'); + disp('Then enter the path in this script to re-export the BDF files'); + return +end +clear data generalInfo pInfo pInfoDesc eInfoDesc README CHANGES stimuli code tInfo chanlocs; % raw data files (replace with your own) % ---------------------------------- -data(1).file = {'/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-001/ses-01/eeg/sub-001_ses-01_task-meditation_eeg.bdf' - '/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-001/ses-02/eeg/sub-001_ses-02_task-meditation_eeg.bdf' }; +data(1).file = {fullfile(dataPath, 'sub-001', 'ses-01', 'eeg', 'sub-001_ses-01_task-meditation_eeg.bdf') + fullfile(dataPath, 'sub-001', 'ses-02', 'eeg', 'sub-001_ses-02_task-meditation_eeg.bdf') }; data(1).session = [1 2]; data(1).run = [1 1]; -data(2).file = {'/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-002/ses-01/eeg/sub-002_ses-01_task-meditation_eeg.bdf' - '/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-002/ses-02/eeg/sub-002_ses-02_task-meditation_eeg.bdf'}; +data(2).file = {fullfile(dataPath, 'sub-002', 'ses-01', 'eeg', 'sub-002_ses-01_task-meditation_eeg.bdf') + fullfile(dataPath, 'sub-002', 'ses-02', 'eeg', 'sub-002_ses-02_task-meditation_eeg.bdf')}; data(2).session = [1 2]; data(2).run = [1 1]; -data(3).file = {'/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-003/ses-01/eeg/sub-003_ses-01_task-meditation_eeg.bdf' - '/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-003/ses-02/eeg/sub-003_ses-02_task-meditation_eeg.bdf'}; +data(3).file = {fullfile(dataPath, 'sub-003', 'ses-01', 'eeg', 'sub-003_ses-01_task-meditation_eeg.bdf') + fullfile(dataPath, 'sub-003', 'ses-02', 'eeg', 'sub-003_ses-02_task-meditation_eeg.bdf')}; data(3).session = [1 2]; data(3).run = [1 1]; -data(4).file = {'/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-004/ses-01/eeg/sub-004_ses-01_task-meditation_eeg.bdf' - '/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-004/ses-02/eeg/sub-004_ses-02_task-meditation_eeg.bdf'}; +data(4).file = {fullfile(dataPath, 'sub-004', 'ses-01', 'eeg', 'sub-004_ses-01_task-meditation_eeg.bdf') + fullfile(dataPath, 'sub-004', 'ses-02', 'eeg', 'sub-004_ses-02_task-meditation_eeg.bdf')}; data(4).session = [1 2]; data(4).run = [1 1]; -data(5).file = {'/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-005/ses-01/eeg/sub-005_ses-01_task-meditation_eeg.bdf' - '/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-005/ses-02/eeg/sub-005_ses-02_task-meditation_eeg.bdf'}; +data(5).file = {fullfile(dataPath, 'sub-005', 'ses-01', 'eeg', 'sub-005_ses-01_task-meditation_eeg.bdf') + fullfile(dataPath, 'sub-005', 'ses-02', 'eeg', 'sub-005_ses-02_task-meditation_eeg.bdf')}; data(5).session = [1 2]; data(5).run = [1 1]; -data(6).file = {'/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-006/ses-01/eeg/sub-006_ses-01_task-meditation_eeg.bdf' - '/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-006/ses-02/eeg/sub-006_ses-02_task-meditation_eeg.bdf'}; +data(6).file = {fullfile(dataPath, 'sub-006', 'ses-01', 'eeg', 'sub-006_ses-01_task-meditation_eeg.bdf') + fullfile(dataPath, 'sub-006', 'ses-02', 'eeg', 'sub-006_ses-02_task-meditation_eeg.bdf')}; data(6).session = [1 2]; data(6).run = [1 1]; -data(7).file = {'/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-007/ses-01/eeg/sub-007_ses-01_task-meditation_eeg.bdf' - '/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-007/ses-02/eeg/sub-007_ses-02_task-meditation_eeg.bdf'}; +data(7).file = {fullfile(dataPath, 'sub-007', 'ses-01', 'eeg', 'sub-007_ses-01_task-meditation_eeg.bdf') + fullfile(dataPath, 'sub-007', 'ses-02', 'eeg', 'sub-007_ses-02_task-meditation_eeg.bdf')}; data(7).session = [1 2]; data(7).run = [1 1]; -data(8).file = {'/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-008/ses-01/eeg/sub-008_ses-01_task-meditation_eeg.bdf'}; +data(8).file = {fullfile(dataPath, 'sub-008', 'ses-01', 'eeg', 'sub-008_ses-01_task-meditation_eeg.bdf')}; data(8).session = 1; data(8).run = 1; -data(9).file = {'/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-009/ses-01/eeg/sub-009_ses-01_task-meditation_eeg.bdf' - '/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-009/ses-02/eeg/sub-009_ses-02_task-meditation_eeg.bdf'}; +data(9).file = {fullfile(dataPath, 'sub-009', 'ses-01', 'eeg', 'sub-009_ses-01_task-meditation_eeg.bdf') + fullfile(dataPath, 'sub-009', 'ses-02', 'eeg', 'sub-009_ses-02_task-meditation_eeg.bdf')}; data(9).session = [1 2]; data(9).run = [1 1]; -data(10).file = {'/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-010/ses-01/eeg/sub-010_ses-01_task-meditation_eeg.bdf' - '/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-010/ses-02/eeg/sub-010_ses-02_task-meditation_eeg.bdf'}; +data(10).file = {fullfile(dataPath, 'sub-010', 'ses-01', 'eeg', 'sub-010_ses-01_task-meditation_eeg.bdf') + fullfile(dataPath, 'sub-010', 'ses-02', 'eeg', 'sub-010_ses-02_task-meditation_eeg.bdf')}; data(10).session = [1 2]; data(10).run = [1 1]; -data(11).file = {'/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-011/ses-01/eeg/sub-011_ses-01_task-meditation_eeg.bdf' - '/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-011/ses-02/eeg/sub-011_ses-02_task-meditation_eeg.bdf'}; +data(11).file = {fullfile(dataPath, 'sub-011', 'ses-01', 'eeg', 'sub-011_ses-01_task-meditation_eeg.bdf') + fullfile(dataPath, 'sub-011', 'ses-02', 'eeg', 'sub-011_ses-02_task-meditation_eeg.bdf')}; data(11).session = [1 2]; data(11).run = [1 1]; -data(12).file = {'/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-012/ses-01/eeg/sub-012_ses-01_task-meditation_eeg.bdf'}; +data(12).file = {fullfile(dataPath, 'sub-012', 'ses-01', 'eeg', 'sub-012_ses-01_task-meditation_eeg.bdf')}; data(12).session = 1; data(12).run = 1; -data(13).file = {'/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-013/ses-01/eeg/sub-013_ses-01_task-meditation_eeg.bdf'}; +data(13).file = {fullfile(dataPath, 'sub-013', 'ses-01', 'eeg', 'sub-013_ses-01_task-meditation_eeg.bdf')}; data(13).session = 1; data(13).run = 1; -data(14).file = {'/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-014/ses-01/eeg/sub-014_ses-01_task-meditation_eeg.bdf'}; +data(14).file = {fullfile(dataPath, 'sub-014', 'ses-01', 'eeg', 'sub-014_ses-01_task-meditation_eeg.bdf')}; data(14).session = 1; data(14).run = 1; -data(15).file = {'/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-015/ses-01/eeg/sub-015_ses-01_task-meditation_eeg.bdf'}; +data(15).file = {fullfile(dataPath, 'sub-015', 'ses-01', 'eeg', 'sub-015_ses-01_task-meditation_eeg.bdf')}; data(15).session = 1; data(15).run = 1; -data(16).file = {'/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-016/ses-01/eeg/sub-016_ses-01_task-meditation_eeg.bdf' - '/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-016/ses-02/eeg/sub-016_ses-02_task-meditation_eeg.bdf'}; +data(16).file = {fullfile(dataPath, 'sub-016', 'ses-01', 'eeg', 'sub-016_ses-01_task-meditation_eeg.bdf') + fullfile(dataPath, 'sub-016', 'ses-02', 'eeg', 'sub-016_ses-02_task-meditation_eeg.bdf')}; data(16).session = [1 2]; data(16).run = [1 1]; -data(17).file = {'/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-017/ses-01/eeg/sub-017_ses-01_task-meditation_eeg.bdf' - '/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-017/ses-02/eeg/sub-017_ses-02_task-meditation_eeg.bdf'}; +data(17).file = {fullfile(dataPath, 'sub-017', 'ses-01', 'eeg', 'sub-017_ses-01_task-meditation_eeg.bdf') + fullfile(dataPath, 'sub-017', 'ses-02', 'eeg', 'sub-017_ses-02_task-meditation_eeg.bdf')}; data(17).session = [1 2]; data(17).run = [1 1]; -data(18).file = {'/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-018/ses-01/eeg/sub-018_ses-01_task-meditation_eeg.bdf' - '/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-018/ses-02/eeg/sub-018_ses-02_task-meditation_eeg.bdf'}; +data(18).file = {fullfile(dataPath, 'sub-018', 'ses-01', 'eeg', 'sub-018_ses-01_task-meditation_eeg.bdf') + fullfile(dataPath, 'sub-018', 'ses-02', 'eeg', 'sub-018_ses-02_task-meditation_eeg.bdf')}; data(18).session = [1 2]; data(18).run = [1 1]; -data(19).file = {'/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-019/ses-01/eeg/sub-019_ses-01_task-meditation_eeg.bdf'}; +data(19).file = {fullfile(dataPath, 'sub-019', 'ses-01', 'eeg', 'sub-019_ses-01_task-meditation_eeg.bdf')}; data(19).session = 1; data(19).run = 1; -data(20).file = {'/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-020/ses-01/eeg/sub-020_ses-01_task-meditation_eeg.bdf'}; +data(20).file = {fullfile(dataPath, 'sub-020', 'ses-01', 'eeg', 'sub-020_ses-01_task-meditation_eeg.bdf')}; data(20).session = 1; data(20).run = 1; -data(21).file = {'/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-021/ses-01/eeg/sub-021_ses-01_task-meditation_eeg.bdf'}; +data(21).file = {fullfile(dataPath, 'sub-021', 'ses-01', 'eeg', 'sub-021_ses-01_task-meditation_eeg.bdf')}; data(21).session = 1; data(21).run = 1; -data(22).file = {'/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-022/ses-01/eeg/sub-022_ses-01_task-meditation_eeg.bdf' - '/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-022/ses-02/eeg/sub-022_ses-02_task-meditation_eeg.bdf' - '/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-022/ses-03/eeg/sub-022_ses-03_task-meditation_eeg.bdf'}; +data(22).file = {fullfile(dataPath, 'sub-022', 'ses-01', 'eeg', 'sub-022_ses-01_task-meditation_eeg.bdf') + fullfile(dataPath, 'sub-022', 'ses-02', 'eeg', 'sub-022_ses-02_task-meditation_eeg.bdf') + fullfile(dataPath, 'sub-022', 'ses-03', 'eeg', 'sub-022_ses-03_task-meditation_eeg.bdf')}; data(22).session = [1 2 3]; data(22).run = [1 1 1]; -data(23).file = {'/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-023/ses-01/eeg/sub-023_ses-01_task-meditation_eeg.bdf' - '/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-023/ses-02/eeg/sub-023_ses-02_task-meditation_eeg.bdf'}; +data(23).file = {fullfile(dataPath, 'sub-023', 'ses-01', 'eeg', 'sub-023_ses-01_task-meditation_eeg.bdf') + fullfile(dataPath, 'sub-023', 'ses-02', 'eeg', 'sub-023_ses-02_task-meditation_eeg.bdf')}; data(23).session = [1 2]; data(23).run = [1 1]; -data(24).file = {'/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/sub-024/ses-01/eeg/sub-024_ses-01_task-meditation_eeg.bdf' }; +data(24).file = {fullfile(dataPath, 'sub-024', 'ses-01', 'eeg', 'sub-024_ses-01_task-meditation_eeg.bdf') }; data(24).session = 1; data(24).run = 1; @@ -156,7 +166,12 @@ 'F' 31 'novice'; 'M' 50 'novice'; 'F' 38 'novice' }; - + +% select subset of subject to export +% ---------------------------------- +pInfo(nSubject+2:end,:) = []; +data(nSubject+1:end) = []; + % participant column description for participants.json file % --------------------------------------------------------- pInfoDesc.gender.Description = 'sex of the participant'; @@ -209,24 +224,25 @@ % List of stimuli to be copied to the stimuli folder % -------------------------------------------------- -stimuli = {'/data/matlab/tracy_mw/rate_mw.wav' - '/data/matlab/tracy_mw/rate_meditation.wav' - '/data/matlab/tracy_mw/rate_tired.wav' - '/data/matlab/tracy_mw/expe_over.wav' - '/data/matlab/tracy_mw/mind_wandering.wav' - '/data/matlab/tracy_mw/self.wav' - '/data/matlab/tracy_mw/time.wav' - '/data/matlab/tracy_mw/valence.wav' - '/data/matlab/tracy_mw/depth.wav' - '/data/matlab/tracy_mw/resume.wav' - '/data/matlab/tracy_mw/resumed.wav' - '/data/matlab/tracy_mw/resumemed.wav' - '/data/matlab/tracy_mw/cancel.wav' - '/data/matlab/tracy_mw/starting.wav' }; +stimuli = { ... + fullfile( dataPath, 'stimuli', 'rate_mw.wav') + fullfile( dataPath, 'stimuli', 'rate_meditation.wav') + fullfile( dataPath, 'stimuli', 'rate_tired.wav') + fullfile( dataPath, 'stimuli', 'expe_over.wav') + fullfile( dataPath, 'stimuli', 'mind_wandering.wav') + fullfile( dataPath, 'stimuli', 'self.wav') + fullfile( dataPath, 'stimuli', 'time.wav') + fullfile( dataPath, 'stimuli', 'valence.wav') + fullfile( dataPath, 'stimuli', 'depth.wav') + fullfile( dataPath, 'stimuli', 'resume.wav') + fullfile( dataPath, 'stimuli', 'resumed.wav') + fullfile( dataPath, 'stimuli', 'resumemed.wav') + fullfile( dataPath, 'stimuli', 'cancel.wav') + fullfile( dataPath, 'stimuli', 'starting.wav') }; % List of script to run the experiment % ------------------------------------ -code = { '/data/matlab/tracy_mw/run_mw_experiment6.m' mfilename('fullpath') }; +code = { fullfile( dataPath, 'code', 'run_mw_experiment6.m') mfilename('fullpath') }; % Task information for xxxx-eeg.json file % --------------------------------------- @@ -247,8 +263,8 @@ % channel location file % --------------------- -chanlocs = '/data/matlab/bids_matlab/BIDS_EEG_meditation_raw_do_not_delete/channel_loc_file.ced'; +chanlocs = []; %fullfile(dataPath, 'channel_loc_file.ced'; % call to the export function % --------------------------- -bids_export(data, 'targetdir', '/Users/arno/temp/bids_meditation_export', 'taskName', 'meditation', 'trialtype', trialTypes, 'gInfo', generalInfo, 'pInfo', pInfo, 'pInfoDesc', pInfoDesc, 'eInfoDesc', eInfoDesc, 'README', README, 'CHANGES', CHANGES, 'stimuli', stimuli, 'codefiles', code, 'tInfo', tInfo, 'chanlocs', chanlocs); +bids_export(data, 'targetdir', targetPath, 'taskName', 'meditation', 'trialtype', trialTypes, 'gInfo', generalInfo, 'pInfo', pInfo, 'pInfoDesc', pInfoDesc, 'eInfoDesc', eInfoDesc, 'README', README, 'CHANGES', CHANGES, 'stimuli', stimuli, 'codefiles', code, 'tInfo', tInfo, 'chanlocs', chanlocs); diff --git a/bids_export_example2.m b/bids_export_example2.m index 960d4ca..3058bd7 100644 --- a/bids_export_example2.m +++ b/bids_export_example2.m @@ -1,11 +1,15 @@ % Matlab script to export to BIDS % Exported dataset is available at https://openneuro.org/datasets/ds003061 % +% You may not use this script because you do not have the original data +% It is provided as an example to be adapted for your specific need +% % Arnaud Delorme - Feb 2020 % export notes % export event type problem % export event value problem +return clear diff --git a/bids_export_example3.m b/bids_export_example3.m new file mode 100644 index 0000000..e01ed18 --- /dev/null +++ b/bids_export_example3.m @@ -0,0 +1,140 @@ +% Matlab script to export to BIDS +% This is a simple example to export the tutorial EEGLAB dataset +% You can run this example and checks that the data passes the BIDS +% validator, then modify it for your own purpose +% +% Arnaud Delorme - Oct 2021 + +data = []; +p =fileparts(which('eeglab')); +data(end+1).file = { fullfile(p, 'sample_data', 'eeglab_data.set') }; +data(end ).session = [1]; +data(end ).run = [1]; +data(end ).task = { 'p300' }; +data(end ).notes = { 'No notes' }; + +%% participant information for participants.tsv file +% ------------------------------------------------- +pInfo = { 'gender' 'age'; + 'M' 22 }; + +%% Code Files used to preprocess and import to BIDS +% -----------------------------------------------------| +codefiles = { fullfile(pwd, mfilename) }; + +%% general information for dataset_description.json file +% ----------------------------------------------------- +generalInfo.Name = 'P300 visual task'; +generalInfo.ReferencesAndLinks = { 'No bibliographic reference other than the DOI for this dataset' }; +generalInfo.BIDSVersion = 'v1.2.1'; +generalInfo.License = 'CC0'; +generalInfo.Authors = { 'Arnaud Delorme' 'Scott Makeig' 'Marissa Westerfield' }; + +%% participant column description for participants.json file +% --------------------------------------------------------- +pInfoDesc.participant_id.LongName = 'Participant identifier'; +pInfoDesc.participant_id.Description = 'Unique participant identifier'; + +pInfoDesc.gender.Description = 'Sex of the participant'; +pInfoDesc.gender.Levels.M = 'male'; +pInfoDesc.gender.Levels.F = 'female'; + +pInfoDesc.age.Description = 'age of the participant'; +pInfoDesc.age.Units = 'years'; + +%% event column description for xxx-events.json file (only one such file) +% ---------------------------------------------------------------------- +eInfo = {'onset' 'latency'; + 'sample' 'latency'; + 'value' 'type' }; % ADD HED HERE + +eInfoDesc.onset.Description = 'Event onset'; +eInfoDesc.onset.Units = 'second'; + +eInfoDesc.response_time.Description = 'Latency of button press after auditory stimulus'; +eInfoDesc.response_time.Levels.Units = 'millisecond'; + +% You do not need to define both trial type and value in this simple +% example, but it is good to know that both exist. There is no definite +% rule regarding the difference between these two fields. As their name +% indicate, "trial_type" contains the type of trial and "value" contains +% more information about a trial of given type. +eInfoDesc.trial_type.Description = 'Type of event'; +eInfoDesc.trial_type.Levels.stimulus = 'Visual stimulus'; +eInfoDesc.trial_type.Levels.response = 'Response of participant'; + +eInfoDesc.value.Description = 'Value of event'; +eInfoDesc.value.Levels.square = 'Square visual stimulus'; +eInfoDesc.value.Levels.rt = 'Behavioral response'; + +% This allow to define trial types based on EEGLAB type - it is optional +trialTypes = { 'rt' 'response'; + 'square' 'stimulus' }; + +%% Content for README file +% ----------------------- +README = [ 'EEGLAB Tutorial Dataset ' 10 ... +'' 10 ... +'During this selective visual attention experiment, ' 10 ... +'stimuli appeared briefly in any of five squares ' 10 ... +'arrayed horizontally above a central fixation cross. ' 10 ... +'In each experimental block, one (target) box was ' 10 ... +'differently colored from the rest Whenever a square ' 10 ... +'appeared in the target box the subject was asked to ' 10 ... +'respond quickly with a right thumb button press. If ' 10 ... +'the stimulus was a circular disk, he was asked to ' 10 ... +'ignore it.' 10 ... +'' 10 ... +'These data were constructed by concatenating ' 10 ... +'three-second epochs from one subject, each containing' 10 ... +'a target square in the attended location (''square'' ' 10 ... +'events, left-hemifield locations 1 or 2 only) ' 10 ... +'followed by a button response (''rt'' events). The data' 10 ... +'were stored in continuous data format to illustrate ' 10 ... +'the process of epoch extraction from continuous data.' ]; + +%% Content for CHANGES file +% ------------------------ +CHANGES = sprintf([ 'Version 1.0 - 4 Aug 2020\n' ... + ' - Initial release\n' ]); + +%% Task information for xxxx-eeg.json file +% --------------------------------------- +tInfo.InstitutionAddress = '9500 Gilman Drive, La Jolla CA 92093, USA'; +tInfo.InstitutionName = 'University of California, San Diego'; +tInfo.InstitutionalDepartmentName = 'Institute of Neural Computation'; +tInfo.PowerLineFrequency = 60; +tInfo.ManufacturersModelName = 'Snapmaster'; +%tInfo.Reference = 'Delorme A, Westerfield M, Makeig S. Medial prefrontal theta bursts precede rapid motor responses during visual selective attention. J Neurosci. 2007 Oct 31;27(44):11949-59. doi: 10.1523/JNEUROSCI.3477-07.2007. PMID: 17978035; PMCID: PMC6673364.' +% tInfo.Instructions + + +% call to the export function +% --------------------------- +targetFolder = './BIDS'; +bids_export(data, ... + 'targetdir', targetFolder, ... + 'taskName', 'P300',... + 'gInfo', generalInfo, ... + 'pInfo', pInfo, ... + 'pInfoDesc', pInfoDesc, ... + 'eInfo', eInfo, ... + 'eInfoDesc', eInfoDesc, ... + 'README', README, ... + 'CHANGES', CHANGES, ... + 'codefiles', codefiles, ... + 'trialtype', trialTypes, ... + 'chanlookup', '/data/matlab/eeglab/plugins/dipfit/standard_BEM/elec/standard_1005.elc', ... + 'renametype', {}, ... + 'checkresponse', 'condition 1', ... + 'tInfo', tInfo, ... + 'copydata', 1); +% +% % copy stimuli and source data folders +% % ----------------------------------- +% copyfile('../stimuli', fullfile(targetFolder, 'stimuli'), 'f'); +% copyfile('../sourcedata', fullfile(targetFolder, 'sourcedata'), 'f'); + +fprintf(2, 'WHAT TO DO NEXT?') +fprintf(2, ' -> upload the %s folder to http://openneuro.org to check it is valid\n', targetFolder); + diff --git a/eegplugin_bids.m b/eegplugin_bids.m index 9653919..e31996d 100644 --- a/eegplugin_bids.m +++ b/eegplugin_bids.m @@ -44,8 +44,8 @@ % create BIDS menus % ----------------- comtaskinfo = [trystrs.no_check '[EEG,LASTCOM] = pop_taskinfo(EEG);' catchstrs.store_and_hist ]; - comsubjinfo = [trystrs.no_check '[EEG,LASTCOM] = pop_participantinfo(EEG,STUDY);' catchstrs.store_and_hist ]; - comeventinfo = [trystrs.no_check '[EEG,LASTCOM] = pop_eventinfo(EEG);' catchstrs.store_and_hist ]; + comsubjinfo = [trystrs.no_check '[EEG,STUDY,LASTCOM] = pop_participantinfo(EEG,STUDY);' catchstrs.store_and_hist ]; + comeventinfo = [trystrs.no_check '[EEG,STUDY,LASTCOM] = pop_eventinfo(EEG,STUDY);' catchstrs.store_and_hist ]; % comvalidatebids = [ trystrs.no_check 'if plugin_askinstall(''bids-validator'',''pop_validatebids'') == 1 pop_validatebids() end' catchstrs.add_to_hist ]; bids = findobj(fig, 'label', 'BIDS tools'); if isempty(bids) diff --git a/pop_eventinfo.m b/pop_eventinfo.m index 2a15f1a..006361f 100644 --- a/pop_eventinfo.m +++ b/pop_eventinfo.m @@ -14,29 +14,36 @@ % 'EEG' - [struct] Updated EEG structure containing event BIDS information % in each EEG structure at EEG.BIDS.eInfoDesc and EEG.BIDS.eInfo % -% 'eInfoDesc' - [struct] structure describing BIDS event fields as you specified. +% 'eInfoDesc' - [struct] structure describing BIDS event fields as you specified. % See BIDS specification for all suggested fields. % -% 'eInfo' - [cell] BIDS event fields and their corresponding +% 'eInfo' - [cell] BIDS event fields and their corresponding % event fields in the EEGLAB event structure. Note that % EEGLAB event latency, duration, and type are inserted % automatically as columns "onset" (latency in sec), "duration" % (duration in sec), "value" (EEGLAB event type) % % Author: Dung Truong, Arnaud Delorme -function [EEG, command] = pop_eventinfo(EEG, varargin) +function [EEG, STUDY, command] = pop_eventinfo(EEG, STUDY, varargin) %% check if there's already an opened window if ~isempty(findobj('Tag','eventBidsTable')) errordlg2('A window is already openened for pop_eventinfo'); return end - command = '[EEG, command] = pop_eventinfo(EEG)'; + command = '[EEG, [], command] = pop_eventinfo(EEG)'; + %% if STUDY is provided, check for consistency + hasSTUDY = false; + if exist('STUDY','var') && ~isempty(STUDY) + [STUDY, EEG] = pop_checkdatasetinfo(STUDY, EEG); + command = '[EEG, STUDY, command] = pop_eventinfo(EEG, STUDY);'; + hasSTUDY = true; + end + % perform check to make sure EEG.event is consistent across EEG if isempty(EEG(1).event) errordlg2('EEG.event is empty for first dataset'); - return - + return end try eventFields = fieldnames([EEG.event]); @@ -48,7 +55,7 @@ warning('There is mismatch in number of fields in EEG.event structures. Using fields of EEG(%d) which has the highest number of fields (%d).', index, num); end end - bidsFields = {'onset', 'duration', 'trial_type','value','stim_file','sample','response_time','HED'}; + bidsFields = {'onset', 'duration', 'trial_type','value','stim_file','sample','response_time'}; eventFields = setdiff(eventFields, 'latency'); % define global variables % ----------------------- @@ -63,7 +70,7 @@ fontSize = 12; % Use GUI - if nargin < 2 + if nargin < 3 % create UI f = figure('MenuBar', 'None', 'ToolBar', 'None', 'Name', 'Edit BIDS event info - pop_eventinfo', 'Color', bg); f.Position(3) = appWidth; @@ -110,7 +117,7 @@ if isfield(eventBIDS.(field), 'Levels') && ~isempty(eventBIDS.(field).Levels) data{i,strcmp(tbl.ColumnName, 'Levels')} = strjoin(fieldnames(eventBIDS.(field).Levels),','); else - if strcmp(field, 'onset') || strcmp(field, "sample") || strcmp(field, "duration") || strcmp(field, "HED") + if strcmp(field, 'onset') || strcmp(field, "sample") || strcmp(field, "duration") data{i,strcmp(tbl.ColumnName, 'Levels')} = 'n/a'; else data{i,strcmp(tbl.ColumnName, 'Levels')} = 'Click to specify below'; @@ -122,7 +129,7 @@ tbl.Data = data; waitfor(f); % Use default value - pop_eventinfo(EEG,'default') called - elseif nargin < 3 && ischar(varargin{1}) && strcmp(varargin{1}, 'default') + elseif nargin < 4 && ischar(varargin{1}) && strcmp(varargin{1}, 'default') done(); end @@ -201,6 +208,14 @@ function done() % prepare return struct fields = fieldnames(eventBIDS); + fMap = []; + if hasSTUDY && isfield(STUDY.etc, 'tags') + hedTags = STUDY.etc.tags; + fMap = fieldMap.createfMapFromStruct(hedTags); + elseif isfield(EEG(1).etc,'tags') + hedTags = EEG(1).etc.tags; + fMap = fieldMap.createfMapFromStruct(hedTags); + end for k=1:length(fields) bidsField = fields{k}; if ~isfield(eventBIDS.(bidsField), 'EEGField') @@ -225,17 +240,38 @@ function done() if isfield(eventBIDS.(bidsField),'Levels') && ~isempty(eventBIDS.(bidsField).Levels) && ~strcmp(eventBIDS.(bidsField).Levels,'n/a') eInfoDesc.(bidsField).Levels = eventBIDS.(bidsField).Levels; end + % parse HED if exists + if ~isempty(fMap) + tMap = fMap.getMap(eegField); + if ~isempty(tMap) && tMap.hasAnnotation() + codes = tMap.getCodes(); + if numel(codes) == 1 && strcmp(codes{1},'HED') + tList = tMap.getValue('HED'); + if tList.hasAnnotation() + eInfoDesc.(bidsField).HED = tagList.stringify(tList.getTags()); + end + else + for c=1:numel(codes) + tList = tMap.getValue(codes{c}); + % only add HED tags for the ones that + if tList.hasAnnotation() + eInfoDesc.(bidsField).HED.(codes{c}) = tagList.stringify(tList.getTags()); + end + end + end + end + end if isfield(eventBIDS.(bidsField), 'TermURL') && ~isempty(eventBIDS.(bidsField).TermURL) eInfoDesc.(bidsField).TermURL = eventBIDS.(bidsField).TermURL; end end end - if numel(EEG) == 1 - command = '[EEG, eInfoDesc, eInfo] = pop_eventinfo(EEG);'; - else - command = '[EEG, eInfoDesc, eInfo] = pop_eventinfo(EEG);'; - end + % add info to STUDY if exists + if hasSTUDY + STUDY.BIDS.eInfoDesc = eInfoDesc; + STUDY.BIDS.eInfo = eInfo; + end % add info to EEG structs for e=1:numel(EEG) EEG(e).BIDS.eInfoDesc = eInfoDesc; @@ -360,9 +396,7 @@ function createLevelUI(~,~,table,field) removeLevelUI(); matchedRow = strcmp(table.Source.Data(:, strcmp(table.Source.ColumnName, 'BIDS Field')), field); levelCellText = table.Source.Data{matchedRow, strcmp(table.Source.ColumnName, 'Levels')}; % text @ (field, Levels) cell. if 'n/a' then no action, 'Click to..' then conditional action, ',...' then get levels - if strcmp(field, 'HED') - uicontrol(f, 'Style', 'text', 'String', 'Levels editing not applied for HED. Use ''pop_tageeg(EEG)'' of HEDTools plug-in to edit event HED tags', 'Units', 'normalized', 'Position', [0.01 0.45 1 0.05],'ForegroundColor', fg,'BackgroundColor', bg, 'Tag', 'levelEditMsg'); - elseif strcmp(field, 'onset') || strcmp(field, 'sample') || strcmp(field, 'duration') + if strcmp(field, 'onset') || strcmp(field, 'sample') || strcmp(field, 'duration') uicontrol(f, 'Style', 'text', 'String', 'Levels editing not applied for field with continuous values.', 'Units', 'normalized', 'Position', [0.01 0.45 1 0.05],'ForegroundColor', fg,'BackgroundColor', bg, 'Tag', 'levelEditMsg'); else % retrieve all unique values from EEG.event.(field). @@ -599,23 +633,7 @@ function removeLevelUI() event.value.Units = ''; event.value.Levels = []; event.value.TermURL = ''; - elseif strcmp(fields{idx}, 'HED') && any(strcmp(eventFields, 'usertags')) - if isfield(EEG(1).event, 'usertags') - event.HED.EEGField = 'usertags'; - else - event.HED.EEGField = ''; - end - event.HED.LongName = 'Hierarchical Event Descriptor'; - event.HED.Description = 'Tags describing the nature of the event'; - event.HED.Levels = []; - event.HED.Units = ''; - event.HED.TermURL = ''; elseif strcmp(fields{idx}, 'duration') - if isfield(EEG(1).event, 'duration') - event.HED.EEGField = 'duration'; - else - event.HED.EEGField = ''; - end event.duration.LongName = 'Event duration'; event.duration.Description = 'Duration of the event (measured from onset) in seconds. Must always be either zero or positive. A "duration" value of zero implies that the delta function or event is so short as to be effectively modeled as an impulse.'; event.duration.Units = 'second'; diff --git a/pop_exportbids.m b/pop_exportbids.m index b912090..9062a42 100644 --- a/pop_exportbids.m +++ b/pop_exportbids.m @@ -59,10 +59,11 @@ { 'Style', 'pushbutton', 'string', 'Edit participants' 'tag' 'participants' 'callback' cb_participants }, ... { 'Style', 'pushbutton', 'string', 'Edit event info' 'tag' 'events' 'callback' cb_events }, ... { 'Style', 'checkbox', 'string', 'Do not use participants ID and create anonymized participant ID instead' 'tag' 'newids' }, ... + { 'Style', 'checkbox', 'string', 'Use single top-level events.json' 'tag' 'eventsJson' }, ... }; relSize = 0.7; - geometry = { [1] [1] [1-relSize relSize*0.8 relSize*0.2] [1-relSize relSize] [1] [1] [1 1 1] [1] }; - geomvert = [1 0.2 1 1 1 3 1 1 ]; + geometry = { [1] [1] [1-relSize relSize*0.8 relSize*0.2] [1-relSize relSize] [1] [1] [1 1 1] [1] [1]}; + geomvert = [1 0.2 1 1 1 3 1 1 1]; userdata.EEG = EEG; userdata.STUDY = STUDY; [results,userdata,~,restag] = inputgui( 'geometry', geometry, 'geomvert', geomvert, 'uilist', uilist, 'helpcom', 'pophelp(''pop_exportbids'');', 'title', 'Export EEGLAB STUDY to BIDS -- pop_exportbids()', 'userdata', userdata ); @@ -80,10 +81,10 @@ % end % options - options = { 'targetdir' restag.outputfolder 'License' restag.license 'CHANGES' restag.changes 'createids' fastif(restag.newids, 'on', 'off') }; + options = { 'targetdir' restag.outputfolder 'License' restag.license 'CHANGES' restag.changes 'createids' fastif(restag.newids, 'on', 'off') 'singleEventsJson' fastif(restag.eventsJson, 'on', 'off')}; if ~isfield(EEG(1), 'BIDS') % none of the edit button was clicked - EEG = pop_eventinfo(EEG, 'default'); + EEG = pop_eventinfo(EEG, STUDY, 'default'); EEG = pop_participantinfo(EEG, STUDY, 'default'); EEG = pop_taskinfo(EEG, 'default'); end diff --git a/pop_importbids.m b/pop_importbids.m index 7dd24bb..7838dcd 100644 --- a/pop_importbids.m +++ b/pop_importbids.m @@ -199,6 +199,10 @@ if ~exist(subjectFolder{iFold},'dir') subjectFolder{ iFold} = fullfile(parentSubjectFolder, subFolders(iFold).name, 'meg'); subjectFolderOut{iFold} = fullfile(outputSubjectFolder, subFolders(iFold).name, 'meg'); + if ~exist(subjectFolder{iFold},'dir') + subjectFolder{ iFold} = fullfile(parentSubjectFolder, subFolders(iFold).name, 'ieeg'); + subjectFolderOut{iFold} = fullfile(outputSubjectFolder, subFolders(iFold).name, 'ieeg'); + end end end end @@ -290,9 +294,11 @@ % extract task name underScores = find(tmpFileName == '_'); if ~strcmpi(tmpFileName(underScores(end)+1:end), 'eeg') - if ~strcmpi(tmpFileName(underScores(end)+1:end), 'meg.fif') - if ~strcmpi(tmpFileName(underScores(end)+1:end), 'meg') - error('Data file name does not contain eeg or meg'); % theoretically impossible + if ~strcmpi(tmpFileName(underScores(end)+1:end), 'ieeg') + if ~strcmpi(tmpFileName(underScores(end)+1:end), 'meg.fif') + if ~strcmpi(tmpFileName(underScores(end)+1:end), 'meg') + error('Data file name does not contain eeg, ieeg, or meg'); % theoretically impossible + end end end end diff --git a/pop_participantinfo.m b/pop_participantinfo.m index 3a73a16..da2ffee 100644 --- a/pop_participantinfo.m +++ b/pop_participantinfo.m @@ -45,8 +45,8 @@ % CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) % ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF % THE POSSIBILITY OF SUCH DAMAGE. -function [EEG, command] = pop_participantinfo(EEG,STUDY, varargin) - command = '[EEG, command] = pop_participantinfo(EEG);'; +function [EEG, STUDY, command] = pop_participantinfo(EEG,STUDY, varargin) + command = '[EEG, [], command] = pop_participantinfo(EEG);'; %% check if there's already an opened window if ~isempty(findobj('Tag','pInfoTable')) @@ -55,9 +55,11 @@ end %% if STUDY is provided, check for consistency + hasSTUDY = false; if exist('STUDY','var') && ~isempty(STUDY) [STUDY, EEG] = pop_checkdatasetinfo(STUDY, EEG); - command = '[EEG, command] = pop_participantinfo(EEG, STUDY);'; + command = '[EEG, STUDY, command] = pop_participantinfo(EEG, STUDY);'; + hasSTUDY = true; end %% default settings @@ -79,13 +81,13 @@ % ------------------------- if isfield(EEG(1), 'subject') && ~isempty(EEG(1).subject) allSubjects = { EEG.subject }; - elseif isfield(STUDY,'datasetinfo') && isfield(STUDY.datasetinfo(1), 'subject') && ~isempty(STUDY.datasetinfo(1).subject) + elseif hasSTUDY && isfield(STUDY,'datasetinfo') && isfield(STUDY.datasetinfo(1), 'subject') && ~isempty(STUDY.datasetinfo(1).subject) allSubjects = { STUDY.datasetinfo.subject }; else if numel(EEG) == 1 errordlg2('No subject ID found. Please fill in "Subject code" in the next window, then resume.'); EEG = pop_editset(EEG); - else + elseif hasSTUDY errordlg2('No subject info found in STUDY. Please add using "Study > Edit study info", then resume.'); end return @@ -399,7 +401,7 @@ function okCB(~, ~) end if ~isempty(EEG(e).subject) rowIdx = strcmp(EEG(e).subject, pTable.Data(:, strcmp('participant_id', pTable.ColumnName))); - elseif ~isempty(STUDY.datasetinfo(1).subject) % assuming order of STUDY.datasetinfo matches with order of EEG in the EEG array + elseif hasSTUDY && ~isempty(STUDY.datasetinfo(1).subject) % assuming order of STUDY.datasetinfo matches with order of EEG in the EEG array rowIdx = strcmp(STUDY.datasetinfo(e).subject, pTable.Data(:, strcmp('participant_id', pTable.ColumnName))); end if isempty(pTable.Data{rowIdx,strcmp('HeadCircumference',pTable.ColumnName)}) diff --git a/pop_taskinfo.m b/pop_taskinfo.m index 36669a2..77c4363 100644 --- a/pop_taskinfo.m +++ b/pop_taskinfo.m @@ -55,7 +55,7 @@ uicontrol('Style', 'text', 'string', 'Task name (no space)','fontsize',fontSize,'BackgroundColor',bg,'ForegroundColor',fg, 'HorizontalAlignment','left','Units', 'normalized', 'Position', [leftMargin top tfWidth tfHeight]); uicontrol('Style', 'edit', 'string', '', 'tag', 'TaskName','fontsize',fontSize,'Units', 'normalized', 'Position', [efLeftMargin top efWidth tfHeight]); top = top - tfHeight - 0.01; - uicontrol('Style', 'checkbox', 'string', 'Use dataset "task condition" instead of task above', 'tag', 'Taskname2','BackgroundColor',bg,'fontsize',fontSize,'Units', 'normalized', 'Position', [efLeftMargin top efWidth tfHeight], 'callback', 'set(findobj(gcbf, ''tag'', ''TaskName''), ''string'', ''mixed'')'); + uicontrol('Style', 'text', 'string', 'For several tasks, use bids_export.m from the command line', 'tag', 'Taskname2','BackgroundColor',bg,'fontsize',fontSize,'Units', 'normalized', 'Position', [efLeftMargin top efWidth tfHeight]); top = top - tfHeight - 0.01; uicontrol('Style', 'text', 'string', 'README (short introduction to the experiment):','fontsize',fontSize,'BackgroundColor',bg,'ForegroundColor',fg, 'HorizontalAlignment','left','Units', 'normalized', 'Position', [leftMargin top fullWidth tfHeight]); top = top - tfHeight*2.2; diff --git a/rename_brainvision_files.m b/rename_brainvision_files.m index 1a089c1..b909184 100644 --- a/rename_brainvision_files.m +++ b/rename_brainvision_files.m @@ -42,7 +42,8 @@ function rename_brainvision_files(varargin) end % determine the filename without extension -[~, f, ~] = fileparts(newheaderfile); +[pathIn , baseNameIn, ~] = fileparts(oldheaderfile); +[pathOut, baseNameOut, ~] = fileparts(newheaderfile); %% do the renaming @@ -58,13 +59,13 @@ function rename_brainvision_files(varargin) [~, rem] = strtok(line, '='); oldmarkerfile = rem(2:end); [~, ~, x] = fileparts(oldmarkerfile); - newmarkerfile = [f switchcase(x)]; + newmarkerfile = [baseNameOut switchcase(x)]; % use relative path line = sprintf('MarkerFile=%s', newmarkerfile); elseif ~isempty(regexp(line, '^DataFile', 'once')) [~, rem] = strtok(line, '='); olddatafile = rem(2:end); [~, ~, x] = fileparts(olddatafile); - newdatafile = [f switchcase(x)]; + newdatafile = [baseNameOut switchcase('.eeg')]; % must be .eeg line = sprintf('DataFile=%s', newdatafile); end fprintf(fid2, '%s\r\n', line); @@ -72,16 +73,8 @@ function rename_brainvision_files(varargin) fclose(fid1); fclose(fid2); -% deal with the marker file -if exist(oldmarkerfile, 'file') == 0 - [~,~,ext] = fileparts(oldmarkerfile); - [~, ff, ~] = fileparts(oldheaderfile); % re-reading as sometimes weird names comes up - if exist([ff ext], 'file') - oldmarkerfile = [ff ext]; - else - error('the file %s does not exists', oldmarkerfile); - end -end +oldmarkerfile = fullfile(pathIn , oldmarkerfile); +newmarkerfile = fullfile(pathOut, newmarkerfile); assert(exist(newmarkerfile, 'file')==0, 'the file %s already exists', newmarkerfile); fid1 = fopen(oldmarkerfile, 'r'); fid2 = fopen(newmarkerfile, 'w'); @@ -92,13 +85,13 @@ function rename_brainvision_files(varargin) [~, rem] = strtok(line, '='); oldheaderfile = rem(2:end); [~, ~, x] = fileparts(oldheaderfile); - newheaderfile = [f switchcase(x)]; + newheaderfile = [baseNameOut switchcase(x)]; line = sprintf('HeaderFile=%s', newheaderfile); elseif ~isempty(regexp(line, '^DataFile', 'once')) [~, rem] = strtok(line, '='); olddatafile = rem(2:end); [~, ~, x] = fileparts(olddatafile); - newdatafile = [f switchcase(x)]; + newdatafile = [baseNameOut switchcase('.eeg')]; line = sprintf('DataFile=%s', newdatafile); end fprintf(fid2, '%s\r\n', line); @@ -106,18 +99,12 @@ function rename_brainvision_files(varargin) fclose(fid1); fclose(fid2); +olddatafile = fullfile(pathIn , olddatafile); +newdatafile = fullfile(pathOut, newdatafile); + % deal with the data file -if exist(olddatafile, 'file') == 0 - [~,~,ext] = fileparts(olddatafile); - [~, ff, ~] = fileparts(oldheaderfile); % re-reading as sometimes weird names comes up - if exist([ff ext], 'file') - olddatafile = [ff ext]; - else - error('the file %s does not exists', oldmarkerfile); - end -end assert(exist(newdatafile, 'file')==0, 'the file %s already exists', newdatafile); -status = copyfile(olddatafile, newdatafile); +status = copyfile( olddatafile, newdatafile); if ~status error('failed to copy data from %s to %s', olddatafile, newdatafile); end