diff --git a/.travis.yml b/.travis.yml index 0cdf80af..22f36925 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,7 +36,7 @@ jobs: script: octave $OCTFLAGS --eval "results = runTests; assert(all(~[results.Failed]))" - stage: "BIDS validator" name: "Create and check dataset" - script: octave $OCTFLAGS --eval "testmanual_makeRawDataset" && bids-validator `pwd`/../output/rawdata/ --ignoreNiftiHeaders + script: cd manualTests && octave $OCTFLAGS --eval "test_makeRawDataset" && bids-validator `pwd`/output/rawdata/ --ignoreNiftiHeaders - stage: "Linter" name: "miss_hit" script: cd .. && mh_style `pwd` diff --git a/checkCFG.m b/checkCFG.m index 2dd50246..e7c4d1dd 100644 --- a/checkCFG.m +++ b/checkCFG.m @@ -1,110 +1,188 @@ -function [cfg, expParameters] = checkCFG(cfg, expParameters) +function cfg = checkCFG(cfg) % check that we have all the fields that we need in the experiment parameters + % reuses a lot of code from the BIDS starter kit checkCppBidsDependencies(); if nargin < 1 || isempty(cfg) cfg = struct(); end - if nargin < 2 || isempty(expParameters) - expParameters = struct(); - end - %% set the expParameters defaults + %% set the cfg defaults fieldsToSet.verbose = false; - fieldsToSet.outputDir = fullfile( ... + + fieldsToSet.fileName.task = ''; + fieldsToSet.fileName.zeroPadding = 3; + fieldsToSet.fileName.dateFormat = 'yyyymmddHHMM'; + + fieldsToSet.dir.output = fullfile( ... fileparts(mfilename('fullpath')), ... '..', ... 'output'); - fieldsToSet = mriDefaults(fieldsToSet); + fieldsToSet.subject.askGrpSess = [true true]; + fieldsToSet.subject.sessionNb = 1; % in case no session was provided + fieldsToSet.subject.subjectGrp = ''; % in case no group was provided - fieldsToSet.subjectGrp = ''; % in case no group was provided - fieldsToSet.sessionNb = 1; % in case no session was provided - fieldsToSet.askGrpSess = [true true]; + fieldsToSet.testingDevice = 'pc'; + + fieldsToSet.eyeTracker = struct(); - expParameters = setDefaultFields(expParameters, fieldsToSet); + fieldsToSet.eyeTracker.do = false; + + fieldsToSet = mriDefaults(fieldsToSet); %% BIDS - clear fieldsToSet; - fieldsToSet.bids = struct(); - expParameters = setDefaultFields(expParameters, fieldsToSet); - clear fieldsToSet; - fieldsToSet.MRI = struct(); - fieldsToSet.datasetDescription = struct(); - expParameters.bids = setDefaultFields(expParameters.bids, fieldsToSet); + fieldsToSet = datasetDescriptionDefaults(fieldsToSet); + fieldsToSet = mriJsonDefaults(fieldsToSet); + fieldsToSet = megJsonDefaults(fieldsToSet); - clear fieldsToSet; - fieldsToSet = datasetDescriptionDefaults(); + fieldsToSet = transferInfoToBids(fieldsToSet, cfg); - expParameters.bids.datasetDescription = ... - setDefaultFields(expParameters.bids.datasetDescription, fieldsToSet); + cfg = setDefaultFields(cfg, fieldsToSet); - clear fieldsToSet; - fieldsToSet = mriJsonDefaults(); - if isfield(expParameters, 'task') - fieldsToSet.TaskName = expParameters.task; - end +end - expParameters.bids.MRI = ... - setDefaultFields(expParameters.bids.MRI, fieldsToSet); +function fieldsToSet = mriDefaults(fieldsToSet) - % sort fields alphabetically - expParameters = orderfields(expParameters); + % for file naming and JSON + fieldsToSet.mri.contrastEnhancement = []; + fieldsToSet.mri.phaseEncodingDirection = []; + fieldsToSet.mri.reconstruction = []; + fieldsToSet.mri.echo = []; + fieldsToSet.mri.acquisition = []; + fieldsToSet.mri.repetitionTime = []; - %% set the cfg defaults + fieldsToSet.mri = orderfields(fieldsToSet.mri); - clear fieldsToSet; - fieldsToSet.verbose = false; - fieldsToSet.testingDevice = 'pc'; - fieldsToSet.eyeTracker = false; +end - cfg = setDefaultFields(cfg, fieldsToSet); +function fieldsToSet = datasetDescriptionDefaults(fieldsToSet) - % sort fields alphabetically - cfg = orderfields(cfg); + % REQUIRED name of the dataset + fieldsToSet.bids.datasetDescription.Name = ''; -end + % REQUIRED The version of the BIDS standard that was used + fieldsToSet.bids.datasetDescription.BIDSVersion = ''; -function fieldsToSet = mriDefaults(fieldsToSet) + % RECOMMENDED + % what license is this dataset distributed under? The + % use of license name abbreviations is suggested for specifying a license. + % A list of common licenses with suggested abbreviations can be found in appendix III. + fieldsToSet.bids.datasetDescription.License = ''; + + % RECOMMENDED List of individuals who contributed to the + % creation/curation of the dataset + fieldsToSet.bids.datasetDescription.Authors = {''}; + + % RECOMMENDED who should be acknowledge in helping to collect the data + fieldsToSet.bids.datasetDescription.Acknowledgements = ''; - % for file naming - fieldsToSet.MRI.ce = []; - fieldsToSet.MRI.dir = []; % phase encoding direction of acquisition for fMRI - fieldsToSet.MRI.rec = []; % reconstruction of fMRI images - fieldsToSet.MRI.echo = []; % echo fMRI images - fieldsToSet.MRI.acq = []; % acquisition of fMRI images + % RECOMMENDED Instructions how researchers using this + % dataset should acknowledge the original authors. This field can also be used + % to define a publication that should be cited in publications that use the + % dataset. + fieldsToSet.bids.datasetDescription.HowToAcknowledge = ''; + + % RECOMMENDED sources of funding (grant numbers) + fieldsToSet.bids.datasetDescription.Funding = {''}; + + % RECOMMENDED a list of references to + % publication that contain information on the dataset, or links. + fieldsToSet.bids.datasetDescription.ReferencesAndLinks = {''}; + + % RECOMMENDED the Document Object Identifier of the dataset + % (not the corresponding paper). + fieldsToSet.bids.datasetDescription.DatasetDOI = ''; + + % sort fields alphabetically + fieldsToSet.bids.datasetDescription = orderfields(fieldsToSet.bids.datasetDescription); end -function fieldsToSet = datasetDescriptionDefaults() - % required - fieldsToSet.Name = ''; - fieldsToSet.BIDSVersion = ''; - % recommended - fieldsToSet.License = ''; - fieldsToSet.Authors = {''}; - fieldsToSet.Acknowledgements = ''; - fieldsToSet.HowToAcknowledge = ''; - fieldsToSet.Funding = {''}; - fieldsToSet.ReferencesAndLinks = {''}; - fieldsToSet.DatasetDOI = ''; +function fieldsToSet = mriJsonDefaults(fieldsToSet) + % for json for funcfional MRI data + + % REQUIRED The time in seconds between the beginning of an acquisition of + % one volume and the beginning of acquisition of the volume following it + % (TR). Please note that this definition includes time between scans + % (when no data has been acquired) in case of sparse acquisition schemes. + % This value needs to be consistent with the pixdim[4] field + % (after accounting for units stored in xyzt_units field) in the NIfTI header + fieldsToSet.bids.mri.RepetitionTime = []; + + % REQUIRED for sparse sequences that do not have the DelayTime field set. + % This parameter is required for sparse sequences. In addition without this + % parameter slice time correction will not be possible. + % + % In addition without this parameter slice time correction will not be possible. + % The time at which each slice was acquired within each volume (frame) of the acquisition. + % The time at which each slice was acquired during the acquisition. Slice + % timing is not slice order - it describes the time (sec) of each slice + % acquisition in relation to the beginning of volume acquisition. It is + % described using a list of times (in JSON format) referring to the acquisition + % time for each slice. The list goes through slices along the slice axis in the + % slice encoding dimension. + fieldsToSet.bids.mri.SliceTiming = []; + + % REQUIRED Name of the task (for resting state use the "rest" prefix). No two tasks + % should have the same name. Task label is derived from this field by + % removing all non alphanumeric ([a-zA-Z0-9]) characters. + fieldsToSet.bids.mri.TaskName = []; + + % RECOMMENDED Text of the instructions given to participants before the scan. + % This is especially important in context of resting state fMRI and + % distinguishing between eyes open and eyes closed paradigms. + fieldsToSet.bids.mri.Instructions = ''; + + % RECOMMENDED Longer description of the task. + fieldsToSet.bids.mri.TaskDescription = ''; + + % fieldsToSet.PhaseEncodingDirection = []; + % fieldsToSet.EffectiveEchoSpacing = []; + % fieldsToSet.EchoTime = []; + + fieldsToSet.bids.mri = orderfields(fieldsToSet.bids.mri); + end -function fieldsToSet = mriJsonDefaults() - - % for json for funcfional data - % required - fieldsToSet.RepetitionTime = []; - fieldsToSet.SliceTiming = []; - fieldsToSet.TaskName = []; - % fieldsToSet.PhaseEncodingDirection = []; - % fieldsToSet.EffectiveEchoSpacing = []; - % fieldsToSet.EchoTime = []; - % recommended - fieldsToSet.Instructions = []; - fieldsToSet.TaskDescription = []; +function fieldsToSet = megJsonDefaults(fieldsToSet) + % for json for MEG data + + % REQUIRED Name of the task (for resting state use the "rest" prefix). No two tasks + % should have the same name. Task label is derived from this field by + % removing all non alphanumeric ([a-zA-Z0-9]) characters. + fieldsToSet.bids.meg.TaskName = []; + + % REQUIRED Sampling frequency + fieldsToSet.bids.meg.SamplingFrequency = []; + + % REQUIRED Frequency (in Hz) of the power grid at the geographical location of + % the MEG instrument (i.e. 50 or 60): + fieldsToSet.bids.meg.PowerLineFrequency = []; + + % REQUIRED Position of the dewar during the MEG scan: "upright", "supine" or + % "degrees" of angle from vertical: for example on CTF systems, + % upright=15°, supine = 90°: + fieldsToSet.bids.meg.DewarPosition = []; + + % REQUIRED List of temporal and/or spatial software filters applied, or ideally + % key:value pairs of pre-applied software filters and their parameter + % values: e.g., {"SSS": {"frame": "head", "badlimit": 7}}, + % {"SpatialCompensation": {"GradientOrder": Order of the gradient + % compensation}}. Write "n/a" if no software filters applied. + fieldsToSet.bids.meg.SoftwareFilters = []; + + % REQUIRED Boolean ("true" or "false") value indicating whether anatomical + % landmark points (i.e. fiducials) are contained within this recording. + fieldsToSet.bids.meg.DigitizedLandmarks = []; + + % REQUIRED Boolean ("true" or "false") value indicating whether head points + % outlining the scalp/face surface are contained within this recording + fieldsToSet.bids.meg.DigitizedHeadPoints = []; + + fieldsToSet.bids.meg = orderfields(fieldsToSet.bids.meg); end diff --git a/checkCppBidsDependencies.m b/checkCppBidsDependencies.m index 97edb06e..de16d169 100644 --- a/checkCppBidsDependencies.m +++ b/checkCppBidsDependencies.m @@ -1,13 +1,13 @@ function checkCppBidsDependencies pth = fileparts(mfilename('fullpath')); - - checkSubmodule(fullfile(pth, 'lib', 'JSONio')) - checkSubmodule(fullfile(pth, 'lib', 'bids-matlab')) - + + checkSubmodule(fullfile(pth, 'lib', 'JSONio')); + checkSubmodule(fullfile(pth, 'lib', 'bids-matlab')); + addpath(fullfile(pth, 'lib', 'utils')); addpath(fullfile(pth, 'subfun')); - + printCreditsCppBids(); end diff --git a/convertSourceToRaw.m b/convertSourceToRaw.m index 10747eef..433d4b5c 100644 --- a/convertSourceToRaw.m +++ b/convertSourceToRaw.m @@ -1,11 +1,15 @@ -function convertSourceToRaw(expParameters) +function convertSourceToRaw(cfg) - sourceDir = fullfile(expParameters.outputDir, 'source'); - rawDir = fullfile(expParameters.outputDir, 'rawdata'); + sourceDir = fullfile(cfg.dir.output, 'source'); + rawDir = fullfile(cfg.dir.output, 'rawdata'); % add dummy readme and change file - copyfile(fullfile('..', 'dummyData', 'README'), sourceDir); - copyfile(fullfile('..', 'dummyData', 'CHANGES'), sourceDir); + copyfile(fullfile( ... + fileparts(mfilename('fullpath')), 'dummyData', 'README'), ... + sourceDir); + copyfile(fullfile( ... + fileparts(mfilename('fullpath')), 'dummyData', 'CHANGES'), ... + sourceDir); copyfile(sourceDir, rawDir); diff --git a/createBoldJson.m b/createBoldJson.m index fcb14059..b6325fa0 100644 --- a/createBoldJson.m +++ b/createBoldJson.m @@ -1,16 +1,16 @@ -function createBoldJson(expParameters) +function createBoldJson(cfg) opts.Indent = ' '; - fileName = strrep(expParameters.fileName.events, '_events', '_bold'); + fileName = strrep(cfg.fileName.events, '_events', '_bold'); fileName = strrep(fileName, '.tsv', '.json'); fileName = fullfile( ... - expParameters.subjectOutputDir, ... - expParameters.modality, ... + cfg.dir.outputSubject, ... + cfg.fileName.modality, ... fileName); - jsonContent = expParameters.bids.MRI; + jsonContent = cfg.bids.mri; bids.util.jsonencode(fileName, jsonContent, opts); diff --git a/createDataDictionary.m b/createDataDictionary.m index 828e3918..d9b6d225 100644 --- a/createDataDictionary.m +++ b/createDataDictionary.m @@ -1,12 +1,12 @@ -function createDataDictionary(expParameters, logFile) +function createDataDictionary(cfg, logFile) opts.Indent = ' '; - fileName = strrep(expParameters.fileName.events, '.tsv', '.json'); + fileName = strrep(cfg.fileName.events, '.tsv', '.json'); fileName = fullfile( ... - expParameters.subjectOutputDir, ... - expParameters.modality, ... + cfg.dir.outputSubject, ... + cfg.fileName.modality, ... fileName); jsonContent = struct( ... diff --git a/createDatasetDescription.m b/createDatasetDescription.m index 4e1cbb8f..0ead3aee 100644 --- a/createDatasetDescription.m +++ b/createDatasetDescription.m @@ -1,12 +1,12 @@ -function createDatasetDescription(expParameters) +function createDatasetDescription(cfg) opts.Indent = ' '; fileName = fullfile( ... - expParameters.outputDir, 'source', ... + cfg.dir.output, 'source', ... 'dataset_description.json'); - jsonContent = expParameters.bids.datasetDescription; + jsonContent = cfg.bids.datasetDescription; bids.util.jsonencode(fileName, jsonContent, opts); diff --git a/createFilename.m b/createFilename.m index 40834d4f..08d4391f 100644 --- a/createFilename.m +++ b/createFilename.m @@ -1,9 +1,9 @@ -function [cfg, expParameters] = createFilename(cfg, expParameters) - % create the BIDS compliant directories and filenames for the behavioral output +function cfg = createFilename(cfg) + % create the BIDS compliant directories and fileNames for the behavioral output % for this subject / session / run using the information from cfg and expParameters. - % Will also create the right filename for the eyetracking data file. + % Will also create the right fileName for the eyetracking data file. % - % For the moment the date of acquisition is appended to the filename + % For the moment the date of acquisition is appended to the fileName % % can work for behavioral experiment if cfg.device is set to 'PC' % can work for fMRI experiment if cfg.device is set to 'scanner' @@ -12,35 +12,32 @@ % % See test_createFilename in the test folder for more details on how to use it. - zeroPadding = 3; - pattern = ['%0' num2str(zeroPadding) '.0f']; - expParameters.pattern = pattern; + cfg = checkCFG(cfg); - dateFormat = 'yyyymmddHHMM'; - expParameters.date = datestr(now, dateFormat); + cfg.fileName.pattern = ['%0' num2str(cfg.fileName.zeroPadding) '.0f']; + cfg.fileName.date = datestr(now, cfg.fileName.dateFormat); - [cfg, expParameters] = checkCFG(cfg, expParameters); - - if ~isfield(expParameters, 'task') - error('createFilename: missing a task name. i.e expParameters.task'); + if ~isfield(cfg, 'task') + error('createFilename: missing a task name. i.e cfg.task.name'); end - expParameters = getModality(cfg, expParameters); + cfg = getModality(cfg); + + cfg = createDirectories(cfg); - expParameters = createDirectories(cfg, expParameters); + cfg = setSuffixes(cfg); - expParameters = setSuffixes(expParameters); + cfg = setFilenames(cfg); - expParameters = setFilenames(cfg, expParameters); + talkToMe(cfg); - talkToMe(cfg, expParameters); - cfg = orderfields(cfg); - expParameters = orderfields(expParameters); + cfg.fileName = orderfields(cfg.fileName); + cfg.dir = orderfields(cfg.dir); end -function expParameters = getModality(cfg, expParameters) +function cfg = getModality(cfg) switch lower(cfg.testingDevice) case 'pc' @@ -57,17 +54,17 @@ modality = 'beh'; end - expParameters.modality = modality; + cfg.fileName.modality = modality; end -function [subjectGrp, subjectNb, sessionNb, modality, taskName] = extractInput(expParameters) +function [subjectGrp, subjectNb, sessionNb, modality, taskName] = extractInput(cfg) - subjectGrp = expParameters.subjectGrp; - subjectNb = expParameters.subjectNb; - sessionNb = expParameters.sessionNb; - modality = expParameters.modality; - taskName = expParameters.task; + subjectGrp = cfg.subject.subjectGrp; + subjectNb = cfg.subject.subjectNb; + sessionNb = cfg.subject.sessionNb; + modality = cfg.fileName.modality; + taskName = cfg.fileName.task; if isempty(sessionNb) sessionNb = 1; @@ -75,120 +72,124 @@ end -function expParameters = createDirectories(cfg, expParameters) +function cfg = createDirectories(cfg) - [subjectGrp, subjectNb, sessionNb, modality] = extractInput(expParameters); + [subjectGrp, subjectNb, sessionNb, modality] = extractInput(cfg); - pattern = expParameters.pattern; + pattern = cfg.fileName.pattern; % output dir - expParameters.subjectOutputDir = fullfile ( ... - expParameters.outputDir, ... + cfg.dir.outputSubject = fullfile ( ... + cfg.dir.output, ... 'source', ... ['sub-' subjectGrp, sprintf(pattern, subjectNb)], ... ['ses-', sprintf(pattern, sessionNb)]); - [~, ~, ~] = mkdir(expParameters.outputDir); - [~, ~, ~] = mkdir(expParameters.subjectOutputDir); - [~, ~, ~] = mkdir(fullfile(expParameters.subjectOutputDir, modality)); + [~, ~, ~] = mkdir(cfg.dir.output); + [~, ~, ~] = mkdir(cfg.dir.outputSubject); + [~, ~, ~] = mkdir(fullfile(cfg.dir.outputSubject, modality)); - if cfg.eyeTracker - [~, ~, ~] = mkdir(fullfile(expParameters.subjectOutputDir, 'eyetracker')); + if cfg.eyeTracker.do + [~, ~, ~] = mkdir(fullfile(cfg.dir.outputSubject, 'eyetracker')); end end -function expParameters = setSuffixes(expParameters) +function cfg = setSuffixes(cfg) - expParameters.runSuffix = ['_run-' sprintf(expParameters.pattern, expParameters.runNb)]; + cfg.fileName.suffix.run = ['_run-' sprintf(cfg.fileName.pattern, cfg.subject.runNb)]; % set values for the suffixes for the different fields in the BIDS name fields2Check = { ... - 'ce', ... - 'dir', ... % For BIDS file naming: phase encoding direction of acquisition for fMRI - 'rec', ... % For BIDS file naming: reconstruction of fMRI images - 'echo', ... % For BIDS file naming: echo fMRI images - 'acq' % For BIDS file naming: acquisition of fMRI images + 'contrastEnhancement', ... + 'phaseEncodingDirection', ... + 'reconstruction', ... + 'echo', ... + 'acquisition' }; for iField = 1:numel(fields2Check) - if isempty (expParameters.MRI.(fields2Check{iField})) %#ok<*GFLD> - expParameters.MRI.([fields2Check{iField} 'Suffix']) = ''; %#ok<*SFLD> + if isempty (cfg.mri.(fields2Check{iField})) %#ok<*GFLD> + cfg.fileName.suffix.mri.(fields2Check{iField}) = ''; %#ok<*SFLD> else - expParameters.MRI.([fields2Check{iField} 'Suffix']) = ... - ['_' fields2Check{iField} '-' getfield(expParameters.MRI, fields2Check{iField})]; + cfg.fileName.suffix.mri.(fields2Check{iField}) = ... + ['_' fields2Check{iField} '-' getfield(cfg.mri, fields2Check{iField})]; end end + cfg.fileName.suffix = orderfields(cfg.fileName.suffix); + end -function expParameters = setFilenames(cfg, expParameters) +function cfg = setFilenames(cfg) - [subjectGrp, subjectNb, sessionNb, modality, taskName] = extractInput(expParameters); + [subjectGrp, subjectNb, sessionNb, modality, taskName] = extractInput(cfg); - runSuffix = expParameters.runSuffix; - pattern = expParameters.pattern; - acqSuffix = expParameters.MRI.acqSuffix ; - ceSuffix = expParameters.MRI.ceSuffix ; - dirSuffix = expParameters.MRI.dirSuffix ; - recSuffix = expParameters.MRI.recSuffix ; - echoSuffix = expParameters.MRI.echoSuffix; + pattern = cfg.fileName.pattern; - expParameters.datasetDescription.filename = fullfile ( ... - expParameters.outputDir, ... + runSuffix = cfg.fileName.suffix.run; + acqSuffix = cfg.fileName.suffix.mri.acquisition ; + ceSuffix = cfg.fileName.suffix.mri.contrastEnhancement ; + dirSuffix = cfg.fileName.suffix.mri.phaseEncodingDirection ; + recSuffix = cfg.fileName.suffix.mri.reconstruction ; + echoSuffix = cfg.fileName.suffix.mri.echo; + + thisDate = cfg.fileName.date; + + cfg.fileName.datasetDescription = fullfile ( ... + cfg.dir.output, ... 'dataset_description.json'); - % create base filename + % create base fileName fileNameBase = ... ['sub-', subjectGrp, sprintf(pattern, subjectNb), ... '_ses-', sprintf(pattern, sessionNb), ... '_task-', taskName]; - expParameters.fileName.base = fileNameBase; + cfg.fileName.base = fileNameBase; switch modality case 'func' - expParameters.fileName.events = ... + cfg.fileName.events = ... [fileNameBase, ... acqSuffix, ceSuffix, ... dirSuffix, recSuffix, ... runSuffix, echoSuffix, ... - '_events_date-' expParameters.date '.tsv']; + '_events_date-' thisDate '.tsv']; otherwise - expParameters.fileName.events = ... - [fileNameBase, runSuffix, '_events_date-' expParameters.date '.tsv']; + cfg.fileName.events = ... + [fileNameBase, runSuffix, '_events_date-' thisDate '.tsv']; end - expParameters.fileName.stim = strrep(expParameters.fileName.events, 'events', 'stim'); + cfg.fileName.stim = strrep(cfg.fileName.events, 'events', 'stim'); - if cfg.eyeTracker - expParameters.fileName.eyetracker = ... + if cfg.eyeTracker.do + cfg.fileName.eyetracker = ... [fileNameBase, acqSuffix, ... - runSuffix, '_eyetrack_date-' expParameters.date '.edf']; - + runSuffix, '_eyetrack_date-' thisDate '.edf']; end end -function talkToMe(cfg, expParameters) +function talkToMe(cfg) fprintf(1, '\nData will be saved in this directory:\n\t%s\n', ... - fullfile(expParameters.subjectOutputDir, expParameters.modality)); + fullfile(cfg.dir.outputSubject, cfg.fileName.modality)); fprintf(1, '\nData will be saved in this file:\n\t%s\n', ... - expParameters.fileName.events); + cfg.fileName.events); - if cfg.eyeTracker + if cfg.eyeTracker.do fprintf(1, '\nEyetracking data will be saved in this directory:\n\t%s\n', ... - fullfile(expParameters.subjectOutputDir, 'eyetracker')); + fullfile(cfg.dir.outputSubject, 'eyetracker')); fprintf(1, '\nEyetracking data will be saved in this file:\n\t%s\n', ... - expParameters.fileName.eyetracker); + cfg.fileName.eyetracker); end diff --git a/printCreditsCppBids.m b/printCreditsCppBids.m index 678130c8..bfb3f252 100644 --- a/printCreditsCppBids.m +++ b/printCreditsCppBids.m @@ -1,16 +1,18 @@ function printCreditsCppBids() - + version = '0.0.1'; - + contributors = { ... 'Rémi Gau', ... 'Marco Barilari', ... 'Ceren Battal'}; - + % DOI_URL = 'https://doi.org/10.5281/zenodo.3554331.'; - + repoURL = 'https://github.com/cpp-lln-lab/CPP_BIDS'; - + + fprintf('\n\n'); + disp('___________________________________________________'); disp('___________________________________________________'); disp(' '); @@ -19,25 +21,24 @@ function printCreditsCppBids() disp(' | (__| _/ _/ | _ \| || |) \__ \'); disp(' \___|_| |_| |___/___|___/|___/'); disp(' '); - - + splash = 'Thank you for using the CPP BIDS - version %s. '; fprintf(splash, version); fprintf('\n\n'); - + fprintf('Current list of contributors includes:\n'); for iCont = 1:numel(contributors) fprintf(' %s\n', contributors{iCont}); end fprintf('\b\n\n'); - + % fprintf('Please cite using the following DOI: \n %s\n\n', DOI_URL) - + fprintf('For bug report, suggestions or contributions see: \n %s\n\n', repoURL); - + disp('___________________________________________________'); disp('___________________________________________________'); - + fprintf('\n\n'); - + end diff --git a/saveEventsFile.m b/saveEventsFile.m index 9945bdf6..76891037 100644 --- a/saveEventsFile.m +++ b/saveEventsFile.m @@ -1,4 +1,4 @@ -function [logFile] = saveEventsFile(action, expParameters, logFile) +function [logFile] = saveEventsFile(action, cfg, logFile) % Function to save output files for events that will be BIDS compliant. % % INPUTS @@ -47,7 +47,7 @@ end if nargin < 2 - expParameters = struct(); + cfg = struct(); end if nargin < 3 || isempty(logFile) @@ -62,50 +62,22 @@ case 'open' - logFile.filename = expParameters.fileName.events; + logFile.filename = cfg.fileName.events; - logFile = initializeFile(expParameters, logFile); + logFile = initializeFile(cfg, logFile); case 'open_stim' - logFile.filename = expParameters.fileName.stim; + logFile.filename = cfg.fileName.stim; - logFile = initializeFile(expParameters, logFile); + logFile = initializeFile(cfg, logFile); case 'save' checklLogFile('checkID', logFile); checklLogFile('type&size', logFile); - % appends to the logfile all the data stored in the structure - % first with the standard BIDS data and then any extra things - for iEvent = 1:size(logFile, 1) - - logFile = checklLogFile('fields', logFile, iEvent); - - onset = logFile(iEvent).onset; - duration = logFile(iEvent).duration; - trial_type = logFile(iEvent).trial_type; - - if isnan(onset) || ischar(onset) || any(isempty({onset trial_type})) || ... - strcmp(trial_type, 'n/a') - - warning('\nSkipping saving this event.\n onset: %f \n trial_type: %s\n', ... - onset, ... - trial_type); - - else - - printData(logFile(1).fileID, onset); - printData(logFile(1).fileID, duration); - printData(logFile(1).fileID, trial_type); - - printExtraColumns(logFile, iEvent); - - fprintf(logFile(1).fileID, '\n'); - - end - end + logFile = saveToLogFile(logFile); case 'close' @@ -116,8 +88,8 @@ fprintf(1, '\nData were saved in this file:\n\n%s\n\n', ... fullfile( ... - expParameters.subjectOutputDir, ... - expParameters.modality, ... + cfg.dir.outputSubject, ... + cfg.fileName.modality, ... logFile.filename)); otherwise @@ -153,14 +125,10 @@ case 'fields' - if ~isfield(logFile, 'onset') || isempty(logFile(iEvent).onset) - logFile(iEvent).onset = nan; - end - if ~isfield(logFile, 'trial_type') || isempty(logFile(iEvent).trial_type) - logFile(iEvent).trial_type = nan; - end - if ~isfield(logFile, 'duration') || isempty(logFile(iEvent).duration) - logFile(iEvent).duration = nan; + for iFields = {'onset', 'trial_type', 'duration'} + if ~isfield(logFile, iFields) || isempty(logFile(iEvent).(iFields{1})) + logFile(iEvent).(iFields{1}) = nan; + end end logFile = checkExtracolumns(logFile, iEvent); @@ -169,18 +137,18 @@ end -function logFile = initializeFile(expParameters, logFile) +function logFile = initializeFile(cfg, logFile) logFile = initializeExtraColumns(logFile); - createDataDictionary(expParameters, logFile); + createDataDictionary(cfg, logFile); % Initialize txt logfiles and empty fields for the standard BIDS % event file logFile.fileID = fopen( ... fullfile( ... - expParameters.subjectOutputDir, ... - expParameters.modality, ... + cfg.dir.outputSubject, ... + cfg.fileName.modality, ... logFile.filename), ... 'w'); @@ -232,7 +200,9 @@ function printHeaderExtraColumns(logFile) data = logFile(iEvent).(namesExtraColumns{iExtraColumn}); end - data = checkInput(data, nbCol); + data = checkInput(data); + + data = nanPadding(data, nbCol); logFile(iEvent).(namesExtraColumns{iExtraColumn}) = data; @@ -248,15 +218,11 @@ function printHeaderExtraColumns(logFile) end -function data = checkInput(data, expectedLength) +function data = checkInput(data) % check the data to write % default will be 'n/a' for chars and NaN for numeric data % for numeric data that don't have the expected length, it will be padded with NaNs - if nargin < 2 - expectedLength = []; - end - if islogical(data) && data data = 'true'; elseif islogical(data) && ~data @@ -271,12 +237,54 @@ function printHeaderExtraColumns(logFile) data = nan; end +end + +function data = nanPadding(data, expectedLength) + + if nargin < 2 + expectedLength = []; + end + if ~isempty(expectedLength) && isnumeric(data) && max(size(data)) < expectedLength padding = expectedLength - max(size(data)); data(end + 1:end + padding) = nan(1, padding); elseif ~isempty(expectedLength) && isnumeric(data) && max(size(data)) > expectedLength - data = data(1:expectedLength); warning('A field for this event is longer than expected. Truncating the extra values.'); + data = data(1:expectedLength); + end + +end + +function logFile = saveToLogFile(logFile) + + % appends to the logfile all the data stored in the structure + % first with the standard BIDS data and then any extra things + for iEvent = 1:size(logFile, 1) + + logFile = checklLogFile('fields', logFile, iEvent); + + onset = logFile(iEvent).onset; + duration = logFile(iEvent).duration; + trial_type = logFile(iEvent).trial_type; + + if isnan(onset) || ischar(onset) || any(isempty({onset trial_type})) || ... + strcmp(trial_type, 'n/a') + + warning('\nSkipping saving this event.\n onset: %f \n trial_type: %s\n', ... + onset, ... + trial_type); + + else + + printData(logFile(1).fileID, onset); + printData(logFile(1).fileID, duration); + printData(logFile(1).fileID, trial_type); + + printExtraColumns(logFile, iEvent); + + fprintf(logFile(1).fileID, '\n'); + + end end end diff --git a/subfun/createTaskName.m b/subfun/createTaskName.m new file mode 100644 index 00000000..6b3fd62b --- /dev/null +++ b/subfun/createTaskName.m @@ -0,0 +1,17 @@ +function [taskName, taskNameValid] = createTaskName(taskName) + % [taskName, taskNameValid] = createTaskName(taskName) + % + % Name of the task (for resting state use the "rest" prefix). No two tasks + % should have the same name. Task label is derived from this field by + % removing all non alphanumeric ([a-zA-Z0-9]) characters. + + % camel case: upper case for first letter for all words but the first one + spaceIdx = regexp(taskName, '[a-zA-Z0-9]*', 'start'); + taskName(spaceIdx(2:end)) = upper(taskName(spaceIdx(2:end))); + + % remove invalid characters + [unvalidCharacters] = regexp(taskName, '[^a-zA-Z0-9]'); + taskNameValid = taskName; + taskNameValid(unvalidCharacters) = []; + +end diff --git a/subfun/setDefaultFields.m b/subfun/setDefaultFields.m index 75669017..e54f805f 100644 --- a/subfun/setDefaultFields.m +++ b/subfun/setDefaultFields.m @@ -1,5 +1,10 @@ function structure = setDefaultFields(structure, fieldsToSet) - % loop through the defaults fiels to set and update if they don't exist + % structure = setDefaultFields(structure, fieldsToSet) + % + % recursively loop through the fields of a structure and sets a value if they don't exist + % + + fieldsToSet = orderfields(fieldsToSet); names = fieldnames(fieldsToSet); @@ -7,13 +12,23 @@ thisField = fieldsToSet.(names{i}); - structure = setFieldToIfNotPresent( ... - structure, ... - names{i}, ... - thisField); + if isfield(structure, names{i}) && isstruct(structure.(names{i})) + + structure.(names{i}) = ... + setDefaultFields(structure.(names{i}), fieldsToSet.(names{i})); + + else + + structure = setFieldToIfNotPresent( ... + structure, ... + names{i}, ... + thisField); + end end + structure = orderfields(structure); + end function structure = setFieldToIfNotPresent(structure, fieldName, value) diff --git a/subfun/transferInfoToBids.m b/subfun/transferInfoToBids.m new file mode 100644 index 00000000..7f239cf7 --- /dev/null +++ b/subfun/transferInfoToBids.m @@ -0,0 +1,16 @@ +function fieldsToSet = transferInfoToBids(fieldsToSet, cfg) + % transfer any info that might have been provided by the user to the + % relevant field for its reuse for BIDS filenames or JSON later + + if isfield(cfg, 'task') && isfield(cfg.task, 'name') + [taskName, taskNameValid] = createTaskName(cfg.task.name); + fieldsToSet.fileName.task = taskNameValid; + fieldsToSet.bids.meg.TaskName = taskName; + fieldsToSet.bids.mri.TaskName = taskName; + end + + if isfield(cfg, 'mri') && isfield(cfg.mri, 'repetitionTime') + fieldsToSet.bids.mri.RepetitionTime = cfg.mri.repetitionTime; + end + +end diff --git a/tests/manualTests/miss_hit.cfg b/tests/manualTests/miss_hit.cfg new file mode 100644 index 00000000..074b1ea7 --- /dev/null +++ b/tests/manualTests/miss_hit.cfg @@ -0,0 +1,3 @@ +line_length: 100 +regex_function_name: "(test_[a-z]+)(([A-Z]){1}[A-Za-z]+)*" +suppress_rule: "copyright_notice" \ No newline at end of file diff --git a/tests/testmanual_makeRawDataset.m b/tests/manualTests/test_makeRawDataset.m similarity index 61% rename from tests/testmanual_makeRawDataset.m rename to tests/manualTests/test_makeRawDataset.m index ce176a41..e3ca4b3d 100644 --- a/tests/testmanual_makeRawDataset.m +++ b/tests/manualTests/test_makeRawDataset.m @@ -4,7 +4,7 @@ function test_makeRawDataset() clear; - outputDir = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); + outputDir = fullfile(fileparts(mfilename('fullpath')), 'output'); if isdir(outputDir) rmdir(outputDir, 's'); @@ -14,16 +14,16 @@ function test_makeRawDataset() %%% set up - expParameters.subjectNb = 1; - expParameters.runNb = 1; - expParameters.task = 'testtask'; - expParameters.outputDir = outputDir; + cfg.subject.subjectNb = 1; + cfg.subject.runNb = 1; + cfg.task.name = 'testtask'; + cfg.dir.output = outputDir; - expParameters.bids.datasetDescription.Name = 'dummy'; - expParameters.bids.datasetDescription.BIDSVersion = '1.0.0'; - expParameters.bids.datasetDescription.Authors = {'Jane Doe', 'John Doe'}; + cfg.bids.datasetDescription.Name = 'dummy'; + cfg.bids.datasetDescription.BIDSVersion = '1.0.0'; + cfg.bids.datasetDescription.Authors = {'Jane Doe', 'John Doe'}; - expParameters.bids.MRI.RepetitionTime = 1.56; + cfg.bids.mri.RepetitionTime = 1.56; cfg.testingDevice = 'mri'; @@ -33,13 +33,13 @@ function test_makeRawDataset() %%% do stuff - [cfg, expParameters] = createFilename(cfg, expParameters); %#ok<*ASGLU> + cfg = createFilename(cfg); % create the events file and header - logFile = saveEventsFile('open', expParameters, logFile); + logFile = saveEventsFile('open', cfg, logFile); - createBoldJson(expParameters); - createDatasetDescription(expParameters); + createBoldJson(cfg); + createDatasetDescription(cfg); % ROW 2: normal events : all info is there logFile(1, 1).onset = 2; @@ -68,19 +68,19 @@ function test_makeRawDataset() logFile(end, 1).duration = 3; logFile(end, 1).LHL24 = rand(1, 2); - saveEventsFile('save', expParameters, logFile); + saveEventsFile('save', cfg, logFile); % close the file - saveEventsFile('close', expParameters, logFile); + saveEventsFile('close', cfg, logFile); % add dummy functional data - funcDir = fullfile(expParameters.outputDir, 'source', 'sub-001', 'ses-001', 'func'); + funcDir = fullfile(cfg.dir.output, 'source', 'sub-001', 'ses-001', 'func'); boldFilename = 'sub-001_ses-001_task-testtask_run-001_bold.nii.gz'; copyfile( ... - fullfile('..', 'dummyData', 'dummyData.nii.gz'), ... + fullfile('..', '..', 'dummyData', 'dummyData.nii.gz'), ... fullfile(funcDir, boldFilename)); %% - convertSourceToRaw(expParameters); + convertSourceToRaw(cfg); end diff --git a/tests/manualTests/test_userInput.m b/tests/manualTests/test_userInput.m new file mode 100644 index 00000000..ce3edab0 --- /dev/null +++ b/tests/manualTests/test_userInput.m @@ -0,0 +1,41 @@ +clear; +clc; + +%% +fprintf('Debug\n'); +cfg.debug = true; + +cfg = userInputs(cfg); +disp(cfg.subject); + +%% +fprintf('No Group\n'); +cfg.debug = false; +cfg.subject.askGrpSess = 0; + +cfg = userInputs(cfg); +disp(cfg.subject); + +%% +fprintf('No Group or session\n'); +cfg.debug = false; +cfg.subject.askGrpSess = [0 0]; + +cfg = userInputs(cfg); +disp(cfg.subject); + +%% +fprintf('No session\n'); +cfg.debug = false; +cfg.subject.askGrpSess = [1 0]; + +cfg = userInputs(cfg); +disp(cfg.subject); + +%% +fprintf('Default\n'); +cfg.debug = false; +cfg.subject.askGrpSess = []; + +cfg = userInputs(cfg); +disp(cfg.subject); diff --git a/tests/test_checkCFG.m b/tests/test_checkCFG.m index 2db865f2..8c2155ed 100644 --- a/tests/test_checkCFG.m +++ b/tests/test_checkCFG.m @@ -1,12 +1,13 @@ function test_checkCFG() - expParameters.outputDir = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); - [cfg, expParameters] = checkCFG([], expParameters); + cfg.dir.output = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); + cfg = checkCFG(cfg); expectedStructure = returnExpectedStructure(); - expectedStructure.outputDir = expParameters.outputDir; + expectedStructure.dir.output = cfg.dir.output; + expectedStructure.testingDevice = 'pc'; - assert(isequal(expectedStructure, expParameters)); + testSubFields(expectedStructure, cfg); %% fprintf('\n--------------------------------------------------------------------'); @@ -15,34 +16,44 @@ function test_checkCFG() outputDir = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); - expParameters.subjectNb = 1; - expParameters.runNb = 1; - expParameters.task = 'testtask'; - expParameters.outputDir = outputDir; + cfg.subject.subjectNb = 1; + cfg.subject.runNb = 1; - expParameters.bids.datasetDescription.Name = 'dummy'; - expParameters.bids.datasetDescription.BIDSVersion = '1.0.0'; - expParameters.bids.datasetDescription.Authors = {'Jane Doe', 'John Doe'}; + cfg.task.name = 'test task'; - expParameters.bids.MRI.RepetitionTime = 1.56; + cfg.dir.output = outputDir; + + cfg.bids.datasetDescription.Name = 'dummy'; + cfg.bids.datasetDescription.BIDSVersion = '1.0.0'; + cfg.bids.datasetDescription.Authors = {'Jane Doe', 'John Doe'}; + + cfg.mri.repetitionTime = 1.56; cfg.testingDevice = 'mri'; - [~, expParameters] = checkCFG(cfg, expParameters); + cfg = checkCFG(cfg); %%% test % test data expectedStructure = returnExpectedStructure(); - expectedStructure.subjectNb = 1; - expectedStructure.runNb = 1; + expectedStructure.subject.subjectNb = 1; + expectedStructure.subject.runNb = 1; + + expectedStructure.dir.output = outputDir; + + expectedStructure.task.name = 'test task'; + + expectedStructure.testingDevice = 'mri'; + + expectedStructure.mri.repetitionTime = 1.56; - expectedStructure.outputDir = outputDir; + expectedStructure.fileName.task = 'testTask'; - expectedStructure.task = 'testtask'; + expectedStructure.bids.mri.RepetitionTime = 1.56; + expectedStructure.bids.mri.TaskName = 'test Task'; - expectedStructure.bids.MRI.RepetitionTime = 1.56; - expectedStructure.bids.MRI.TaskName = 'testtask'; + expectedStructure.bids.meg.TaskName = 'test Task'; expectedStructure.bids.datasetDescription.Name = 'dummy'; expectedStructure.bids.datasetDescription.BIDSVersion = '1.0.0'; @@ -50,7 +61,7 @@ function test_checkCFG() expectedStructure = orderfields(expectedStructure); - assert(isequal(expectedStructure, expParameters)); + testSubFields(expectedStructure, cfg); fprintf('\n'); @@ -58,26 +69,38 @@ function test_checkCFG() function expectedStructure = returnExpectedStructure() - expectedStructure.subjectGrp = ''; - expectedStructure.sessionNb = 1; + expectedStructure.subject.subjectGrp = ''; + expectedStructure.subject.sessionNb = 1; + expectedStructure.subject.askGrpSess = [true true]; expectedStructure.verbose = 0; - expectedStructure.askGrpSess = [true true]; - - expectedStructure.MRI.ce = []; - expectedStructure.MRI.dir = []; - expectedStructure.MRI.rec = []; - expectedStructure.MRI.echo = []; - expectedStructure.MRI.acq = []; - - expectedStructure.bids.MRI.RepetitionTime = []; - expectedStructure.bids.MRI.SliceTiming = ''; - expectedStructure.bids.MRI.TaskName = ''; - % expectedStructure.bids.MRI.PhaseEncodingDirection = ''; - % expectedStructure.bids.MRI.EffectiveEchoSpacing = ''; - % expectedStructure.bids.MRI.EchoTime = ''; - expectedStructure.bids.MRI.Instructions = ''; - expectedStructure.bids.MRI.TaskDescription = ''; + + expectedStructure.fileName.task = ''; + expectedStructure.fileName.zeroPadding = 3; + expectedStructure.fileName.dateFormat = 'yyyymmddHHMM'; + + expectedStructure.eyeTracker.do = false; + + expectedStructure.mri.contrastEnhancement = []; + expectedStructure.mri.phaseEncodingDirection = []; + expectedStructure.mri.reconstruction = []; + expectedStructure.mri.echo = []; + expectedStructure.mri.acquisition = []; + expectedStructure.mri.repetitionTime = []; + + expectedStructure.bids.mri.RepetitionTime = []; + expectedStructure.bids.mri.SliceTiming = ''; + expectedStructure.bids.mri.TaskName = ''; + expectedStructure.bids.mri.Instructions = ''; + expectedStructure.bids.mri.TaskDescription = ''; + + expectedStructure.bids.meg.TaskName = ''; + expectedStructure.bids.meg.SamplingFrequency = []; + expectedStructure.bids.meg.PowerLineFrequency = []; + expectedStructure.bids.meg.DewarPosition = []; + expectedStructure.bids.meg.SoftwareFilters = []; + expectedStructure.bids.meg.DigitizedLandmarks = []; + expectedStructure.bids.meg.DigitizedHeadPoints = []; expectedStructure.bids.datasetDescription.Name = ''; expectedStructure.bids.datasetDescription.BIDSVersion = ''; @@ -90,4 +113,35 @@ function test_checkCFG() expectedStructure.bids.datasetDescription.DatasetDOI = ''; expectedStructure = orderfields(expectedStructure); + +end + +function testSubFields(expectedStructure, cfg) + % check that that the structures match + % if it fails it check from which subfield the error comes from + + try + + assert(isequal(expectedStructure, cfg)); + + catch ME + + if isstruct(expectedStructure) + + names = fieldnames(expectedStructure); + + for i = 1:numel(names) + + disp(names{i}); + testSubFields(expectedStructure.(names{i}), cfg.(names{i})); + + end + + end + + expectedStructure; + cfg; + + rethrow(ME); + end end diff --git a/tests/test_createBoldJson.m b/tests/test_createBoldJson.m index b5bd9130..72348bfa 100644 --- a/tests/test_createBoldJson.m +++ b/tests/test_createBoldJson.m @@ -4,26 +4,26 @@ function test_createBoldJson() %%% set up part - expParameters.subjectNb = 1; - expParameters.runNb = 1; - expParameters.task = 'testtask'; - expParameters.outputDir = outputDir; + cfg.subject.subjectNb = 1; + cfg.subject.runNb = 1; + cfg.task.name = 'testtask'; + cfg.dir.output = outputDir; - cfg = struct(); + % cfg = struct(); cfg.testingDevice = 'mri'; - [cfg, expParameters] = createFilename(cfg, expParameters); %#ok<*ASGLU> + cfg = createFilename(cfg); - logFile = saveEventsFile('init', expParameters); %#ok<*NASGU> + logFile = saveEventsFile('init', cfg); %#ok<*NASGU> - createBoldJson(expParameters); + createBoldJson(cfg); %%% test part % test data funcDir = fullfile(outputDir, 'source', 'sub-001', 'ses-001', 'func'); eventFilename = ['sub-001_ses-001_task-testtask_run-001_bold_date-' ... - expParameters.date '.json']; + cfg.fileName.date '.json']; % check that the file has the right path and name assert(exist(fullfile(funcDir, eventFilename), 'file') == 2); diff --git a/tests/test_createDataDictionary.m b/tests/test_createDataDictionary.m index d5875706..c9bf1a81 100644 --- a/tests/test_createDataDictionary.m +++ b/tests/test_createDataDictionary.m @@ -6,28 +6,27 @@ function test_createDataDictionary() %%% set up part - expParameters.subjectNb = 1; - expParameters.runNb = 1; - expParameters.task = 'testtask'; - expParameters.outputDir = outputDir; + cfg.subject.subjectNb = 1; + cfg.subject.runNb = 1; + cfg.task.name = 'testtask'; + cfg.dir.output = outputDir; - cfg = struct(); cfg.testingDevice = 'mri'; - [cfg, expParameters] = createFilename(cfg, expParameters); %#ok + cfg = createFilename(cfg); logFile.extraColumns.Speed.length = 1; logFile.extraColumns.LHL24.length = 3; - logFile = saveEventsFile('init', expParameters, logFile); + logFile = saveEventsFile('init', cfg, logFile); - createDataDictionary(expParameters, logFile); + createDataDictionary(cfg, logFile); %%% test part % test data funcDir = fullfile(outputDir, 'source', 'sub-001', 'ses-001', 'func'); jsonFilename = ['sub-001_ses-001_task-testtask_run-001_events_date-' ... - expParameters.date '.json']; + cfg.fileName.date '.json']; % check that the file has the right path and name assert(exist(fullfile(funcDir, jsonFilename), 'file') == 2); diff --git a/tests/test_createDatasetDescription.m b/tests/test_createDatasetDescription.m index 07ee42f5..629fe432 100644 --- a/tests/test_createDatasetDescription.m +++ b/tests/test_createDatasetDescription.m @@ -4,18 +4,16 @@ function test_createDatasetDescription() %%% set up part - expParameters.outputDir = outputDir; + cfg.dir.output = outputDir; - expParameters.bids.datasetDescription.json.Name = 'dummy_dataset'; - expParameters.bids.datasetDescription.json.BIDSVersion = '1.0.0'; - expParameters.bids.datasetDescription.json.License = 'none'; - expParameters.bids.datasetDescription.json.Authors = {'Jane Doe'}; + cfg.bids.datasetDescription.json.Name = 'dummy_dataset'; + cfg.bids.datasetDescription.json.BIDSVersion = '1.0.0'; + cfg.bids.datasetDescription.json.License = 'none'; + cfg.bids.datasetDescription.json.Authors = {'Jane Doe'}; - cfg = struct(); + cfg = checkCFG(cfg); - [cfg, expParameters] = checkCFG(cfg, expParameters); %#ok<*ASGLU> - - createDatasetDescription(expParameters); + createDatasetDescription(cfg); %%% test part diff --git a/tests/test_createFilename.m b/tests/test_createFilename.m index 171ed583..ebce891f 100644 --- a/tests/test_createFilename.m +++ b/tests/test_createFilename.m @@ -7,25 +7,23 @@ function test_createFilename() %%% set up part - expParameters.subjectNb = 1; - expParameters.runNb = 1; - expParameters.task = 'testtask'; - expParameters.outputDir = outputDir; - - cfg = struct(); + cfg.subject.subjectNb = 1; + cfg.subject.runNb = 1; + cfg.task.name = 'test task'; + cfg.dir.output = outputDir; %%% run part - [cfg, expParameters] = createFilename(cfg, expParameters); + cfg = createFilename(cfg); %%% test part % test data behDir = fullfile(outputDir, 'source', 'sub-001', 'ses-001', 'beh'); eyetrackerDir = fullfile(outputDir, 'source', 'sub-001', 'ses-001', 'eyetracker'); - eventFilename = ['sub-001_ses-001_task-testtask_run-001_events_date-'... - expParameters.date '.tsv']; - stimFilename = ['sub-001_ses-001_task-testtask_run-001_stim_date-'... - expParameters.date '.tsv']; + eventFilename = ['sub-001_ses-001_task-testTask_run-001_events_date-'... + cfg.fileName.date '.tsv']; + stimFilename = ['sub-001_ses-001_task-testTask_run-001_stim_date-'... + cfg.fileName.date '.tsv']; % make sure the beh dir is created assert(exist(behDir, 'dir') == 7); @@ -34,10 +32,10 @@ function test_createFilename() assert(exist(eyetrackerDir, 'dir') == 0); % make sure the events filename is created - assert(strcmp(expParameters.fileName.events, eventFilename)); + assert(strcmp(cfg.fileName.events, eventFilename)); % make sure the stim filename is created - assert(strcmp(expParameters.fileName.stim, stimFilename)); + assert(strcmp(cfg.fileName.stim, stimFilename)); %% fMRI and eye tracker fprintf('\n--------------------------------------------------------------------'); @@ -48,28 +46,28 @@ function test_createFilename() %%% set up part - expParameters.subjectGrp = 'ctrl'; - expParameters.subjectNb = 2; - expParameters.sessionNb = 2; - expParameters.runNb = 2; - expParameters.task = 'testtask'; - expParameters.outputDir = outputDir; + cfg.subject.subjectGrp = 'ctrl'; + cfg.subject.subjectNb = 2; + cfg.subject.sessionNb = 2; + cfg.subject.runNb = 2; + cfg.task.name = 'test task'; + cfg.dir.output = outputDir; - cfg.eyeTracker = true; + cfg.eyeTracker.do = true; cfg.testingDevice = 'mri'; - [cfg, expParameters] = createFilename(cfg, expParameters); + cfg = createFilename(cfg); %%% test part % test data funcDir = fullfile(outputDir, 'source', 'sub-ctrl002', 'ses-002', 'func'); eyetrackerDir = fullfile(outputDir, 'source', 'sub-ctrl002', 'ses-002', 'eyetracker'); - baseFilename = 'sub-ctrl002_ses-002_task-testtask'; - eventFilename = ['sub-ctrl002_ses-002_task-testtask_run-002_events_date-' ... - expParameters.date '.tsv']; - eyetrackerFilename = ['sub-ctrl002_ses-002_task-testtask_run-002_eyetrack_date-' ... - expParameters.date '.edf']; + baseFilename = 'sub-ctrl002_ses-002_task-testTask'; + eventFilename = ['sub-ctrl002_ses-002_task-testTask_run-002_events_date-' ... + cfg.fileName.date '.tsv']; + eyetrackerFilename = ['sub-ctrl002_ses-002_task-testTask_run-002_eyetrack_date-' ... + cfg.fileName.date '.edf']; % make sure the func dir is created assert(exist(funcDir, 'dir') == 7); @@ -78,9 +76,9 @@ function test_createFilename() assert(exist(eyetrackerDir, 'dir') == 7); % make sure the right filenames are created - assert(strcmp(expParameters.fileName.base, baseFilename)); - assert(strcmp(expParameters.fileName.events, eventFilename)); - assert(strcmp(expParameters.fileName.eyetracker, eyetrackerFilename)); + assert(strcmp(cfg.fileName.base, baseFilename)); + assert(strcmp(cfg.fileName.events, eventFilename)); + assert(strcmp(cfg.fileName.eyetracker, eyetrackerFilename)); %% EEG fprintf('\n--------------------------------------------------------------------'); @@ -91,16 +89,16 @@ function test_createFilename() %%% set up part - expParameters.subjectGrp = 'blind'; - expParameters.subjectNb = 3; - expParameters.sessionNb = 1; - expParameters.runNb = 1; - expParameters.task = 'testtask'; - expParameters.outputDir = outputDir; + cfg.subject.subjectGrp = 'blind'; + cfg.subject.subjectNb = 3; + cfg.subject.sessionNb = 1; + cfg.subject.runNb = 1; + cfg.task.name = 'test task'; + cfg.dir.output = outputDir; cfg.testingDevice = 'eeg'; - [cfg, expParameters] = createFilename(cfg, expParameters); %#ok + cfg = createFilename(cfg); %%% test part diff --git a/tests/test_createTaskName.m b/tests/test_createTaskName.m new file mode 100644 index 00000000..6f30fe35 --- /dev/null +++ b/tests/test_createTaskName.m @@ -0,0 +1,25 @@ +function test_createTaskName() + % + + taskName = '&|@#-_(§!{})[]ù%£+/=:;.?,\<> visual task'; + + [taskName, taskNameValid] = createTaskName(taskName); + + [unvalidCharacters] = regexp(taskNameValid, '[^a-zA-Z0-9]'); + + assert(isempty(unvalidCharacters)); + + taskName = ' 09 visual task'; + + [taskName, taskNameValid] = createTaskName(taskName); + + [unvalidCharacters] = regexp(taskNameValid, '[^a-zA-Z0-9]'); + + assert(isempty(unvalidCharacters)); + + taskName = 'foo bar'; + [taskName, taskNameValid] = createTaskName(taskName); + assert(isequal(taskName, 'foo Bar')); + assert(isequal(taskNameValid, 'fooBar')); + +end diff --git a/tests/test_saveEventsFileInit.m b/tests/test_saveEventsFileInit.m index 765f96f3..ca0ecf02 100644 --- a/tests/test_saveEventsFileInit.m +++ b/tests/test_saveEventsFileInit.m @@ -21,11 +21,11 @@ function test_saveEventsFileInit() clear; %%% set up - [cfg, expParameters] = checkCFG(); %#ok + cfg = checkCFG(); logFile.extraColumns = {'Speed'}; %%% do stuff - [logFile] = saveEventsFile('init', expParameters, logFile); + [logFile] = saveEventsFile('init', cfg, logFile); %%% test section expectedStrcut(1).extraColumns.Speed.length = 1; @@ -42,12 +42,12 @@ function test_saveEventsFileInit() clear; %%% set up - [cfg, expParameters] = checkCFG(); %#ok + cfg = checkCFG(); logFile.extraColumns.Speed.length = 1; logFile.extraColumns.LHL24.length = 3; %%% do stuff - [logFile] = saveEventsFile('init', expParameters, logFile); + [logFile] = saveEventsFile('init', cfg, logFile); %%% test section expectedStrcut(1).extraColumns.Speed.length = 1; diff --git a/tests/test_saveEventsFileOpen.m b/tests/test_saveEventsFileOpen.m index 21d52954..032f5f47 100644 --- a/tests/test_saveEventsFileOpen.m +++ b/tests/test_saveEventsFileOpen.m @@ -7,37 +7,36 @@ function test_saveEventsFileOpen() %%% set up - expParameters.subjectNb = 1; - expParameters.runNb = 1; - expParameters.task = 'testtask'; - expParameters.outputDir = outputDir; + cfg.subject.subjectNb = 1; + cfg.subject.runNb = 1; + cfg.task.name = 'testtask'; + cfg.dir.output = outputDir; cfg.testingDevice = 'mri'; %%% do stuff - [cfg, expParameters] = createFilename(cfg, expParameters); + cfg = createFilename(cfg); % create the events file and header - logFile = saveEventsFile('open', expParameters); + logFile = saveEventsFile('open', cfg); % close the file - saveEventsFile('close', expParameters, logFile); + saveEventsFile('close', cfg, logFile); %%% test section % test data funcDir = fullfile(outputDir, 'source', 'sub-001', 'ses-001', 'func'); eventFilename = ['sub-001_ses-001_task-testtask_run-001_events_date-' ... - expParameters.date '.tsv']; - - % open the file - FID = fopen(fullfile(funcDir, eventFilename), 'r'); - C = textscan(FID, repmat('%s', 1, 3), 'Delimiter', '\t', 'EndOfLine', '\n'); + cfg.fileName.date '.tsv']; % check that the file has the right path and name assert(exist(fullfile(funcDir, eventFilename), 'file') == 2); + FID = fopen(fullfile(funcDir, eventFilename), 'r'); + C = textscan(FID, repmat('%s', 1, 3), 'Delimiter', '\t', 'EndOfLine', '\n'); + % check the extra columns of the header assert(isequal(C{1}{1}, 'onset')); assert(isequal(C{2}{1}, 'duration')); @@ -52,10 +51,10 @@ function test_saveEventsFileOpen() %%% set up - expParameters.subjectNb = 1; - expParameters.runNb = 1; - expParameters.task = 'testtask'; - expParameters.outputDir = outputDir; + cfg.subject.subjectNb = 1; + cfg.subject.runNb = 1; + cfg.task.name = 'testtask'; + cfg.dir.output = outputDir; cfg.testingDevice = 'mri'; @@ -64,23 +63,22 @@ function test_saveEventsFileOpen() %%% do stuff - [cfg, expParameters] = createFilename(cfg, expParameters); %#ok + cfg = createFilename(cfg); % create the events file and header - logFile = saveEventsFile('open', expParameters, logFile); + logFile = saveEventsFile('open', cfg, logFile); % close the file - saveEventsFile('close', expParameters, logFile); + saveEventsFile('close', cfg, logFile); %%% test section % open the file + funcDir = fullfile(cfg.dir.outputSubject, cfg.fileName.modality); + eventFilename = cfg.fileName.events; + nbExtraCol = 2; - FID = fopen(fullfile( ... - expParameters.subjectOutputDir, ... - expParameters.modality, ... - expParameters.fileName.events), ... - 'r'); + FID = fopen(fullfile(funcDir, eventFilename), 'r'); C = textscan(FID, repmat('%s', 1, nbExtraCol + 3), 'Delimiter', '\t', 'EndOfLine', '\n'); % check the extra columns of the header diff --git a/tests/test_saveEventsFileOpenMultiColumn.m b/tests/test_saveEventsFileOpenMultiColumn.m index d1415fbf..69b0dc5b 100644 --- a/tests/test_saveEventsFileOpenMultiColumn.m +++ b/tests/test_saveEventsFileOpenMultiColumn.m @@ -7,14 +7,14 @@ function test_saveEventsFileOpenMultiColumn() %%% set up - expParameters.subjectNb = 1; - expParameters.runNb = 1; - expParameters.task = 'testtask'; - expParameters.outputDir = outputDir; + cfg.subject.subjectNb = 1; + cfg.subject.runNb = 1; + cfg.task.name = 'testtask'; + cfg.dir.output = outputDir; cfg.testingDevice = 'mri'; - [cfg, expParameters] = createFilename(cfg, expParameters); %#ok + cfg = createFilename(cfg); % define the extra columns: here we specify how many columns we want for % each variable @@ -25,23 +25,23 @@ function test_saveEventsFileOpenMultiColumn() %%% do stuff % create the events file and header - logFile = saveEventsFile('open', expParameters, logFile); + logFile = saveEventsFile('open', cfg, logFile); % close the file - saveEventsFile('close', expParameters, logFile); + saveEventsFile('close', cfg, logFile); %%% test section % check the extra columns of the header and some of the content + funcDir = fullfile(cfg.dir.outputSubject, cfg.fileName.modality); + eventFilename = cfg.fileName.events; + nbExtraCol = ... logFile(1).extraColumns.Speed.length + ... logFile(1).extraColumns.LHL24.length + ... logFile(1).extraColumns.is_Fixation.length; - FID = fopen(fullfile( ... - expParameters.subjectOutputDir, ... - expParameters.modality, ... - expParameters.fileName.events), ... - 'r'); + + FID = fopen(fullfile(funcDir, eventFilename), 'r'); C = textscan(FID, repmat('%s', 1, nbExtraCol + 3), 'Delimiter', '\t', 'EndOfLine', '\n'); % check the extra columns of the header diff --git a/tests/test_saveEventsFileSave.m b/tests/test_saveEventsFileSave.m index ad3bfe84..9cc610c5 100644 --- a/tests/test_saveEventsFileSave.m +++ b/tests/test_saveEventsFileSave.m @@ -7,21 +7,21 @@ function test_saveEventsFileSave() %%% set up - expParameters.subjectNb = 1; - expParameters.runNb = 1; - expParameters.task = 'testtask'; - expParameters.outputDir = outputDir; + cfg.subject.subjectNb = 1; + cfg.subject.runNb = 1; + cfg.task.name = 'testtask'; + cfg.dir.output = outputDir; cfg.testingDevice = 'mri'; - [cfg, expParameters] = createFilename(cfg, expParameters); %#ok + cfg = createFilename(cfg); logFile.extraColumns.Speed.length = 1; logFile.extraColumns.LHL24.length = 12; logFile.extraColumns.is_Fixation.length = 1; % create the events file and header - logFile = saveEventsFile('open', expParameters, logFile); + logFile = saveEventsFile('open', cfg, logFile); %%% do stuff @@ -33,7 +33,7 @@ function test_saveEventsFileSave() logFile(end, 1).is_Fixation = true; logFile(end, 1).LHL24 = 1:12; - logFile = saveEventsFile('save', expParameters, logFile); + logFile = saveEventsFile('save', cfg, logFile); % ROW 3: missing info (speed, LHL24) logFile(1, 1).onset = 3; @@ -68,10 +68,10 @@ function test_saveEventsFileSave() % logFile(end, 1).duration = 3; % logFile(end, 1).LHL24 = rand(1, 15); - saveEventsFile('save', expParameters, logFile); + saveEventsFile('save', cfg, logFile); % close the file - saveEventsFile('close', expParameters, logFile); + saveEventsFile('close', cfg, logFile); %%% test section @@ -80,11 +80,10 @@ function test_saveEventsFileSave() logFile(1).extraColumns.Speed.length + ... logFile(1).extraColumns.LHL24.length + ... logFile(1).extraColumns.is_Fixation.length; - FID = fopen(fullfile( ... - expParameters.subjectOutputDir, ... - expParameters.modality, ... - expParameters.fileName.events), ... - 'r'); + + funcDir = fullfile(cfg.dir.outputSubject, cfg.fileName.modality); + eventFilename = cfg.fileName.events; + FID = fopen(fullfile(funcDir, eventFilename), 'r'); C = textscan(FID, repmat('%s', 1, nbExtraCol + 3), 'Delimiter', '\t', 'EndOfLine', '\n'); % event 1/ ROW 2: check that values are entered correctly diff --git a/tests/test_setDefaultFields.m b/tests/test_setDefaultFields.m new file mode 100644 index 00000000..b0aea0fc --- /dev/null +++ b/tests/test_setDefaultFields.m @@ -0,0 +1,29 @@ +function test_setDefaultFields() + + structure = struct(); + + fieldsToSet.field = 1; + + structure = setDefaultFields(structure, fieldsToSet); + + expectedStructure.field = 1; + + assert(isequal(expectedStructure, structure)); + + fprintf('\n--------------------------------------------------------------------'); + + clear; + + structure.field.subfield_1 = 3; + + fieldsToSet.field.subfield_1 = 1; + fieldsToSet.field.subfield_2 = 1; + + structure = setDefaultFields(structure, fieldsToSet); + + expectedStructure.field.subfield_1 = 3; + expectedStructure.field.subfield_2 = 1; + + assert(isequal(expectedStructure, structure)); + +end diff --git a/tests/test_transferInfoToBids.m b/tests/test_transferInfoToBids.m new file mode 100644 index 00000000..5af1816f --- /dev/null +++ b/tests/test_transferInfoToBids.m @@ -0,0 +1,35 @@ +function test_transferInfoToBids() + + %% + cfg = struct(); + fieldsToSet = struct(); + cfg = transferInfoToBids(fieldsToSet, cfg); + + expectedStruct = struct(); + + assert(isequal(expectedStruct, fieldsToSet)); + + %% + cfg.task.name = 'foo bar'; + + fieldsToSet = transferInfoToBids(fieldsToSet, cfg); + + expectedStruct.fileName.task = 'fooBar'; + expectedStruct.bids.meg.TaskName = 'foo Bar'; + expectedStruct.bids.mri.TaskName = 'foo Bar'; + + assert(isequal(expectedStruct, fieldsToSet)); + + %% + clear cfg fieldsToSet expectedStruct; + + cfg.mri.repetitionTime = 1.56; + + fieldsToSet = struct(); + fieldsToSet = transferInfoToBids(fieldsToSet, cfg); + + expectedStruct.bids.mri.RepetitionTime = 1.56; + + assert(isequal(expectedStruct, fieldsToSet)); + +end diff --git a/tests/userInput_test.m b/tests/userInput_test.m deleted file mode 100644 index eae757a7..00000000 --- a/tests/userInput_test.m +++ /dev/null @@ -1,34 +0,0 @@ -%% -cfg = struct('debug', true); -expParameters = struct(); - -[expParameters] = userInputs(cfg, expParameters); -disp(expParameters); - -%% -cfg = struct('debug', false); -expParameters = struct('askGrpSess', 0); - -[expParameters] = userInputs(cfg, expParameters); -disp(expParameters); - -%% -cfg = struct('debug', false); -expParameters = struct('askGrpSess', [0 0]); - -[expParameters] = userInputs(cfg, expParameters); -disp(expParameters); - -%% -cfg = struct('debug', false); -expParameters = struct('askGrpSess', [0 1]); - -[expParameters] = userInputs(cfg, expParameters); -disp(expParameters); - -%% -cfg = struct('debug', false); -expParameters = struct('askGrpSess', []); - -[expParameters] = userInputs(cfg, expParameters); -disp(expParameters); diff --git a/userInputs.m b/userInputs.m index a6433949..8b153471 100644 --- a/userInputs.m +++ b/userInputs.m @@ -1,4 +1,4 @@ -function [expParameters] = userInputs(cfg, expParameters) +function cfg = userInputs(cfg) % Get subject, run and session number and make sure they are % positive integer values % @@ -8,16 +8,20 @@ % group % - the second value set to false will skip asking for the session - if nargin < 1 || isempty(cfg.debug) - cfg.debug = false; + if nargin < 1 + cfg = []; end - if nargin < 2 - expParameters = []; + if isempty(cfg.debug) + cfg.debug = false; end askGrpSess = [true true]; - if isfield(expParameters, 'askGrpSess') && ~isempty(expParameters.askGrpSess) - askGrpSess = expParameters.askGrpSess; + if isfield(cfg, 'subject') && ... + isfield(cfg.subject, 'askGrpSess') && ... + ~isempty(cfg.subject.askGrpSess) + + askGrpSess = cfg.subject.askGrpSess; + end if numel(askGrpSess) < 2 askGrpSess(2) = 1; @@ -59,10 +63,10 @@ end - expParameters.subjectGrp = subjectGrp; - expParameters.subjectNb = subjectNb; - expParameters.sessionNb = sessionNb; - expParameters.runNb = runNb; + cfg.subject.subjectGrp = subjectGrp; + cfg.subject.subjectNb = subjectNb; + cfg.subject.sessionNb = sessionNb; + cfg.subject.runNb = runNb; end