From 90e1a1be50c8f810279aa903a64c1638cb0d2162 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Sat, 1 Aug 2020 08:47:00 +0200 Subject: [PATCH 01/11] make printCredits shut up if we ask him --- checkCFG.m | 7 +-- checkCppBidsDependencies.m | 4 +- printCreditsCppBids.m | 93 +++++++++++++++++++++----------------- 3 files changed, 57 insertions(+), 47 deletions(-) diff --git a/checkCFG.m b/checkCFG.m index 10337a5d..09ddfc9a 100644 --- a/checkCFG.m +++ b/checkCFG.m @@ -4,13 +4,13 @@ % 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 + + checkCppBidsDependencies(cfg); - %% set the cfg defaults + %% list the defaults to set fieldsToSet.verbose = false; @@ -43,6 +43,7 @@ fieldsToSet = transferInfoToBids(fieldsToSet, cfg); + %% Set defaults cfg = setDefaultFields(cfg, fieldsToSet); end diff --git a/checkCppBidsDependencies.m b/checkCppBidsDependencies.m index 898f3c6d..3afc5dfb 100644 --- a/checkCppBidsDependencies.m +++ b/checkCppBidsDependencies.m @@ -1,4 +1,4 @@ -function checkCppBidsDependencies() +function checkCppBidsDependencies(cfg) % checkCppBidsDependencies() % @@ -10,7 +10,7 @@ function checkCppBidsDependencies() addpath(fullfile(pth, 'lib', 'utils')); addpath(fullfile(pth, 'subfun')); - printCreditsCppBids(); + printCreditsCppBids(cfg); end diff --git a/printCreditsCppBids.m b/printCreditsCppBids.m index bfb3f252..189a064a 100644 --- a/printCreditsCppBids.m +++ b/printCreditsCppBids.m @@ -1,44 +1,53 @@ -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(' '); - disp(' ___ ___ ___ ___ ___ ___ ___ '); - disp(' / __| _ \ _ \ | _ )_ _| \/ __|'); - 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}); +function printCreditsCppBids(cfg) + + verbose = true; + if ~isempty(cfg) && isfield(cfg, 'verbose') && ~isempty(cfg.verbose) + verbose = cfg.verbose; 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'); - + + if verbose + + 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(' '); + disp(' ___ ___ ___ ___ ___ ___ ___ '); + disp(' / __| _ \ _ \ | _ )_ _| \/ __|'); + 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 + end From b6479f84cc4a98e8c074ecc23a5be899d7db255b Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Sat, 1 Aug 2020 08:47:34 +0200 Subject: [PATCH 02/11] rewrite test for checkCFG for mox unit --- tests/checkSubFields.m | 29 ++++++ tests/returnExpectedCfgStructure.m | 48 +++++++++ tests/test_checkCFG.m | 160 +++++++++-------------------- 3 files changed, 124 insertions(+), 113 deletions(-) create mode 100644 tests/checkSubFields.m create mode 100644 tests/returnExpectedCfgStructure.m diff --git a/tests/checkSubFields.m b/tests/checkSubFields.m new file mode 100644 index 00000000..764f4aff --- /dev/null +++ b/tests/checkSubFields.m @@ -0,0 +1,29 @@ +function checkSubFields(expectedStructure, cfg) + % check that that the structures match + % if it fails it check from which subfield the error comes from + + try + + assertEqual(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 + + disp(expectedStructure); + disp(cfg); + + rethrow(ME); + end +end diff --git a/tests/returnExpectedCfgStructure.m b/tests/returnExpectedCfgStructure.m new file mode 100644 index 00000000..41982a55 --- /dev/null +++ b/tests/returnExpectedCfgStructure.m @@ -0,0 +1,48 @@ +function expectedCfgStructure = returnExpectedCfgStructure() + + expectedCfgStructure.subject.subjectGrp = ''; + expectedCfgStructure.subject.sessionNb = 1; + expectedCfgStructure.subject.askGrpSess = [true true]; + + expectedCfgStructure.verbose = 0; + + expectedCfgStructure.fileName.task = ''; + expectedCfgStructure.fileName.zeroPadding = 3; + expectedCfgStructure.fileName.dateFormat = 'yyyymmddHHMM'; + + expectedCfgStructure.eyeTracker.do = false; + + expectedCfgStructure.mri.contrastEnhancement = []; + expectedCfgStructure.mri.phaseEncodingDirection = []; + expectedCfgStructure.mri.reconstruction = []; + expectedCfgStructure.mri.echo = []; + expectedCfgStructure.mri.acquisition = []; + expectedCfgStructure.mri.repetitionTime = []; + + expectedCfgStructure.bids.mri.RepetitionTime = []; + expectedCfgStructure.bids.mri.SliceTiming = ''; + expectedCfgStructure.bids.mri.TaskName = ''; + expectedCfgStructure.bids.mri.Instructions = ''; + expectedCfgStructure.bids.mri.TaskDescription = ''; + + expectedCfgStructure.bids.meg.TaskName = ''; + expectedCfgStructure.bids.meg.SamplingFrequency = []; + expectedCfgStructure.bids.meg.PowerLineFrequency = []; + expectedCfgStructure.bids.meg.DewarPosition = []; + expectedCfgStructure.bids.meg.SoftwareFilters = []; + expectedCfgStructure.bids.meg.DigitizedLandmarks = []; + expectedCfgStructure.bids.meg.DigitizedHeadPoints = []; + + expectedCfgStructure.bids.datasetDescription.Name = ''; + expectedCfgStructure.bids.datasetDescription.BIDSVersion = ''; + expectedCfgStructure.bids.datasetDescription.License = ''; + expectedCfgStructure.bids.datasetDescription.Authors = {''}; + expectedCfgStructure.bids.datasetDescription.Acknowledgements = ''; + expectedCfgStructure.bids.datasetDescription.HowToAcknowledge = ''; + expectedCfgStructure.bids.datasetDescription.Funding = {''}; + expectedCfgStructure.bids.datasetDescription.ReferencesAndLinks = {''}; + expectedCfgStructure.bids.datasetDescription.DatasetDOI = ''; + + expectedCfgStructure = orderfields(expectedCfgStructure); + +end \ No newline at end of file diff --git a/tests/test_checkCFG.m b/tests/test_checkCFG.m index 8c2155ed..3768bdd0 100644 --- a/tests/test_checkCFG.m +++ b/tests/test_checkCFG.m @@ -1,147 +1,81 @@ -function test_checkCFG() +function test_suite = test_checkCFG %#ok<*STOUT> + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions = localfunctions(); %#ok<*NASGU> + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; +end +function test_checkCfgDefault() + + %% set up cfg.dir.output = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); cfg = checkCFG(cfg); - - expectedStructure = returnExpectedStructure(); + + %% create test data + expectedStructure = returnExpectedCfgStructure(); expectedStructure.dir.output = cfg.dir.output; expectedStructure.testingDevice = 'pc'; - - testSubFields(expectedStructure, cfg); - - %% - fprintf('\n--------------------------------------------------------------------'); - - clear; - + + %% test + checkSubFields(expectedStructure, cfg); + +end + +function test_checkCfgBasic() + + %% set up outputDir = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); - + + cfg.verbose = false; + cfg.subject.subjectNb = 1; cfg.subject.runNb = 1; - + cfg.task.name = 'test task'; - + 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'; - + cfg = checkCFG(cfg); - - %%% test - - % test data - expectedStructure = returnExpectedStructure(); + + %% create test data + expectedStructure = returnExpectedCfgStructure(); 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.fileName.task = 'testTask'; - + expectedStructure.bids.mri.RepetitionTime = 1.56; expectedStructure.bids.mri.TaskName = 'test Task'; - + expectedStructure.bids.meg.TaskName = 'test Task'; - + expectedStructure.bids.datasetDescription.Name = 'dummy'; expectedStructure.bids.datasetDescription.BIDSVersion = '1.0.0'; expectedStructure.bids.datasetDescription.Authors = {'Jane Doe', 'John Doe'}; - + expectedStructure = orderfields(expectedStructure); - - testSubFields(expectedStructure, cfg); - - fprintf('\n'); - -end - -function expectedStructure = returnExpectedStructure() - - expectedStructure.subject.subjectGrp = ''; - expectedStructure.subject.sessionNb = 1; - expectedStructure.subject.askGrpSess = [true true]; - - expectedStructure.verbose = 0; - - 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 = ''; - expectedStructure.bids.datasetDescription.License = ''; - expectedStructure.bids.datasetDescription.Authors = {''}; - expectedStructure.bids.datasetDescription.Acknowledgements = ''; - expectedStructure.bids.datasetDescription.HowToAcknowledge = ''; - expectedStructure.bids.datasetDescription.Funding = {''}; - expectedStructure.bids.datasetDescription.ReferencesAndLinks = {''}; - expectedStructure.bids.datasetDescription.DatasetDOI = ''; - - expectedStructure = orderfields(expectedStructure); - + + %% test + checkSubFields(expectedStructure, cfg); + 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 From d815e9e7476b80b617f36517384a89c9d1117465 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Sat, 1 Aug 2020 09:18:05 +0200 Subject: [PATCH 03/11] add doc on tests and remove old testing function --- .gitignore | 15 +++++++++--- tests/README.md | 23 ++++++++++++++++++ tests/runTests.m | 63 ------------------------------------------------ 3 files changed, 35 insertions(+), 66 deletions(-) create mode 100644 tests/README.md delete mode 100644 tests/runTests.m diff --git a/.gitignore b/.gitignore index c1cac9ae..fef67417 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,23 @@ - +# exclude mac file *DS_Store +# exclude matlab autosaves and octave workspace info *.m~ *octave-workspace # exclude content of logfiles folders +output/** *.tsv *.mat -check_my_code_report.txt +# exclude temp files from tests and coverage +tests/coverage* +tests/test_code_report.txt test_code_report.txt -output/** +# exclude report from check_my_code +check_my_code_report.txt + + + + diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 00000000..0559aa7f --- /dev/null +++ b/tests/README.md @@ -0,0 +1,23 @@ +# how to run the tests + +- Install [MOxUnit for matlab and octave](https://github.com/MOxUnit/MOxUnit) to run the tests +- Install [MOcov for matlab and octave](https://github.com/MOcov/MOcov) to get the code coverage +- Make sure you are in the `tests` directory. +- Run `moxunit_runtests` or `moxunit_runtests -verbose` to run the tests. + +This should tell you which tests pass or fail. + +The following command would give more info and will give you HTML output in a `coverage_html` folder +showing you which lines of code is or is not checked by your test suite. + +``` matlab +success = moxunit_runtests(pwd, ... % the path where the tests are + '-verbose', ... + '-with_coverage', ... + '-cover', fullfile(pwd, '..'), ... % the path of the code whose coverage we want to estimate + '-cover_xml_file','coverage.xml', ... + '-cover_html_dir','coverage_html'); +``` + + + diff --git a/tests/runTests.m b/tests/runTests.m deleted file mode 100644 index 77ceecec..00000000 --- a/tests/runTests.m +++ /dev/null @@ -1,63 +0,0 @@ -function results = runTests(pth) - % Run tests - % List all the 'test_*.m' files located in the same directory as this - % function, run them and keep track of how many passed, failed or are - % incomplete. - % __________________________________________________________________________ - - % Copyright (C) 2019, Guillaume Flandin, Wellcome Centre for Human Neuroimaging - % Copyright (C) 2020--, CPP_BIDS developers - - % -Get the path of where this file is located - if ~nargin - pth = fileparts(mfilename('fullpath')); - end - - % -List all the 'test_*.m' files located in the same directory as this - % function - d = dir(pth); - d([d.isdir]) = []; - d(arrayfun(@(x) isempty(regexp(x.name, '^test_.*\.m$', 'once')), d)) = []; - - results = struct('Passed', {}, 'Failed', {}, 'Incomplete', {}, 'Duration', {}); - for i = 1:numel(d) - - results(i).Failed = false; - results(i).Passed = false; - results(i).Incomplete = false; - - tstart = tic; - - % -Run each test file and catch error message in case of failure - try - fprintf('\n\n--------------------------------------------------------------------\n'); - fprintf('%s', d(i).name(1:end - 2)); - fprintf('\n--------------------------------------------------------------------\n'); - feval(d(i).name(1:end - 2)); - results(i).Passed = true; - - catch err - results(i).Failed = true; - fprintf('\n%s', err.message); - end - - results(i).Duration = toc(tstart); - - fprintf('\n'); - - end - - if ~nargout - fprintf(['Totals (%d tests):\n\t%d Passed, %d Failed, %d Incomplete.\n' ... - '\t%f seconds testing time.\n\n'], numel(results), nnz([results.Passed]), ... - nnz([results.Failed]), nnz([results.Incomplete]), sum([results.Duration])); - for i = 1:numel(d) - status = 'Incomplete'; - if results(i).Passed - status = 'Passed'; - elseif results(i).Failed - status = 'Failed'; - end - fprintf('%s: %s\n', d(i).name(1:end - 2), status); - end - end From f620894eaad8c51984ccc272e44ab0c6a03831b1 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Sat, 1 Aug 2020 10:52:52 +0200 Subject: [PATCH 04/11] update all the tests to be used with mox unit --- tests/test_createBoldJson.m | 26 +++-- tests/test_createDataDictionary.m | 35 ++++--- tests/test_createDatasetDescription.m | 22 +++-- tests/test_createFilename.m | 82 ++++++++------- tests/test_createTaskName.m | 34 ++++++- tests/test_saveEventsFileInit.m | 87 +++++++++------- tests/test_saveEventsFileOpen.m | 110 +++++++++++---------- tests/test_saveEventsFileOpenMultiColumn.m | 58 ++++++----- tests/test_saveEventsFileSave.m | 55 ++++++----- tests/test_setDefaultFields.m | 23 ++++- 10 files changed, 326 insertions(+), 206 deletions(-) diff --git a/tests/test_createBoldJson.m b/tests/test_createBoldJson.m index 72348bfa..48629da1 100644 --- a/tests/test_createBoldJson.m +++ b/tests/test_createBoldJson.m @@ -1,15 +1,26 @@ -function test_createBoldJson() +function test_suite = test_createBoldJson %#ok<*STOUT> + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions = localfunctions(); %#ok<*NASGU> + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; +end + +function test_createBoldJsonBasic() outputDir = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); - %%% set up part + %% set up + cfg.verbose = false; + cfg.subject.subjectNb = 1; cfg.subject.runNb = 1; + cfg.task.name = 'testtask'; + cfg.dir.output = outputDir; - % cfg = struct(); cfg.testingDevice = 'mri'; cfg = createFilename(cfg); @@ -18,14 +29,13 @@ function test_createBoldJson() createBoldJson(cfg); - %%% test part - - % test data + %% data to test against funcDir = fullfile(outputDir, 'source', 'sub-001', 'ses-001', 'func'); + eventFilename = ['sub-001_ses-001_task-testtask_run-001_bold_date-' ... cfg.fileName.date '.json']; - % check that the file has the right path and name - assert(exist(fullfile(funcDir, eventFilename), 'file') == 2); + %% test + assertTrue(exist(fullfile(funcDir, eventFilename), 'file') == 2); end diff --git a/tests/test_createDataDictionary.m b/tests/test_createDataDictionary.m index c9bf1a81..5c26006a 100644 --- a/tests/test_createDataDictionary.m +++ b/tests/test_createDataDictionary.m @@ -1,11 +1,18 @@ -function test_createDataDictionary() +function test_suite = test_createDataDictionary %#ok<*STOUT> + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions = localfunctions(); %#ok<*NASGU> + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; +end - clear; +function test_createDataDictionaryBasic() outputDir = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); - %%% set up part + %% set up + cfg.verbose = false; cfg.subject.subjectNb = 1; cfg.subject.runNb = 1; cfg.task.name = 'testtask'; @@ -21,20 +28,24 @@ function test_createDataDictionary() createDataDictionary(cfg, logFile); - %%% test part - - % test data + %% check that the file has the right path and name + + % data to test against funcDir = fullfile(outputDir, 'source', 'sub-001', 'ses-001', 'func'); jsonFilename = ['sub-001_ses-001_task-testtask_run-001_events_date-' ... cfg.fileName.date '.json']; - % check that the file has the right path and name - assert(exist(fullfile(funcDir, jsonFilename), 'file') == 2); + % test + assertTrue(exist(fullfile(funcDir, jsonFilename), 'file') == 2); - % check content - expectedStruct = bids.util.jsondecode(fullfile(pwd, 'testData', 'eventsDataDictionary.json')); + %% check content actualStruct = bids.util.jsondecode(fullfile(funcDir, jsonFilename)); - - assert(isequal(expectedStruct, actualStruct)); + + % data to test against + expectedStruct = bids.util.jsondecode(... + fullfile(pwd, 'testData', 'eventsDataDictionary.json')); + + % test + assertTrue(isequal(expectedStruct, actualStruct)); end diff --git a/tests/test_createDatasetDescription.m b/tests/test_createDatasetDescription.m index 629fe432..1e2e0d89 100644 --- a/tests/test_createDatasetDescription.m +++ b/tests/test_createDatasetDescription.m @@ -1,10 +1,20 @@ -function test_createDatasetDescription() +function test_suite = test_createDatasetDescription %#ok<*STOUT> + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions = localfunctions(); %#ok<*NASGU> + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; +end + +function test_createDatasetDescriptionBasic() outputDir = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); - %%% set up part + %% set up cfg.dir.output = outputDir; + + cfg.verbose = false; cfg.bids.datasetDescription.json.Name = 'dummy_dataset'; cfg.bids.datasetDescription.json.BIDSVersion = '1.0.0'; @@ -15,13 +25,11 @@ function test_createDatasetDescription() createDatasetDescription(cfg); - %%% test part - - % test data + %% data to test against directory = fullfile(outputDir, 'source'); filename = 'dataset_description.json'; - % check that the file has the right path and name - assert(exist(fullfile(directory, filename), 'file') == 2); + %% test + assertTrue(exist(fullfile(directory, filename), 'file') == 2); end diff --git a/tests/test_createFilename.m b/tests/test_createFilename.m index ebce891f..803ecd04 100644 --- a/tests/test_createFilename.m +++ b/tests/test_createFilename.m @@ -1,51 +1,60 @@ -function test_createFilename() - % test for filename creation and their directories +function test_suite = test_createFilename %#ok<*STOUT> + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions = localfunctions(); %#ok<*NASGU> + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; +end - %% PC +function test_createFilenameBasic() outputDir = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); - %%% set up part + %% set up + cfg.verbose = false; cfg.subject.subjectNb = 1; cfg.subject.runNb = 1; cfg.task.name = 'test task'; cfg.dir.output = outputDir; - %%% run part cfg = createFilename(cfg); - %%% test part - - % test data + %% data to test against 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-'... cfg.fileName.date '.tsv']; + stimFilename = ['sub-001_ses-001_task-testTask_run-001_stim_date-'... cfg.fileName.date '.tsv']; + %% test + % make sure the beh dir is created - assert(exist(behDir, 'dir') == 7); + assertTrue(exist(behDir, 'dir') == 7); % make sure the eyetracker dir is not created - assert(exist(eyetrackerDir, 'dir') == 0); + assertTrue(exist(eyetrackerDir, 'dir') == 0); % make sure the events filename is created - assert(strcmp(cfg.fileName.events, eventFilename)); + assertEqual(cfg.fileName.events, eventFilename); % make sure the stim filename is created - assert(strcmp(cfg.fileName.stim, stimFilename)); + assertEqual(cfg.fileName.stim, stimFilename); - %% fMRI and eye tracker - fprintf('\n--------------------------------------------------------------------'); + +end - clear; +function test_createFilenameMriEyetracker() outputDir = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); - %%% set up part + %% set up + cfg.verbose = false; cfg.subject.subjectGrp = 'ctrl'; cfg.subject.subjectNb = 2; cfg.subject.sessionNb = 2; @@ -58,37 +67,41 @@ function test_createFilename() cfg = createFilename(cfg); - %%% test part - - % test data + %% data to test against + 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-' ... cfg.fileName.date '.tsv']; + eyetrackerFilename = ['sub-ctrl002_ses-002_task-testTask_run-002_eyetrack_date-' ... cfg.fileName.date '.edf']; + %% tests % make sure the func dir is created - assert(exist(funcDir, 'dir') == 7); + assertTrue(exist(funcDir, 'dir') == 7); % make sure the eyetracker dir is created - assert(exist(eyetrackerDir, 'dir') == 7); + assertTrue(exist(eyetrackerDir, 'dir') == 7); % make sure the right filenames are created - assert(strcmp(cfg.fileName.base, baseFilename)); - assert(strcmp(cfg.fileName.events, eventFilename)); - assert(strcmp(cfg.fileName.eyetracker, eyetrackerFilename)); - - %% EEG - fprintf('\n--------------------------------------------------------------------'); - - clear; + assertEqual(cfg.fileName.base, baseFilename); + assertEqual(cfg.fileName.events, eventFilename); + assertEqual(cfg.fileName.eyetracker, eyetrackerFilename); + +end + +function test_createFilenameEeg() outputDir = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); - %%% set up part + %% set up + cfg.verbose = false; cfg.subject.subjectGrp = 'blind'; cfg.subject.subjectNb = 3; cfg.subject.sessionNb = 1; @@ -100,10 +113,11 @@ function test_createFilename() cfg = createFilename(cfg); - %%% test part - - % test data + %% data to test against eegDir = fullfile(outputDir, 'source', 'sub-blind003', 'ses-001', 'eeg'); + %% test % make sure the func dir is created - assert(exist(eegDir, 'dir') == 7); + assertTrue(exist(eegDir, 'dir') == 7); + +end \ No newline at end of file diff --git a/tests/test_createTaskName.m b/tests/test_createTaskName.m index 6f30fe35..40f14ef3 100644 --- a/tests/test_createTaskName.m +++ b/tests/test_createTaskName.m @@ -1,21 +1,33 @@ -function test_createTaskName() - % +function test_suite = test_createTaskName %#ok<*STOUT> + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions = localfunctions(); %#ok<*NASGU> + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; +end - taskName = '&|@#-_(§!{})[]ù%£+/=:;.?,\<> visual task'; +function test_createTaskNameRemoveInvalidCharacters() + %% set up + + taskName = '&|@#-_(§!{})[]ù%£+/=:;.?,\<> visual task'; + [taskName, taskNameValid] = createTaskName(taskName); [unvalidCharacters] = regexp(taskNameValid, '[^a-zA-Z0-9]'); - assert(isempty(unvalidCharacters)); + %% test + assertTrue(isempty(unvalidCharacters)); + %% set up taskName = ' 09 visual task'; [taskName, taskNameValid] = createTaskName(taskName); [unvalidCharacters] = regexp(taskNameValid, '[^a-zA-Z0-9]'); - assert(isempty(unvalidCharacters)); + %% test + assertTrue(isempty(unvalidCharacters)); taskName = 'foo bar'; [taskName, taskNameValid] = createTaskName(taskName); @@ -23,3 +35,15 @@ function test_createTaskName() assert(isequal(taskNameValid, 'fooBar')); end + +function test_createTaskNameCamelCase() + + %% set up + taskName = 'foo bar'; + [taskName, taskNameValid] = createTaskName(taskName); + + %% test + assertEqual(taskName, 'foo Bar'); + assertEqual(taskNameValid, 'fooBar'); + +end diff --git a/tests/test_saveEventsFileInit.m b/tests/test_saveEventsFileInit.m index ca0ecf02..ed86be99 100644 --- a/tests/test_saveEventsFileInit.m +++ b/tests/test_saveEventsFileInit.m @@ -1,55 +1,69 @@ -function test_saveEventsFileInit() +function test_suite = test_saveEventsFileInit %#ok<*STOUT> + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions = localfunctions(); %#ok<*NASGU> + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; +end - %% +function test_saveEventsFileInitBasic() + + %% set up - %%% set up + cfg.verbose = false; + % make sure the dependencies are there - checkCFG(); - - %%% do stuff + checkCFG(cfg); + [logFile] = saveEventsFile('init'); - - %%% test section + + %% data to test against expectedStrcut(1).filename = ''; expectedStrcut(1).extraColumns = []; + + %% test + assertEqual(expectedStrcut, logFile); + +end - assert(isequal(expectedStrcut, logFile)); - - %% - fprintf('\n--------------------------------------------------------------------'); - - clear; +function test_saveEventsFileInitExtraColumns() + + %% set up - %%% set up - cfg = checkCFG(); + cfg.verbose = false; + + cfg = checkCFG(cfg); + logFile.extraColumns = {'Speed'}; - - %%% do stuff + [logFile] = saveEventsFile('init', cfg, logFile); - - %%% test section + + %% data to test against expectedStrcut(1).extraColumns.Speed.length = 1; expectedStrcut(1).extraColumns.Speed.bids.LongName = ''; expectedStrcut(1).extraColumns.Speed.bids.Description = ''; expectedStrcut(1).extraColumns.Speed.bids.Levels = ''; expectedStrcut(1).extraColumns.Speed.bids.TermURL = ''; + + %% test + assertEqual(expectedStrcut, logFile); + +end - assert(isequal(expectedStrcut, logFile)); - - %% - fprintf('\n--------------------------------------------------------------------'); - - clear; +function test_saveEventsFileInitExtraColumnsArray() + + %% set up - %%% set up - cfg = checkCFG(); + cfg.verbose = false; + + cfg = checkCFG(cfg); + logFile.extraColumns.Speed.length = 1; logFile.extraColumns.LHL24.length = 3; - - %%% do stuff + [logFile] = saveEventsFile('init', cfg, logFile); - - %%% test section + + %% data to test against expectedStrcut(1).extraColumns.Speed.length = 1; expectedStrcut(1).extraColumns.Speed.bids.LongName = ''; expectedStrcut(1).extraColumns.Speed.bids.Description = ''; @@ -60,9 +74,8 @@ function test_saveEventsFileInit() expectedStrcut(1).extraColumns.LHL24.bids.Description = ''; expectedStrcut(1).extraColumns.LHL24.bids.Levels = ''; expectedStrcut(1).extraColumns.LHL24.bids.TermURL = ''; - - assert(isequal(expectedStrcut, logFile)); - - fprintf('\n'); - + + %% test + assertEqual(expectedStrcut, logFile); + end diff --git a/tests/test_saveEventsFileOpen.m b/tests/test_saveEventsFileOpen.m index 032f5f47..e536a03d 100644 --- a/tests/test_saveEventsFileOpen.m +++ b/tests/test_saveEventsFileOpen.m @@ -1,91 +1,101 @@ -function test_saveEventsFileOpen() - - %% Initialize file - clear; +function test_suite = test_saveEventsFileOpen %#ok<*STOUT> + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions = localfunctions(); %#ok<*NASGU> + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; +end +function test_saveEventsFileOpenBasic() + outputDir = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); - - %%% set up - + + %% set up + + cfg.verbose = false; + cfg.subject.subjectNb = 1; cfg.subject.runNb = 1; + cfg.task.name = 'testtask'; + cfg.dir.output = outputDir; - + cfg.testingDevice = 'mri'; - - %%% do stuff - + cfg = createFilename(cfg); - + % create the events file and header logFile = saveEventsFile('open', cfg); - + % close the file saveEventsFile('close', cfg, logFile); - - %%% test section - - % test data + + %% data to test against funcDir = fullfile(outputDir, 'source', 'sub-001', 'ses-001', 'func'); eventFilename = ['sub-001_ses-001_task-testtask_run-001_events_date-' ... 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'); - + + %% test % check the extra columns of the header - assert(isequal(C{1}{1}, 'onset')); - assert(isequal(C{2}{1}, 'duration')); - assert(isequal(C{3}{1}, 'trial_type')); - - %% check header writing with extra columns - fprintf('\n\n--------------------------------------------------------------------'); - - clear; + assertEqual(C{1}{1}, 'onset'); + assertEqual(C{2}{1}, 'duration'); + assertEqual(C{3}{1}, 'trial_type'); + +end +function test_saveEventsFileOpenExtraColumns() + outputDir = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); - - %%% set up - + + %% set up + + cfg.verbose = false; + cfg.subject.subjectNb = 1; + cfg.subject.runNb = 1; + cfg.task.name = 'testtask'; + cfg.dir.output = outputDir; - + cfg.testingDevice = 'mri'; - - % define the extra columns: they will be added to the tsv files in the order the user input them - logFile.extraColumns = {'Speed', 'is_Fixation'}; - - %%% do stuff - + cfg = createFilename(cfg); + + % define the extra columns + % they will be added to the tsv files in the order the user input them + logFile.extraColumns = {'Speed', 'is_Fixation'}; % create the events file and header logFile = saveEventsFile('open', cfg, logFile); - + % close the file saveEventsFile('close', cfg, logFile); - - %%% test section - + + %% data to test against + % open the file funcDir = fullfile(cfg.dir.outputSubject, cfg.fileName.modality); eventFilename = cfg.fileName.events; - + nbExtraCol = 2; FID = fopen(fullfile(funcDir, eventFilename), 'r'); C = textscan(FID, repmat('%s', 1, nbExtraCol + 3), 'Delimiter', '\t', 'EndOfLine', '\n'); - + + %% test % check the extra columns of the header - assert(isequal(C{1}{1}, 'onset')); - assert(isequal(C{2}{1}, 'duration')); - assert(isequal(C{3}{1}, 'trial_type')); - assert(isequal(C{4}{1}, 'Speed')); - assert(isequal(C{5}{1}, 'is_Fixation')); - + assertEqual(C{1}{1}, 'onset'); + assertEqual(C{2}{1}, 'duration'); + assertEqual(C{3}{1}, 'trial_type'); + assertEqual(C{4}{1}, 'Speed'); + assertEqual(C{5}{1}, 'is_Fixation'); + end diff --git a/tests/test_saveEventsFileOpenMultiColumn.m b/tests/test_saveEventsFileOpenMultiColumn.m index 69b0dc5b..66c826b0 100644 --- a/tests/test_saveEventsFileOpenMultiColumn.m +++ b/tests/test_saveEventsFileOpenMultiColumn.m @@ -1,53 +1,59 @@ -function test_saveEventsFileOpenMultiColumn() - - %% check header writing with several columns for one variable - clear; +function test_suite = test_saveEventsFileOpenMultiColumn %#ok<*STOUT> + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions = localfunctions(); %#ok<*NASGU> + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; +end +function test_saveEventsFileOpenMultiColumnCheckHeader() + outputDir = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); - - %%% set up - + + %% set up + + cfg.verbose = false; + cfg.subject.subjectNb = 1; cfg.subject.runNb = 1; + cfg.task.name = 'testtask'; + cfg.dir.output = outputDir; - + cfg.testingDevice = 'mri'; - + cfg = createFilename(cfg); - + % define the extra columns: here we specify how many columns we want for % each variable logFile.extraColumns.Speed.length = 1; % will set 1 columns with name Speed logFile.extraColumns.LHL24.length = 12; % will set 12 columns with names LHL24-01, LHL24-02, ... logFile.extraColumns.is_Fixation = []; % will set 1 columns with name is_Fixation - - %%% do stuff - + % create the events file and header logFile = saveEventsFile('open', cfg, logFile); - + % close the file 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; - + + %% test 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 - assert(isequal(C{4}{1}, 'Speed')); - assert(isequal(C{5}{1}, 'LHL24_01')); - assert(isequal(C{16}{1}, 'LHL24_12')); - assert(isequal(C{17}{1}, 'is_Fixation')); - + assertEqual(C{4}{1}, 'Speed'); + assertEqual(C{5}{1}, 'LHL24_01'); + assertEqual(C{16}{1}, 'LHL24_12'); + assertEqual(C{17}{1}, 'is_Fixation'); + end diff --git a/tests/test_saveEventsFileSave.m b/tests/test_saveEventsFileSave.m index 9cc610c5..b49d5001 100644 --- a/tests/test_saveEventsFileSave.m +++ b/tests/test_saveEventsFileSave.m @@ -1,15 +1,24 @@ -function test_saveEventsFileSave() +function test_suite = test_saveEventsFileSave %#ok<*STOUT> + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions = localfunctions(); %#ok<*NASGU> + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; +end - %% write things in it - clear; +function test_saveEventsFileSaveBasic() outputDir = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); - %%% set up + %% set up + cfg.verbose = false; + cfg.subject.subjectNb = 1; cfg.subject.runNb = 1; + cfg.task.name = 'testtask'; + cfg.dir.output = outputDir; cfg.testingDevice = 'mri'; @@ -23,8 +32,6 @@ function test_saveEventsFileSave() % create the events file and header logFile = saveEventsFile('open', cfg, logFile); - %%% do stuff - % ROW 2: normal events : all info is there logFile(1, 1).onset = 2; logFile(end, 1).trial_type = 'motion_up'; @@ -73,7 +80,7 @@ function test_saveEventsFileSave() % close the file saveEventsFile('close', cfg, logFile); - %%% test section + %% test % check the extra columns of the header and some of the content nbExtraCol = ... @@ -82,34 +89,36 @@ function test_saveEventsFileSave() logFile(1).extraColumns.is_Fixation.length; 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 - assert(isequal(C{1}{2}, sprintf('%f', 2))); - assert(isequal(C{3}{2}, 'motion_up')); - assert(isequal(C{2}{2}, sprintf('%f', 3))); - assert(isequal(C{4}{2}, sprintf('%f', 2))); - assert(isequal(C{5}{2}, sprintf('%f', 1))); - assert(isequal(C{16}{2}, sprintf('%f', 12))); - assert(isequal(C{17}{2}, 'true')); + assertEqual(C{1}{2}, sprintf('%f', 2)); + assertEqual(C{3}{2}, 'motion_up'); + assertEqual(C{2}{2}, sprintf('%f', 3)); + assertEqual(C{4}{2}, sprintf('%f', 2)); + assertEqual(C{5}{2}, sprintf('%f', 1)); + assertEqual(C{16}{2}, sprintf('%f', 12)); + assertEqual(C{17}{2}, 'true'); % event 2 / ROW 3: missing info replaced by nans - assert(isequal(C{4}{3}, 'n/a')); - assert(isequal(C{5}{3}, 'n/a')); - assert(isequal(C{16}{3}, 'n/a')); - assert(isequal(C{17}{3}, 'false')); + assertEqual(C{4}{3}, 'n/a'); + assertEqual(C{5}{3}, 'n/a'); + assertEqual(C{16}{3}, 'n/a'); + assertEqual(C{17}{3}, 'false'); % event 3 / ROW 4: missing info (duration is missing and speed is empty) - assert(isequal(C{2}{4}, 'n/a')); - assert(isequal(C{4}{4}, 'n/a')); + assertEqual(C{2}{4}, 'n/a'); + assertEqual(C{4}{4}, 'n/a'); % event 4-5 / ROW 5-6: skip empty events - assert(~isequal(C{1}{5}, 'n/a')); + assertTrue(~isequal(C{1}{5}, 'n/a')); % check values entered properly - assert(isequal(C{15}{5}, 'n/a')); - assert(isequal(C{16}{5}, 'n/a')); + assertEqual(C{15}{5}, 'n/a'); + assertEqual(C{16}{5}, 'n/a'); end diff --git a/tests/test_setDefaultFields.m b/tests/test_setDefaultFields.m index b0aea0fc..c327d148 100644 --- a/tests/test_setDefaultFields.m +++ b/tests/test_setDefaultFields.m @@ -1,19 +1,32 @@ -function test_setDefaultFields() +function test_suite = test_setDefaultFields %#ok<*STOUT> + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions = localfunctions(); %#ok<*NASGU> + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; +end + +function test_setDefaultFieldsWrite() + %% set up structure = struct(); fieldsToSet.field = 1; structure = setDefaultFields(structure, fieldsToSet); + %% data to test against expectedStructure.field = 1; - assert(isequal(expectedStructure, structure)); + %% test + assertEqual(expectedStructure, structure); + +end - fprintf('\n--------------------------------------------------------------------'); - clear; +function test_setDefaultFieldsNoOverwrite() + % set up structure.field.subfield_1 = 3; fieldsToSet.field.subfield_1 = 1; @@ -21,9 +34,11 @@ function test_setDefaultFields() structure = setDefaultFields(structure, fieldsToSet); + % data to test against expectedStructure.field.subfield_1 = 3; expectedStructure.field.subfield_2 = 1; + % test assert(isequal(expectedStructure, structure)); end From 057b759c1502ed9fdc1e5a375755d464397a8915 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Sat, 1 Aug 2020 10:54:15 +0200 Subject: [PATCH 05/11] make some functions shut up if we ask them --- createFilename.m | 120 +++++++++++++------------- saveEventsFile.m | 214 +++++++++++++++++++++++++---------------------- 2 files changed, 175 insertions(+), 159 deletions(-) diff --git a/createFilename.m b/createFilename.m index 88469cc8..3aa01c1a 100644 --- a/createFilename.m +++ b/createFilename.m @@ -11,34 +11,34 @@ % % % See test_createFilename in the test folder for more details on how to use it. - + cfg = checkCFG(cfg); - + cfg.fileName.pattern = ['%0' num2str(cfg.fileName.zeroPadding) '.0f']; cfg.fileName.date = datestr(now, cfg.fileName.dateFormat); - + if ~isfield(cfg, 'task') error('createFilename: missing a task name. i.e cfg.task.name'); end - + cfg = getModality(cfg); - + cfg = createDirectories(cfg); - + cfg = setSuffixes(cfg); - + cfg = setFilenames(cfg); - + talkToMe(cfg); - + cfg = orderfields(cfg); cfg.fileName = orderfields(cfg.fileName); cfg.dir = orderfields(cfg.dir); - + end function cfg = getModality(cfg) - + switch lower(cfg.testingDevice) case 'pc' modality = 'beh'; @@ -53,52 +53,52 @@ otherwise modality = 'beh'; end - + cfg.fileName.modality = modality; - + end function [subjectGrp, subjectNb, sessionNb, modality, taskName] = extractInput(cfg) - + 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; end - + end function cfg = createDirectories(cfg) - + [subjectGrp, subjectNb, sessionNb, modality] = extractInput(cfg); - + pattern = cfg.fileName.pattern; - + % output dir cfg.dir.outputSubject = fullfile ( ... cfg.dir.output, ... 'source', ... ['sub-' subjectGrp, sprintf(pattern, subjectNb)], ... ['ses-', sprintf(pattern, sessionNb)]); - + [~, ~, ~] = mkdir(cfg.dir.output); [~, ~, ~] = mkdir(cfg.dir.outputSubject); [~, ~, ~] = mkdir(fullfile(cfg.dir.outputSubject, modality)); - + if cfg.eyeTracker.do [~, ~, ~] = mkdir(fullfile(cfg.dir.outputSubject, 'eyetracker')); end - + end function cfg = setSuffixes(cfg) - + 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 = { ... 'contrastEnhancement', ... @@ -107,7 +107,7 @@ 'echo', ... 'acquisition' }; - + for iField = 1:numel(fields2Check) if isempty (cfg.mri.(fields2Check{iField})) %#ok<*GFLD> cfg.fileName.suffix.mri.(fields2Check{iField}) = ''; %#ok<*SFLD> @@ -116,81 +116,85 @@ ['_' fields2Check{iField} '-' getfield(cfg.mri, fields2Check{iField})]; end end - + cfg.fileName.suffix = orderfields(cfg.fileName.suffix); - + end function cfg = setFilenames(cfg) - + [subjectGrp, subjectNb, sessionNb, modality, taskName] = extractInput(cfg); - + pattern = cfg.fileName.pattern; - + 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 fileNameBase = ... ['sub-', subjectGrp, sprintf(pattern, subjectNb), ... '_ses-', sprintf(pattern, sessionNb), ... '_task-', taskName]; cfg.fileName.base = fileNameBase; - + switch modality - + case 'func' - + cfg.fileName.events = ... [fileNameBase, ... acqSuffix, ceSuffix, ... dirSuffix, recSuffix, ... runSuffix, echoSuffix, ... '_events_date-' thisDate '.tsv']; - + otherwise - + cfg.fileName.events = ... [fileNameBase, runSuffix, '_events_date-' thisDate '.tsv']; - + end - + cfg.fileName.stim = strrep(cfg.fileName.events, 'events', 'stim'); - + if cfg.eyeTracker.do cfg.fileName.eyetracker = ... [fileNameBase, acqSuffix, ... runSuffix, '_eyetrack_date-' thisDate '.edf']; end - + end function talkToMe(cfg) - - fprintf(1, '\nData will be saved in this directory:\n\t%s\n', ... - fullfile(cfg.dir.outputSubject, cfg.fileName.modality)); - - fprintf(1, '\nData will be saved in this file:\n\t%s\n', ... - cfg.fileName.events); - - if cfg.eyeTracker.do - - fprintf(1, '\nEyetracking data will be saved in this directory:\n\t%s\n', ... - fullfile(cfg.dir.outputSubject, 'eyetracker')); - - fprintf(1, '\nEyetracking data will be saved in this file:\n\t%s\n', ... - cfg.fileName.eyetracker); - + + if cfg.verbose + + fprintf(1, '\nData will be saved in this directory:\n\t%s\n', ... + fullfile(cfg.dir.outputSubject, cfg.fileName.modality)); + + fprintf(1, '\nData will be saved in this file:\n\t%s\n', ... + cfg.fileName.events); + + if cfg.eyeTracker.do + + fprintf(1, '\nEyetracking data will be saved in this directory:\n\t%s\n', ... + fullfile(cfg.dir.outputSubject, 'eyetracker')); + + fprintf(1, '\nEyetracking data will be saved in this file:\n\t%s\n', ... + cfg.fileName.eyetracker); + + end + end - + end diff --git a/saveEventsFile.m b/saveEventsFile.m index 624b17dd..9ce5a01c 100644 --- a/saveEventsFile.m +++ b/saveEventsFile.m @@ -43,108 +43,104 @@ % to true then this will tell you where the file is located. % % See test_saveEventsFile in the test folder for more details on how to use it. - + if nargin < 1 error('Missing action input'); end - + if nargin < 2 cfg = struct(); end - + if nargin < 3 || isempty(logFile) logFile = checklLogFile('init'); end - + switch action - + case 'init' - + logFile = initializeExtraColumns(logFile); - + case 'open' - + logFile.filename = cfg.fileName.events; - + logFile = initializeFile(cfg, logFile); - + case 'open_stim' - + logFile.filename = cfg.fileName.stim; - + logFile = initializeFile(cfg, logFile); - + case 'save' - + checklLogFile('checkID', logFile); checklLogFile('type&size', logFile); - + logFile = saveToLogFile(logFile); - + case 'close' - + checklLogFile('checkID', logFile); - + % close txt log file fclose(logFile(1).fileID); - - fprintf(1, '\nData were saved in this file:\n\n%s\n\n', ... - fullfile( ... - cfg.dir.outputSubject, ... - cfg.fileName.modality, ... - logFile.filename)); - + + talkToMe(cfg, logFile); + otherwise - + errorSaveEventsFile('unknownActionType'); - + end - + logFile = resetLogFileVar(logFile); - + end function logFile = checklLogFile(action, logFile, iEvent) - + switch action - + case 'init' - + logFile = struct('filename', [], 'extraColumns', cell(1)); logFile(1).filename = ''; - + case 'checkID' - + if ~isfield(logFile(1), 'fileID') || isempty(logFile(1).fileID) errorSaveEventsFile('missingFileID'); end - + case 'type&size' - + if ~isstruct(logFile) || size(logFile, 2) > 1 errorSaveEventsFile('wrongLogSize'); end - + case 'fields' - + 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); - + end - + end function logFile = initializeFile(cfg, logFile) - + logFile = initializeExtraColumns(logFile); - + createDataDictionary(cfg, logFile); - + % Initialize txt logfiles and empty fields for the standard BIDS % event file logFile.fileID = fopen( ... @@ -153,36 +149,36 @@ cfg.fileName.modality, ... logFile.filename), ... 'w'); - + % print the basic BIDS columns fprintf(logFile.fileID, '%s\t%s\t%s', 'onset', 'duration', 'trial_type'); - + printHeaderExtraColumns(logFile); - + % next line so we start printing at the right place fprintf(logFile.fileID, '\n'); - + end function printHeaderExtraColumns(logFile) % print any extra column specified by the user - + [namesExtraColumns, logFile] = returnNamesExtraColumns(logFile); - + for iExtraColumn = 1:numel(namesExtraColumns) - + nbCol = returnNbColumns(logFile, namesExtraColumns{iExtraColumn}); - + for iCol = 1:nbCol - + headerName = returnHeaderName(namesExtraColumns{iExtraColumn}, nbCol, iCol); - + fprintf(logFile.fileID, '\t%s', headerName); - + end - + end - + end function logFile = checkExtracolumns(logFile, iEvent) @@ -190,24 +186,24 @@ function printHeaderExtraColumns(logFile) % if the field we are looking for does not exist or is empty in the % action logFile structure we will write a n/a % otherwise we write its content - + namesExtraColumns = returnNamesExtraColumns(logFile); - + for iExtraColumn = 1:numel(namesExtraColumns) - + nbCol = returnNbColumns(logFile, namesExtraColumns{iExtraColumn}); - + data = 'n/a'; if isfield(logFile, namesExtraColumns{iExtraColumn}) data = logFile(iEvent).(namesExtraColumns{iExtraColumn}); end - + data = checkInput(data); - + data = nanPadding(data, nbCol); - + logFile(iEvent).(namesExtraColumns{iExtraColumn}) = data; - + if any(isnan(data)) warning('Missing some %s data for this event.', namesExtraColumns{iExtraColumn}); disp(logFile(iEvent)); @@ -215,22 +211,22 @@ function printHeaderExtraColumns(logFile) warning('Missing %s data for this event.', namesExtraColumns{iExtraColumn}); disp(logFile(iEvent)); end - + end - + end 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 islogical(data) && data data = 'true'; elseif islogical(data) && ~data data = 'false'; end - + if ischar(data) && isempty(data) || strcmp(data, ' ') data = 'n/a'; elseif isempty(data) @@ -238,15 +234,15 @@ function printHeaderExtraColumns(logFile) % numeric valur has the right length and needs to be nan padded 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); @@ -254,56 +250,56 @@ function printHeaderExtraColumns(logFile) 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 function printExtraColumns(logFile, iEvent) % loops through the extra columns and print them - + namesExtraColumns = returnNamesExtraColumns(logFile); - + for iExtraColumn = 1:numel(namesExtraColumns) - + data = logFile(iEvent).(namesExtraColumns{iExtraColumn}); - + printData(logFile(1).fileID, data); - + end - + end function printData(output, data) @@ -323,37 +319,53 @@ function printData(output, data) end function logFile = resetLogFileVar(logFile) - + logFile(2:end) = []; - + namesColumns = {'onset', 'duration', 'trial_type'}; namesExtraColumns = returnNamesExtraColumns(logFile); namesColumns = cat(2, namesColumns, namesExtraColumns'); - + for iColumn = 1:numel(namesColumns) - + if isfield(logFile, namesColumns{iColumn}) logFile = rmfield(logFile, namesColumns{iColumn}); end - + end - + end function errorSaveEventsFile(identifier) - + switch identifier case 'unknownActionType' errorStruct.message = 'unknown action for saveEventsFile'; - + case 'missingFileID' errorStruct.message = 'logFile must contain a valid fileID field'; - + case 'wrongLogSize' errorStruct.message = 'logFile must be a nx1 structure'; - + end - + errorStruct.identifier = ['saveEventsFile:' identifier]; error(errorStruct); end + +function talkToMe(cfg, logFile) + + if cfg.verbose + + fprintf(1, '\nData were saved in this file:\n\n%s\n\n', ... + fullfile( ... + cfg.dir.outputSubject, ... + cfg.fileName.modality, ... + logFile.filename)); + + end + +end + + From 00912ab205e122b86dd688475edc21fe5828aec0 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Sat, 1 Aug 2020 12:44:48 +0200 Subject: [PATCH 06/11] update tests doc update testing doc --- tests/README.md | 74 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/tests/README.md b/tests/README.md index 0559aa7f..673cc339 100644 --- a/tests/README.md +++ b/tests/README.md @@ -7,6 +7,8 @@ This should tell you which tests pass or fail. +## code coverage + The following command would give more info and will give you HTML output in a `coverage_html` folder showing you which lines of code is or is not checked by your test suite. @@ -18,6 +20,78 @@ success = moxunit_runtests(pwd, ... % the path where the tests are '-cover_xml_file','coverage.xml', ... '-cover_html_dir','coverage_html'); ``` +This will return a clear underestimation of the code coverage as the the code in dependencies in the `lib` folder +are also included in this report. + +If you want to get a slightly more accurate estimate you should run the following. + +I have not been able to find a way to exclude certain files without breaking some tests. + +```matlab +coverage = mocov( ... + '-expression', 'moxunit_runtests()', ... + '-verbose', ... + '-cover', fullfile(pwd, '..'), ... + '-cover_exclude', '*jsonread.m', ... + '-cover_exclude', '*json*code.m', ... + '-cover_exclude', '*Contents.m', ... + '-cover_exclude', '*report.m', ... + '-cover_exclude', '*layout.m', ... + '-cover_exclude', '*query.m', ... + '-cover_exclude', '*private*', ... + '-cover_exclude', '*util*', ... + '-cover_exclude', '*Tests*', ... + '-cover_exclude', '*tests*', ... + '-cover_exclude', '*test_*', ... + '-cover_xml_file','coverage.xml', ... + '-cover_html_dir','coverage_html') +``` + + +## Adding more tests + +You can use the following function template to write more tests. + + +```matlab +function test_suite = test_functionToTest() + % This top function is necessary for mox unit to run tests. + % DO NOT CHANGE IT except to adapt the name of the function. + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions = localfunctions(); %#ok<*NASGU> + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; +end +function test_functionToTestBasic() + %% set up + + + %% data to test against + + + %% test + % assertTrue( ); + % assertFalse( ); + % assertEqual( ); + +end + +function test_functionToTestUseCase1() + + %% set up + + + %% data to test against + + + %% test + % assertTrue( ); + % assertFalse( ); + % assertEqual( ); + +end +``` From e20affcd91c4ee7440448715f6a677e40b72f09d Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Sat, 1 Aug 2020 12:53:58 +0200 Subject: [PATCH 07/11] update continuous integration - create github action workflow - remove travis --- .github/workflows/moxunit.yml | 26 ++++++++++++++++++++++++++ .travis.yml | 6 +++--- 2 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/moxunit.yml diff --git a/.github/workflows/moxunit.yml b/.github/workflows/moxunit.yml new file mode 100644 index 00000000..14884735 --- /dev/null +++ b/.github/workflows/moxunit.yml @@ -0,0 +1,26 @@ +name: CI + +on: + push: + branches: + - master +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: true + fetch-depth: 1 + - uses: agahkarakuzu/moxunit-action@master + with: + src: src + with_coverage: true + cover_xml_file: coverage.xml + - uses: codecov/codecov-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos + file: coverage.xml # optional + flags: unittests # optional + name: codecov-umbrella # optional + fail_ci_if_error: true # optional (default = false) diff --git a/.travis.yml b/.travis.yml index 94ba9f97..51fdee5e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,9 +34,9 @@ before_script: jobs: include: - - stage: "Tests" - name: "Unit and integration Tests" - script: octave $OCTFLAGS --eval "results = runTests; assert(all(~[results.Failed]))" + # - stage: "Tests" + # name: "Unit and integration Tests" + # script: octave $OCTFLAGS --eval "results = runTests; assert(all(~[results.Failed]))" - stage: "BIDS validator" name: "Create and check dataset" script: cd manualTests && octave $OCTFLAGS --eval "test_makeRawDataset" && bids-validator `pwd`/output/rawdata/ --ignoreNiftiHeaders From 02adeef653c108a6b583d823cc06123cffa50d92 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Sat, 1 Aug 2020 12:58:35 +0200 Subject: [PATCH 08/11] add trigger to action on PRs --- .github/workflows/moxunit.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/moxunit.yml b/.github/workflows/moxunit.yml index 14884735..a7c72eb6 100644 --- a/.github/workflows/moxunit.yml +++ b/.github/workflows/moxunit.yml @@ -4,6 +4,9 @@ on: push: branches: - master + pull_request: + branches: '*' + jobs: build: runs-on: ubuntu-latest From d6dd167b4cd8ca6e8ed6db21730e71f34b713fb2 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Sat, 1 Aug 2020 13:13:55 +0200 Subject: [PATCH 09/11] update github action --- .github/workflows/moxunit.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/moxunit.yml b/.github/workflows/moxunit.yml index a7c72eb6..e426698f 100644 --- a/.github/workflows/moxunit.yml +++ b/.github/workflows/moxunit.yml @@ -6,7 +6,7 @@ on: - master pull_request: branches: '*' - + jobs: build: runs-on: ubuntu-latest @@ -15,14 +15,15 @@ jobs: with: submodules: true fetch-depth: 1 - - uses: agahkarakuzu/moxunit-action@master + - name: MOxUnit Action + uses: joergbrech/moxunit-action@v1.1 with: - src: src + tests: tests with_coverage: true cover_xml_file: coverage.xml - - uses: codecov/codecov-action@v1 + - name: Code coverage + uses: codecov/codecov-action@v1 with: - token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos file: coverage.xml # optional flags: unittests # optional name: codecov-umbrella # optional From 2fc70ca1f9f61c087299619bec8f2a211f683fdc Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Sat, 1 Aug 2020 13:19:25 +0200 Subject: [PATCH 10/11] mh autofix --- checkCFG.m | 2 +- checkCppBidsDependencies.m | 2 +- convertSourceToRaw.m | 2 +- createFilename.m | 102 +++++------ printCreditsCppBids.m | 32 ++-- saveEventsFile.m | 202 ++++++++++----------- subfun/returnHeaderName.m | 2 +- subfun/returnNamesExtraColumns.m | 2 +- tests/checkSubFields.m | 22 +-- tests/returnExpectedCfgStructure.m | 22 +-- tests/test_checkCFG.m | 55 +++--- tests/test_createBoldJson.m | 10 +- tests/test_createDataDictionary.m | 8 +- tests/test_createDatasetDescription.m | 2 +- tests/test_createFilename.m | 25 ++- tests/test_createTaskName.m | 8 +- tests/test_saveEventsFileInit.m | 46 ++--- tests/test_saveEventsFileOpen.m | 64 +++---- tests/test_saveEventsFileOpenMultiColumn.m | 34 ++-- tests/test_saveEventsFileSave.m | 12 +- tests/test_setDefaultFields.m | 3 +- 21 files changed, 325 insertions(+), 332 deletions(-) diff --git a/checkCFG.m b/checkCFG.m index 09ddfc9a..a1563897 100644 --- a/checkCFG.m +++ b/checkCFG.m @@ -7,7 +7,7 @@ if nargin < 1 || isempty(cfg) cfg = struct(); end - + checkCppBidsDependencies(cfg); %% list the defaults to set diff --git a/checkCppBidsDependencies.m b/checkCppBidsDependencies.m index 3afc5dfb..aef02a1a 100644 --- a/checkCppBidsDependencies.m +++ b/checkCppBidsDependencies.m @@ -1,7 +1,7 @@ function checkCppBidsDependencies(cfg) % checkCppBidsDependencies() % - + pth = fileparts(mfilename('fullpath')); checkSubmodule(fullfile(pth, 'lib', 'JSONio')); diff --git a/convertSourceToRaw.m b/convertSourceToRaw.m index e6e7df5a..aad85f16 100644 --- a/convertSourceToRaw.m +++ b/convertSourceToRaw.m @@ -6,7 +6,7 @@ function convertSourceToRaw(cfg) % - creates dummy README and CHANGE file % - copy source dir to raw dir % - remove the date suffix (_date-YYYYMMDDHHMM) from the files where it is present - % + % sourceDir = fullfile(cfg.dir.output, 'source'); rawDir = fullfile(cfg.dir.output, 'rawdata'); diff --git a/createFilename.m b/createFilename.m index 3aa01c1a..8f73628a 100644 --- a/createFilename.m +++ b/createFilename.m @@ -11,34 +11,34 @@ % % % See test_createFilename in the test folder for more details on how to use it. - + cfg = checkCFG(cfg); - + cfg.fileName.pattern = ['%0' num2str(cfg.fileName.zeroPadding) '.0f']; cfg.fileName.date = datestr(now, cfg.fileName.dateFormat); - + if ~isfield(cfg, 'task') error('createFilename: missing a task name. i.e cfg.task.name'); end - + cfg = getModality(cfg); - + cfg = createDirectories(cfg); - + cfg = setSuffixes(cfg); - + cfg = setFilenames(cfg); - + talkToMe(cfg); - + cfg = orderfields(cfg); cfg.fileName = orderfields(cfg.fileName); cfg.dir = orderfields(cfg.dir); - + end function cfg = getModality(cfg) - + switch lower(cfg.testingDevice) case 'pc' modality = 'beh'; @@ -53,52 +53,52 @@ otherwise modality = 'beh'; end - + cfg.fileName.modality = modality; - + end function [subjectGrp, subjectNb, sessionNb, modality, taskName] = extractInput(cfg) - + 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; end - + end function cfg = createDirectories(cfg) - + [subjectGrp, subjectNb, sessionNb, modality] = extractInput(cfg); - + pattern = cfg.fileName.pattern; - + % output dir cfg.dir.outputSubject = fullfile ( ... cfg.dir.output, ... 'source', ... ['sub-' subjectGrp, sprintf(pattern, subjectNb)], ... ['ses-', sprintf(pattern, sessionNb)]); - + [~, ~, ~] = mkdir(cfg.dir.output); [~, ~, ~] = mkdir(cfg.dir.outputSubject); [~, ~, ~] = mkdir(fullfile(cfg.dir.outputSubject, modality)); - + if cfg.eyeTracker.do [~, ~, ~] = mkdir(fullfile(cfg.dir.outputSubject, 'eyetracker')); end - + end function cfg = setSuffixes(cfg) - + 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 = { ... 'contrastEnhancement', ... @@ -107,7 +107,7 @@ 'echo', ... 'acquisition' }; - + for iField = 1:numel(fields2Check) if isempty (cfg.mri.(fields2Check{iField})) %#ok<*GFLD> cfg.fileName.suffix.mri.(fields2Check{iField}) = ''; %#ok<*SFLD> @@ -116,85 +116,85 @@ ['_' fields2Check{iField} '-' getfield(cfg.mri, fields2Check{iField})]; end end - + cfg.fileName.suffix = orderfields(cfg.fileName.suffix); - + end function cfg = setFilenames(cfg) - + [subjectGrp, subjectNb, sessionNb, modality, taskName] = extractInput(cfg); - + pattern = cfg.fileName.pattern; - + 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 fileNameBase = ... ['sub-', subjectGrp, sprintf(pattern, subjectNb), ... '_ses-', sprintf(pattern, sessionNb), ... '_task-', taskName]; cfg.fileName.base = fileNameBase; - + switch modality - + case 'func' - + cfg.fileName.events = ... [fileNameBase, ... acqSuffix, ceSuffix, ... dirSuffix, recSuffix, ... runSuffix, echoSuffix, ... '_events_date-' thisDate '.tsv']; - + otherwise - + cfg.fileName.events = ... [fileNameBase, runSuffix, '_events_date-' thisDate '.tsv']; - + end - + cfg.fileName.stim = strrep(cfg.fileName.events, 'events', 'stim'); - + if cfg.eyeTracker.do cfg.fileName.eyetracker = ... [fileNameBase, acqSuffix, ... runSuffix, '_eyetrack_date-' thisDate '.edf']; end - + end function talkToMe(cfg) - + if cfg.verbose - + fprintf(1, '\nData will be saved in this directory:\n\t%s\n', ... fullfile(cfg.dir.outputSubject, cfg.fileName.modality)); - + fprintf(1, '\nData will be saved in this file:\n\t%s\n', ... cfg.fileName.events); - + if cfg.eyeTracker.do - + fprintf(1, '\nEyetracking data will be saved in this directory:\n\t%s\n', ... fullfile(cfg.dir.outputSubject, 'eyetracker')); - + fprintf(1, '\nEyetracking data will be saved in this file:\n\t%s\n', ... cfg.fileName.eyetracker); - + end - + end - + end diff --git a/printCreditsCppBids.m b/printCreditsCppBids.m index 189a064a..ab1e19d8 100644 --- a/printCreditsCppBids.m +++ b/printCreditsCppBids.m @@ -1,25 +1,25 @@ function printCreditsCppBids(cfg) - + verbose = true; if ~isempty(cfg) && isfield(cfg, 'verbose') && ~isempty(cfg.verbose) verbose = cfg.verbose; end - + if verbose - + 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(' '); @@ -28,26 +28,26 @@ function printCreditsCppBids(cfg) 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 - + end diff --git a/saveEventsFile.m b/saveEventsFile.m index 9ce5a01c..5f7388e9 100644 --- a/saveEventsFile.m +++ b/saveEventsFile.m @@ -43,104 +43,104 @@ % to true then this will tell you where the file is located. % % See test_saveEventsFile in the test folder for more details on how to use it. - + if nargin < 1 error('Missing action input'); end - + if nargin < 2 cfg = struct(); end - + if nargin < 3 || isempty(logFile) logFile = checklLogFile('init'); end - + switch action - + case 'init' - + logFile = initializeExtraColumns(logFile); - + case 'open' - + logFile.filename = cfg.fileName.events; - + logFile = initializeFile(cfg, logFile); - + case 'open_stim' - + logFile.filename = cfg.fileName.stim; - + logFile = initializeFile(cfg, logFile); - + case 'save' - + checklLogFile('checkID', logFile); checklLogFile('type&size', logFile); - + logFile = saveToLogFile(logFile); - + case 'close' - + checklLogFile('checkID', logFile); - + % close txt log file fclose(logFile(1).fileID); - + talkToMe(cfg, logFile); - + otherwise - + errorSaveEventsFile('unknownActionType'); - + end - + logFile = resetLogFileVar(logFile); - + end function logFile = checklLogFile(action, logFile, iEvent) - + switch action - + case 'init' - + logFile = struct('filename', [], 'extraColumns', cell(1)); logFile(1).filename = ''; - + case 'checkID' - + if ~isfield(logFile(1), 'fileID') || isempty(logFile(1).fileID) errorSaveEventsFile('missingFileID'); end - + case 'type&size' - + if ~isstruct(logFile) || size(logFile, 2) > 1 errorSaveEventsFile('wrongLogSize'); end - + case 'fields' - + 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); - + end - + end function logFile = initializeFile(cfg, logFile) - + logFile = initializeExtraColumns(logFile); - + createDataDictionary(cfg, logFile); - + % Initialize txt logfiles and empty fields for the standard BIDS % event file logFile.fileID = fopen( ... @@ -149,36 +149,36 @@ cfg.fileName.modality, ... logFile.filename), ... 'w'); - + % print the basic BIDS columns fprintf(logFile.fileID, '%s\t%s\t%s', 'onset', 'duration', 'trial_type'); - + printHeaderExtraColumns(logFile); - + % next line so we start printing at the right place fprintf(logFile.fileID, '\n'); - + end function printHeaderExtraColumns(logFile) % print any extra column specified by the user - + [namesExtraColumns, logFile] = returnNamesExtraColumns(logFile); - + for iExtraColumn = 1:numel(namesExtraColumns) - + nbCol = returnNbColumns(logFile, namesExtraColumns{iExtraColumn}); - + for iCol = 1:nbCol - + headerName = returnHeaderName(namesExtraColumns{iExtraColumn}, nbCol, iCol); - + fprintf(logFile.fileID, '\t%s', headerName); - + end - + end - + end function logFile = checkExtracolumns(logFile, iEvent) @@ -186,24 +186,24 @@ function printHeaderExtraColumns(logFile) % if the field we are looking for does not exist or is empty in the % action logFile structure we will write a n/a % otherwise we write its content - + namesExtraColumns = returnNamesExtraColumns(logFile); - + for iExtraColumn = 1:numel(namesExtraColumns) - + nbCol = returnNbColumns(logFile, namesExtraColumns{iExtraColumn}); - + data = 'n/a'; if isfield(logFile, namesExtraColumns{iExtraColumn}) data = logFile(iEvent).(namesExtraColumns{iExtraColumn}); end - + data = checkInput(data); - + data = nanPadding(data, nbCol); - + logFile(iEvent).(namesExtraColumns{iExtraColumn}) = data; - + if any(isnan(data)) warning('Missing some %s data for this event.', namesExtraColumns{iExtraColumn}); disp(logFile(iEvent)); @@ -211,22 +211,22 @@ function printHeaderExtraColumns(logFile) warning('Missing %s data for this event.', namesExtraColumns{iExtraColumn}); disp(logFile(iEvent)); end - + end - + end 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 islogical(data) && data data = 'true'; elseif islogical(data) && ~data data = 'false'; end - + if ischar(data) && isempty(data) || strcmp(data, ' ') data = 'n/a'; elseif isempty(data) @@ -234,15 +234,15 @@ function printHeaderExtraColumns(logFile) % numeric valur has the right length and needs to be nan padded 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); @@ -250,56 +250,56 @@ function printHeaderExtraColumns(logFile) 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 function printExtraColumns(logFile, iEvent) % loops through the extra columns and print them - + namesExtraColumns = returnNamesExtraColumns(logFile); - + for iExtraColumn = 1:numel(namesExtraColumns) - + data = logFile(iEvent).(namesExtraColumns{iExtraColumn}); - + printData(logFile(1).fileID, data); - + end - + end function printData(output, data) @@ -319,53 +319,51 @@ function printData(output, data) end function logFile = resetLogFileVar(logFile) - + logFile(2:end) = []; - + namesColumns = {'onset', 'duration', 'trial_type'}; namesExtraColumns = returnNamesExtraColumns(logFile); namesColumns = cat(2, namesColumns, namesExtraColumns'); - + for iColumn = 1:numel(namesColumns) - + if isfield(logFile, namesColumns{iColumn}) logFile = rmfield(logFile, namesColumns{iColumn}); end - + end - + end function errorSaveEventsFile(identifier) - + switch identifier case 'unknownActionType' errorStruct.message = 'unknown action for saveEventsFile'; - + case 'missingFileID' errorStruct.message = 'logFile must contain a valid fileID field'; - + case 'wrongLogSize' errorStruct.message = 'logFile must be a nx1 structure'; - + end - + errorStruct.identifier = ['saveEventsFile:' identifier]; error(errorStruct); end function talkToMe(cfg, logFile) - + if cfg.verbose - + fprintf(1, '\nData were saved in this file:\n\n%s\n\n', ... fullfile( ... cfg.dir.outputSubject, ... cfg.fileName.modality, ... logFile.filename)); - - end - -end + end +end diff --git a/subfun/returnHeaderName.m b/subfun/returnHeaderName.m index 1caff806..2401ea0d 100644 --- a/subfun/returnHeaderName.m +++ b/subfun/returnHeaderName.m @@ -1,7 +1,7 @@ function headerName = returnHeaderName(columnName, nbCol, iCol) % headerName = returnHeaderName(columnName, nbCol, iCol) % - + if nbCol == 1 headerName = sprintf('%s', columnName); else diff --git a/subfun/returnNamesExtraColumns.m b/subfun/returnNamesExtraColumns.m index 502d2e57..ada0791c 100644 --- a/subfun/returnNamesExtraColumns.m +++ b/subfun/returnNamesExtraColumns.m @@ -1,7 +1,7 @@ function [namesExtraColumns, logFile] = returnNamesExtraColumns(logFile) % [namesExtraColumns, logFile] = returnNamesExtraColumns(logFile) % - + namesExtraColumns = []; if isfield(logFile, 'extraColumns') && ~isempty(logFile(1).extraColumns) diff --git a/tests/checkSubFields.m b/tests/checkSubFields.m index 764f4aff..a6b8cb7e 100644 --- a/tests/checkSubFields.m +++ b/tests/checkSubFields.m @@ -1,29 +1,29 @@ function checkSubFields(expectedStructure, cfg) % check that that the structures match % if it fails it check from which subfield the error comes from - + try - + assertEqual(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 - + disp(expectedStructure); disp(cfg); - + rethrow(ME); end end diff --git a/tests/returnExpectedCfgStructure.m b/tests/returnExpectedCfgStructure.m index 41982a55..749338ae 100644 --- a/tests/returnExpectedCfgStructure.m +++ b/tests/returnExpectedCfgStructure.m @@ -1,30 +1,30 @@ function expectedCfgStructure = returnExpectedCfgStructure() - + expectedCfgStructure.subject.subjectGrp = ''; expectedCfgStructure.subject.sessionNb = 1; expectedCfgStructure.subject.askGrpSess = [true true]; - + expectedCfgStructure.verbose = 0; - + expectedCfgStructure.fileName.task = ''; expectedCfgStructure.fileName.zeroPadding = 3; expectedCfgStructure.fileName.dateFormat = 'yyyymmddHHMM'; - + expectedCfgStructure.eyeTracker.do = false; - + expectedCfgStructure.mri.contrastEnhancement = []; expectedCfgStructure.mri.phaseEncodingDirection = []; expectedCfgStructure.mri.reconstruction = []; expectedCfgStructure.mri.echo = []; expectedCfgStructure.mri.acquisition = []; expectedCfgStructure.mri.repetitionTime = []; - + expectedCfgStructure.bids.mri.RepetitionTime = []; expectedCfgStructure.bids.mri.SliceTiming = ''; expectedCfgStructure.bids.mri.TaskName = ''; expectedCfgStructure.bids.mri.Instructions = ''; expectedCfgStructure.bids.mri.TaskDescription = ''; - + expectedCfgStructure.bids.meg.TaskName = ''; expectedCfgStructure.bids.meg.SamplingFrequency = []; expectedCfgStructure.bids.meg.PowerLineFrequency = []; @@ -32,7 +32,7 @@ expectedCfgStructure.bids.meg.SoftwareFilters = []; expectedCfgStructure.bids.meg.DigitizedLandmarks = []; expectedCfgStructure.bids.meg.DigitizedHeadPoints = []; - + expectedCfgStructure.bids.datasetDescription.Name = ''; expectedCfgStructure.bids.datasetDescription.BIDSVersion = ''; expectedCfgStructure.bids.datasetDescription.License = ''; @@ -42,7 +42,7 @@ expectedCfgStructure.bids.datasetDescription.Funding = {''}; expectedCfgStructure.bids.datasetDescription.ReferencesAndLinks = {''}; expectedCfgStructure.bids.datasetDescription.DatasetDOI = ''; - + expectedCfgStructure = orderfields(expectedCfgStructure); - -end \ No newline at end of file + +end diff --git a/tests/test_checkCFG.m b/tests/test_checkCFG.m index 3768bdd0..80656a23 100644 --- a/tests/test_checkCFG.m +++ b/tests/test_checkCFG.m @@ -7,75 +7,72 @@ end function test_checkCfgDefault() - + %% set up cfg.dir.output = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); cfg = checkCFG(cfg); - + %% create test data expectedStructure = returnExpectedCfgStructure(); expectedStructure.dir.output = cfg.dir.output; expectedStructure.testingDevice = 'pc'; - + %% test checkSubFields(expectedStructure, cfg); - + end - + function test_checkCfgBasic() - + %% set up outputDir = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); - + cfg.verbose = false; - + cfg.subject.subjectNb = 1; cfg.subject.runNb = 1; - + cfg.task.name = 'test task'; - + 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'; - + cfg = checkCFG(cfg); - + %% create test data expectedStructure = returnExpectedCfgStructure(); 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.fileName.task = 'testTask'; - + expectedStructure.bids.mri.RepetitionTime = 1.56; expectedStructure.bids.mri.TaskName = 'test Task'; - + expectedStructure.bids.meg.TaskName = 'test Task'; - + expectedStructure.bids.datasetDescription.Name = 'dummy'; expectedStructure.bids.datasetDescription.BIDSVersion = '1.0.0'; expectedStructure.bids.datasetDescription.Authors = {'Jane Doe', 'John Doe'}; - + expectedStructure = orderfields(expectedStructure); - + %% test checkSubFields(expectedStructure, cfg); - -end - - +end diff --git a/tests/test_createBoldJson.m b/tests/test_createBoldJson.m index 48629da1..769d7148 100644 --- a/tests/test_createBoldJson.m +++ b/tests/test_createBoldJson.m @@ -10,15 +10,15 @@ function test_createBoldJsonBasic() outputDir = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); - %% set up + %% set up cfg.verbose = false; - + cfg.subject.subjectNb = 1; cfg.subject.runNb = 1; - + cfg.task.name = 'testtask'; - + cfg.dir.output = outputDir; cfg.testingDevice = 'mri'; @@ -31,7 +31,7 @@ function test_createBoldJsonBasic() %% data to test against funcDir = fullfile(outputDir, 'source', 'sub-001', 'ses-001', 'func'); - + eventFilename = ['sub-001_ses-001_task-testtask_run-001_bold_date-' ... cfg.fileName.date '.json']; diff --git a/tests/test_createDataDictionary.m b/tests/test_createDataDictionary.m index 5c26006a..dc8f5ed2 100644 --- a/tests/test_createDataDictionary.m +++ b/tests/test_createDataDictionary.m @@ -29,7 +29,7 @@ function test_createDataDictionaryBasic() createDataDictionary(cfg, logFile); %% check that the file has the right path and name - + % data to test against funcDir = fullfile(outputDir, 'source', 'sub-001', 'ses-001', 'func'); jsonFilename = ['sub-001_ses-001_task-testtask_run-001_events_date-' ... @@ -40,11 +40,11 @@ function test_createDataDictionaryBasic() %% check content actualStruct = bids.util.jsondecode(fullfile(funcDir, jsonFilename)); - + % data to test against - expectedStruct = bids.util.jsondecode(... + expectedStruct = bids.util.jsondecode( ... fullfile(pwd, 'testData', 'eventsDataDictionary.json')); - + % test assertTrue(isequal(expectedStruct, actualStruct)); diff --git a/tests/test_createDatasetDescription.m b/tests/test_createDatasetDescription.m index 1e2e0d89..c4664eb0 100644 --- a/tests/test_createDatasetDescription.m +++ b/tests/test_createDatasetDescription.m @@ -13,7 +13,7 @@ function test_createDatasetDescriptionBasic() %% set up cfg.dir.output = outputDir; - + cfg.verbose = false; cfg.bids.datasetDescription.json.Name = 'dummy_dataset'; diff --git a/tests/test_createFilename.m b/tests/test_createFilename.m index 803ecd04..772a536a 100644 --- a/tests/test_createFilename.m +++ b/tests/test_createFilename.m @@ -22,17 +22,17 @@ function test_createFilenameBasic() %% data to test against 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-'... cfg.fileName.date '.tsv']; - + stimFilename = ['sub-001_ses-001_task-testTask_run-001_stim_date-'... cfg.fileName.date '.tsv']; %% test - + % make sure the beh dir is created assertTrue(exist(behDir, 'dir') == 7); @@ -45,7 +45,6 @@ function test_createFilenameBasic() % make sure the stim filename is created assertEqual(cfg.fileName.stim, stimFilename); - end function test_createFilenameMriEyetracker() @@ -68,16 +67,16 @@ function test_createFilenameMriEyetracker() cfg = createFilename(cfg); %% data to test against - + 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-' ... cfg.fileName.date '.tsv']; - + eyetrackerFilename = ['sub-ctrl002_ses-002_task-testTask_run-002_eyetrack_date-' ... cfg.fileName.date '.edf']; @@ -92,9 +91,9 @@ function test_createFilenameMriEyetracker() assertEqual(cfg.fileName.base, baseFilename); assertEqual(cfg.fileName.events, eventFilename); assertEqual(cfg.fileName.eyetracker, eyetrackerFilename); - + end - + function test_createFilenameEeg() outputDir = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); @@ -120,4 +119,4 @@ function test_createFilenameEeg() % make sure the func dir is created assertTrue(exist(eegDir, 'dir') == 7); -end \ No newline at end of file +end diff --git a/tests/test_createTaskName.m b/tests/test_createTaskName.m index 40f14ef3..f3aac9a4 100644 --- a/tests/test_createTaskName.m +++ b/tests/test_createTaskName.m @@ -8,10 +8,10 @@ function test_createTaskNameRemoveInvalidCharacters() - %% set up - + %% set up + taskName = '&|@#-_(§!{})[]ù%£+/=:;.?,\<> visual task'; - + [taskName, taskNameValid] = createTaskName(taskName); [unvalidCharacters] = regexp(taskNameValid, '[^a-zA-Z0-9]'); @@ -41,7 +41,7 @@ function test_createTaskNameCamelCase() %% set up taskName = 'foo bar'; [taskName, taskNameValid] = createTaskName(taskName); - + %% test assertEqual(taskName, 'foo Bar'); assertEqual(taskNameValid, 'fooBar'); diff --git a/tests/test_saveEventsFileInit.m b/tests/test_saveEventsFileInit.m index ed86be99..d56fe165 100644 --- a/tests/test_saveEventsFileInit.m +++ b/tests/test_saveEventsFileInit.m @@ -7,62 +7,62 @@ end function test_saveEventsFileInitBasic() - - %% set up + + %% set up cfg.verbose = false; - + % make sure the dependencies are there checkCFG(cfg); - + [logFile] = saveEventsFile('init'); - + %% data to test against expectedStrcut(1).filename = ''; expectedStrcut(1).extraColumns = []; - + %% test assertEqual(expectedStrcut, logFile); - + end function test_saveEventsFileInitExtraColumns() - - %% set up + + %% set up cfg.verbose = false; - + cfg = checkCFG(cfg); - + logFile.extraColumns = {'Speed'}; - + [logFile] = saveEventsFile('init', cfg, logFile); - + %% data to test against expectedStrcut(1).extraColumns.Speed.length = 1; expectedStrcut(1).extraColumns.Speed.bids.LongName = ''; expectedStrcut(1).extraColumns.Speed.bids.Description = ''; expectedStrcut(1).extraColumns.Speed.bids.Levels = ''; expectedStrcut(1).extraColumns.Speed.bids.TermURL = ''; - + %% test assertEqual(expectedStrcut, logFile); - + end function test_saveEventsFileInitExtraColumnsArray() - - %% set up + + %% set up cfg.verbose = false; - + cfg = checkCFG(cfg); - + logFile.extraColumns.Speed.length = 1; logFile.extraColumns.LHL24.length = 3; - + [logFile] = saveEventsFile('init', cfg, logFile); - + %% data to test against expectedStrcut(1).extraColumns.Speed.length = 1; expectedStrcut(1).extraColumns.Speed.bids.LongName = ''; @@ -74,8 +74,8 @@ function test_saveEventsFileInitExtraColumnsArray() expectedStrcut(1).extraColumns.LHL24.bids.Description = ''; expectedStrcut(1).extraColumns.LHL24.bids.Levels = ''; expectedStrcut(1).extraColumns.LHL24.bids.TermURL = ''; - + %% test assertEqual(expectedStrcut, logFile); - + end diff --git a/tests/test_saveEventsFileOpen.m b/tests/test_saveEventsFileOpen.m index e536a03d..134f6ffe 100644 --- a/tests/test_saveEventsFileOpen.m +++ b/tests/test_saveEventsFileOpen.m @@ -7,89 +7,89 @@ end function test_saveEventsFileOpenBasic() - + outputDir = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); - + %% set up - + cfg.verbose = false; - + cfg.subject.subjectNb = 1; cfg.subject.runNb = 1; - + cfg.task.name = 'testtask'; - + cfg.dir.output = outputDir; - + cfg.testingDevice = 'mri'; - + cfg = createFilename(cfg); - + % create the events file and header logFile = saveEventsFile('open', cfg); - + % close the file saveEventsFile('close', cfg, logFile); - + %% data to test against funcDir = fullfile(outputDir, 'source', 'sub-001', 'ses-001', 'func'); eventFilename = ['sub-001_ses-001_task-testtask_run-001_events_date-' ... 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'); - + %% test % check the extra columns of the header assertEqual(C{1}{1}, 'onset'); assertEqual(C{2}{1}, 'duration'); assertEqual(C{3}{1}, 'trial_type'); - + end function test_saveEventsFileOpenExtraColumns() - + outputDir = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); - + %% set up - + cfg.verbose = false; - + cfg.subject.subjectNb = 1; - + cfg.subject.runNb = 1; - + cfg.task.name = 'testtask'; - + cfg.dir.output = outputDir; - + cfg.testingDevice = 'mri'; - + cfg = createFilename(cfg); - - % define the extra columns + + % define the extra columns % they will be added to the tsv files in the order the user input them logFile.extraColumns = {'Speed', 'is_Fixation'}; % create the events file and header logFile = saveEventsFile('open', cfg, logFile); - + % close the file saveEventsFile('close', cfg, logFile); - + %% data to test against - + % open the file funcDir = fullfile(cfg.dir.outputSubject, cfg.fileName.modality); eventFilename = cfg.fileName.events; - + nbExtraCol = 2; FID = fopen(fullfile(funcDir, eventFilename), 'r'); C = textscan(FID, repmat('%s', 1, nbExtraCol + 3), 'Delimiter', '\t', 'EndOfLine', '\n'); - + %% test % check the extra columns of the header assertEqual(C{1}{1}, 'onset'); @@ -97,5 +97,5 @@ function test_saveEventsFileOpenExtraColumns() assertEqual(C{3}{1}, 'trial_type'); assertEqual(C{4}{1}, 'Speed'); assertEqual(C{5}{1}, 'is_Fixation'); - + end diff --git a/tests/test_saveEventsFileOpenMultiColumn.m b/tests/test_saveEventsFileOpenMultiColumn.m index 66c826b0..05e63f41 100644 --- a/tests/test_saveEventsFileOpenMultiColumn.m +++ b/tests/test_saveEventsFileOpenMultiColumn.m @@ -7,53 +7,53 @@ end function test_saveEventsFileOpenMultiColumnCheckHeader() - + outputDir = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); - + %% set up - + cfg.verbose = false; - + cfg.subject.subjectNb = 1; cfg.subject.runNb = 1; - + cfg.task.name = 'testtask'; - + cfg.dir.output = outputDir; - + cfg.testingDevice = 'mri'; - + cfg = createFilename(cfg); - + % define the extra columns: here we specify how many columns we want for % each variable logFile.extraColumns.Speed.length = 1; % will set 1 columns with name Speed logFile.extraColumns.LHL24.length = 12; % will set 12 columns with names LHL24-01, LHL24-02, ... logFile.extraColumns.is_Fixation = []; % will set 1 columns with name is_Fixation - + % create the events file and header logFile = saveEventsFile('open', cfg, logFile); - + % close the file saveEventsFile('close', cfg, logFile); - + 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; - + %% test 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 assertEqual(C{4}{1}, 'Speed'); assertEqual(C{5}{1}, 'LHL24_01'); assertEqual(C{16}{1}, 'LHL24_12'); assertEqual(C{17}{1}, 'is_Fixation'); - + end diff --git a/tests/test_saveEventsFileSave.m b/tests/test_saveEventsFileSave.m index b49d5001..514aee75 100644 --- a/tests/test_saveEventsFileSave.m +++ b/tests/test_saveEventsFileSave.m @@ -10,15 +10,15 @@ function test_saveEventsFileSaveBasic() outputDir = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); - %% set up + %% set up cfg.verbose = false; - + cfg.subject.subjectNb = 1; cfg.subject.runNb = 1; - + cfg.task.name = 'testtask'; - + cfg.dir.output = outputDir; cfg.testingDevice = 'mri'; @@ -89,9 +89,9 @@ function test_saveEventsFileSaveBasic() logFile(1).extraColumns.is_Fixation.length; 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'); diff --git a/tests/test_setDefaultFields.m b/tests/test_setDefaultFields.m index c327d148..39ca05d4 100644 --- a/tests/test_setDefaultFields.m +++ b/tests/test_setDefaultFields.m @@ -20,9 +20,8 @@ function test_setDefaultFieldsWrite() %% test assertEqual(expectedStructure, structure); - -end +end function test_setDefaultFieldsNoOverwrite() From 14baca80614b34a7a7db22fa47e684b0a78b137f Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Sat, 1 Aug 2020 17:17:16 +0200 Subject: [PATCH 11/11] reorganize for github actions --- .github/workflows/moxunit.yml | 2 + .gitignore | 4 +- .travis.yml | 6 +- checkCppBidsDependencies.m | 27 ------- .../manualTests => manualTests}/miss_hit.cfg | 0 .../testData/eventsDataDictionary.json | 0 .../test_createDataDictionary.m | 4 + .../test_makeRawDataset.m | 8 +- .../test_userInput.m | 0 checkCFG.m => src/checkCFG.m | 0 src/checkCppBidsDependencies.m | 39 +++++++++ .../convertSourceToRaw.m | 4 +- createBoldJson.m => src/createBoldJson.m | 0 .../createDataDictionary.m | 0 .../createDatasetDescription.m | 0 createFilename.m => src/createFilename.m | 0 src/miss_hit.cfg | 3 + .../printCreditsCppBids.m | 0 saveEventsFile.m => src/saveEventsFile.m | 0 {subfun => src/subfun}/createTaskName.m | 0 .../subfun}/initializeExtraColumns.m | 0 {subfun => src/subfun}/returnHeaderName.m | 0 .../subfun}/returnNamesExtraColumns.m | 0 {subfun => src/subfun}/returnNbColumns.m | 0 {subfun => src/subfun}/setDefaultFields.m | 0 {subfun => src/subfun}/transferInfoToBids.m | 0 userInputs.m => src/userInputs.m | 0 tests/checkSubFields.m | 29 ------- tests/returnExpectedCfgStructure.m | 48 ----------- tests/test_checkCFG.m | 79 +++++++++++++++++++ 30 files changed, 139 insertions(+), 114 deletions(-) delete mode 100644 checkCppBidsDependencies.m rename {tests/manualTests => manualTests}/miss_hit.cfg (100%) rename {tests => manualTests}/testData/eventsDataDictionary.json (100%) rename {tests => manualTests}/test_createDataDictionary.m (99%) rename {tests/manualTests => manualTests}/test_makeRawDataset.m (92%) rename {tests/manualTests => manualTests}/test_userInput.m (100%) rename checkCFG.m => src/checkCFG.m (100%) create mode 100644 src/checkCppBidsDependencies.m rename convertSourceToRaw.m => src/convertSourceToRaw.m (92%) rename createBoldJson.m => src/createBoldJson.m (100%) rename createDataDictionary.m => src/createDataDictionary.m (100%) rename createDatasetDescription.m => src/createDatasetDescription.m (100%) rename createFilename.m => src/createFilename.m (100%) create mode 100644 src/miss_hit.cfg rename printCreditsCppBids.m => src/printCreditsCppBids.m (100%) rename saveEventsFile.m => src/saveEventsFile.m (100%) rename {subfun => src/subfun}/createTaskName.m (100%) rename {subfun => src/subfun}/initializeExtraColumns.m (100%) rename {subfun => src/subfun}/returnHeaderName.m (100%) rename {subfun => src/subfun}/returnNamesExtraColumns.m (100%) rename {subfun => src/subfun}/returnNbColumns.m (100%) rename {subfun => src/subfun}/setDefaultFields.m (100%) rename {subfun => src/subfun}/transferInfoToBids.m (100%) rename userInputs.m => src/userInputs.m (100%) delete mode 100644 tests/checkSubFields.m delete mode 100644 tests/returnExpectedCfgStructure.m diff --git a/.github/workflows/moxunit.yml b/.github/workflows/moxunit.yml index e426698f..16a9ee9c 100644 --- a/.github/workflows/moxunit.yml +++ b/.github/workflows/moxunit.yml @@ -19,11 +19,13 @@ jobs: uses: joergbrech/moxunit-action@v1.1 with: tests: tests + src: src with_coverage: true cover_xml_file: coverage.xml - name: Code coverage uses: codecov/codecov-action@v1 with: + token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos file: coverage.xml # optional flags: unittests # optional name: codecov-umbrella # optional diff --git a/.gitignore b/.gitignore index fef67417..51fd1392 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,9 @@ *octave-workspace # exclude content of logfiles folders -output/** +tests/output/* +manualTests/output/* +output/* *.tsv *.mat diff --git a/.travis.yml b/.travis.yml index 51fdee5e..16d3b453 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,8 +29,10 @@ install: - sudo npm install -g bids-validator before_script: + # Add to src functions to path + - octave $OCTFLAGS --eval "addpath(genpath(fullfile(pwd, 'src'))); savepath ();" # Change current directory - - cd tests + - cd manualTests jobs: include: @@ -39,7 +41,7 @@ jobs: # script: octave $OCTFLAGS --eval "results = runTests; assert(all(~[results.Failed]))" - stage: "BIDS validator" name: "Create and check dataset" - script: cd manualTests && octave $OCTFLAGS --eval "test_makeRawDataset" && bids-validator `pwd`/output/rawdata/ --ignoreNiftiHeaders + script: octave $OCTFLAGS --eval "test_makeRawDataset" && bids-validator `pwd`/output/rawdata/ --ignoreNiftiHeaders - stage: "Linter" name: "miss_hit" script: cd .. && mh_style `pwd` diff --git a/checkCppBidsDependencies.m b/checkCppBidsDependencies.m deleted file mode 100644 index aef02a1a..00000000 --- a/checkCppBidsDependencies.m +++ /dev/null @@ -1,27 +0,0 @@ -function checkCppBidsDependencies(cfg) - % checkCppBidsDependencies() - % - - pth = fileparts(mfilename('fullpath')); - - checkSubmodule(fullfile(pth, 'lib', 'JSONio')); - checkSubmodule(fullfile(pth, 'lib', 'bids-matlab')); - - addpath(fullfile(pth, 'lib', 'utils')); - addpath(fullfile(pth, 'subfun')); - - printCreditsCppBids(cfg); - -end - -function checkSubmodule(pth) - % If external dir is empty throw an exception - % and ask user to update submodules. - if numel(dir(pth)) <= 2 % Means that the external is empty - error(['Git submodules are not cloned!', ... - 'Try this in your terminal:', ... - ' git submodule update --recursive ']); - else - addpath(pth); - end -end diff --git a/tests/manualTests/miss_hit.cfg b/manualTests/miss_hit.cfg similarity index 100% rename from tests/manualTests/miss_hit.cfg rename to manualTests/miss_hit.cfg diff --git a/tests/testData/eventsDataDictionary.json b/manualTests/testData/eventsDataDictionary.json similarity index 100% rename from tests/testData/eventsDataDictionary.json rename to manualTests/testData/eventsDataDictionary.json diff --git a/tests/test_createDataDictionary.m b/manualTests/test_createDataDictionary.m similarity index 99% rename from tests/test_createDataDictionary.m rename to manualTests/test_createDataDictionary.m index dc8f5ed2..d1f71138 100644 --- a/tests/test_createDataDictionary.m +++ b/manualTests/test_createDataDictionary.m @@ -13,9 +13,12 @@ function test_createDataDictionaryBasic() %% set up cfg.verbose = false; + cfg.subject.subjectNb = 1; cfg.subject.runNb = 1; + cfg.task.name = 'testtask'; + cfg.dir.output = outputDir; cfg.testingDevice = 'mri'; @@ -32,6 +35,7 @@ function test_createDataDictionaryBasic() % data to test against funcDir = fullfile(outputDir, 'source', 'sub-001', 'ses-001', 'func'); + jsonFilename = ['sub-001_ses-001_task-testtask_run-001_events_date-' ... cfg.fileName.date '.json']; diff --git a/tests/manualTests/test_makeRawDataset.m b/manualTests/test_makeRawDataset.m similarity index 92% rename from tests/manualTests/test_makeRawDataset.m rename to manualTests/test_makeRawDataset.m index e3ca4b3d..51b30262 100644 --- a/tests/manualTests/test_makeRawDataset.m +++ b/manualTests/test_makeRawDataset.m @@ -1,9 +1,5 @@ function test_makeRawDataset() - fprintf('\n\n--------------------------------------------------------------------\n\n'); - - clear; - outputDir = fullfile(fileparts(mfilename('fullpath')), 'output'); if isdir(outputDir) @@ -16,7 +12,9 @@ function test_makeRawDataset() cfg.subject.subjectNb = 1; cfg.subject.runNb = 1; + cfg.task.name = 'testtask'; + cfg.dir.output = outputDir; cfg.bids.datasetDescription.Name = 'dummy'; @@ -77,7 +75,7 @@ function test_makeRawDataset() 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)); %% diff --git a/tests/manualTests/test_userInput.m b/manualTests/test_userInput.m similarity index 100% rename from tests/manualTests/test_userInput.m rename to manualTests/test_userInput.m diff --git a/checkCFG.m b/src/checkCFG.m similarity index 100% rename from checkCFG.m rename to src/checkCFG.m diff --git a/src/checkCppBidsDependencies.m b/src/checkCppBidsDependencies.m new file mode 100644 index 00000000..03dfd92f --- /dev/null +++ b/src/checkCppBidsDependencies.m @@ -0,0 +1,39 @@ +function checkCppBidsDependencies(cfg) + % checkCppBidsDependencies() + % + + GITHUB_WORKSPACE = getenv('GITHUB_WORKSPACE'); + + if strcmp(GITHUB_WORKSPACE, '/github/workspace') + + pth = GITHUB_WORKSPACE; + addpath(fullfile(pth, 'lib', 'JSONio')); + addpath(fullfile(pth, 'lib', 'bids-matlab')); + + elseif isempty(GITHUB_WORKSPACE) % local + + pth = fullfile(fileparts(mfilename('fullpath')), '..'); + checkSubmodule(fullfile(pth, 'lib', 'JSONio')); + checkSubmodule(fullfile(pth, 'lib', 'bids-matlab')); + + addpath(fullfile(pth, 'src', 'subfun')); + + end + + addpath(fullfile(pth, 'lib', 'utils')); + + printCreditsCppBids(cfg); + +end + +function checkSubmodule(pth) + % If external dir is empty throw an exception + % and ask user to update submodules. + if numel(dir(pth)) <= 2 % Means that the external is empty + error(['Git submodules are not cloned!', ... + 'Try this in your terminal:', ... + ' git submodule update --recursive ']); + else + addpath(pth); + end +end diff --git a/convertSourceToRaw.m b/src/convertSourceToRaw.m similarity index 92% rename from convertSourceToRaw.m rename to src/convertSourceToRaw.m index aad85f16..bc73606a 100644 --- a/convertSourceToRaw.m +++ b/src/convertSourceToRaw.m @@ -13,10 +13,10 @@ function convertSourceToRaw(cfg) % add dummy readme and change file copyfile(fullfile( ... - fileparts(mfilename('fullpath')), 'dummyData', 'README'), ... + fileparts(mfilename('fullpath')), '..', 'dummyData', 'README'), ... sourceDir); copyfile(fullfile( ... - fileparts(mfilename('fullpath')), 'dummyData', 'CHANGES'), ... + fileparts(mfilename('fullpath')), '..', 'dummyData', 'CHANGES'), ... sourceDir); copyfile(sourceDir, rawDir); diff --git a/createBoldJson.m b/src/createBoldJson.m similarity index 100% rename from createBoldJson.m rename to src/createBoldJson.m diff --git a/createDataDictionary.m b/src/createDataDictionary.m similarity index 100% rename from createDataDictionary.m rename to src/createDataDictionary.m diff --git a/createDatasetDescription.m b/src/createDatasetDescription.m similarity index 100% rename from createDatasetDescription.m rename to src/createDatasetDescription.m diff --git a/createFilename.m b/src/createFilename.m similarity index 100% rename from createFilename.m rename to src/createFilename.m diff --git a/src/miss_hit.cfg b/src/miss_hit.cfg new file mode 100644 index 00000000..fce7fa45 --- /dev/null +++ b/src/miss_hit.cfg @@ -0,0 +1,3 @@ +line_length: 100 +regex_function_name: "[a-z]+(([A-Z]){1}[A-Za-z]+)*" +suppress_rule: "copyright_notice" \ No newline at end of file diff --git a/printCreditsCppBids.m b/src/printCreditsCppBids.m similarity index 100% rename from printCreditsCppBids.m rename to src/printCreditsCppBids.m diff --git a/saveEventsFile.m b/src/saveEventsFile.m similarity index 100% rename from saveEventsFile.m rename to src/saveEventsFile.m diff --git a/subfun/createTaskName.m b/src/subfun/createTaskName.m similarity index 100% rename from subfun/createTaskName.m rename to src/subfun/createTaskName.m diff --git a/subfun/initializeExtraColumns.m b/src/subfun/initializeExtraColumns.m similarity index 100% rename from subfun/initializeExtraColumns.m rename to src/subfun/initializeExtraColumns.m diff --git a/subfun/returnHeaderName.m b/src/subfun/returnHeaderName.m similarity index 100% rename from subfun/returnHeaderName.m rename to src/subfun/returnHeaderName.m diff --git a/subfun/returnNamesExtraColumns.m b/src/subfun/returnNamesExtraColumns.m similarity index 100% rename from subfun/returnNamesExtraColumns.m rename to src/subfun/returnNamesExtraColumns.m diff --git a/subfun/returnNbColumns.m b/src/subfun/returnNbColumns.m similarity index 100% rename from subfun/returnNbColumns.m rename to src/subfun/returnNbColumns.m diff --git a/subfun/setDefaultFields.m b/src/subfun/setDefaultFields.m similarity index 100% rename from subfun/setDefaultFields.m rename to src/subfun/setDefaultFields.m diff --git a/subfun/transferInfoToBids.m b/src/subfun/transferInfoToBids.m similarity index 100% rename from subfun/transferInfoToBids.m rename to src/subfun/transferInfoToBids.m diff --git a/userInputs.m b/src/userInputs.m similarity index 100% rename from userInputs.m rename to src/userInputs.m diff --git a/tests/checkSubFields.m b/tests/checkSubFields.m deleted file mode 100644 index a6b8cb7e..00000000 --- a/tests/checkSubFields.m +++ /dev/null @@ -1,29 +0,0 @@ -function checkSubFields(expectedStructure, cfg) - % check that that the structures match - % if it fails it check from which subfield the error comes from - - try - - assertEqual(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 - - disp(expectedStructure); - disp(cfg); - - rethrow(ME); - end -end diff --git a/tests/returnExpectedCfgStructure.m b/tests/returnExpectedCfgStructure.m deleted file mode 100644 index 749338ae..00000000 --- a/tests/returnExpectedCfgStructure.m +++ /dev/null @@ -1,48 +0,0 @@ -function expectedCfgStructure = returnExpectedCfgStructure() - - expectedCfgStructure.subject.subjectGrp = ''; - expectedCfgStructure.subject.sessionNb = 1; - expectedCfgStructure.subject.askGrpSess = [true true]; - - expectedCfgStructure.verbose = 0; - - expectedCfgStructure.fileName.task = ''; - expectedCfgStructure.fileName.zeroPadding = 3; - expectedCfgStructure.fileName.dateFormat = 'yyyymmddHHMM'; - - expectedCfgStructure.eyeTracker.do = false; - - expectedCfgStructure.mri.contrastEnhancement = []; - expectedCfgStructure.mri.phaseEncodingDirection = []; - expectedCfgStructure.mri.reconstruction = []; - expectedCfgStructure.mri.echo = []; - expectedCfgStructure.mri.acquisition = []; - expectedCfgStructure.mri.repetitionTime = []; - - expectedCfgStructure.bids.mri.RepetitionTime = []; - expectedCfgStructure.bids.mri.SliceTiming = ''; - expectedCfgStructure.bids.mri.TaskName = ''; - expectedCfgStructure.bids.mri.Instructions = ''; - expectedCfgStructure.bids.mri.TaskDescription = ''; - - expectedCfgStructure.bids.meg.TaskName = ''; - expectedCfgStructure.bids.meg.SamplingFrequency = []; - expectedCfgStructure.bids.meg.PowerLineFrequency = []; - expectedCfgStructure.bids.meg.DewarPosition = []; - expectedCfgStructure.bids.meg.SoftwareFilters = []; - expectedCfgStructure.bids.meg.DigitizedLandmarks = []; - expectedCfgStructure.bids.meg.DigitizedHeadPoints = []; - - expectedCfgStructure.bids.datasetDescription.Name = ''; - expectedCfgStructure.bids.datasetDescription.BIDSVersion = ''; - expectedCfgStructure.bids.datasetDescription.License = ''; - expectedCfgStructure.bids.datasetDescription.Authors = {''}; - expectedCfgStructure.bids.datasetDescription.Acknowledgements = ''; - expectedCfgStructure.bids.datasetDescription.HowToAcknowledge = ''; - expectedCfgStructure.bids.datasetDescription.Funding = {''}; - expectedCfgStructure.bids.datasetDescription.ReferencesAndLinks = {''}; - expectedCfgStructure.bids.datasetDescription.DatasetDOI = ''; - - expectedCfgStructure = orderfields(expectedCfgStructure); - -end diff --git a/tests/test_checkCFG.m b/tests/test_checkCFG.m index 80656a23..f82a1171 100644 --- a/tests/test_checkCFG.m +++ b/tests/test_checkCFG.m @@ -76,3 +76,82 @@ function test_checkCfgBasic() checkSubFields(expectedStructure, cfg); end + +function expectedCfgStructure = returnExpectedCfgStructure() + + expectedCfgStructure.subject.subjectGrp = ''; + expectedCfgStructure.subject.sessionNb = 1; + expectedCfgStructure.subject.askGrpSess = [true true]; + + expectedCfgStructure.verbose = 0; + + expectedCfgStructure.fileName.task = ''; + expectedCfgStructure.fileName.zeroPadding = 3; + expectedCfgStructure.fileName.dateFormat = 'yyyymmddHHMM'; + + expectedCfgStructure.eyeTracker.do = false; + + expectedCfgStructure.mri.contrastEnhancement = []; + expectedCfgStructure.mri.phaseEncodingDirection = []; + expectedCfgStructure.mri.reconstruction = []; + expectedCfgStructure.mri.echo = []; + expectedCfgStructure.mri.acquisition = []; + expectedCfgStructure.mri.repetitionTime = []; + + expectedCfgStructure.bids.mri.RepetitionTime = []; + expectedCfgStructure.bids.mri.SliceTiming = ''; + expectedCfgStructure.bids.mri.TaskName = ''; + expectedCfgStructure.bids.mri.Instructions = ''; + expectedCfgStructure.bids.mri.TaskDescription = ''; + + expectedCfgStructure.bids.meg.TaskName = ''; + expectedCfgStructure.bids.meg.SamplingFrequency = []; + expectedCfgStructure.bids.meg.PowerLineFrequency = []; + expectedCfgStructure.bids.meg.DewarPosition = []; + expectedCfgStructure.bids.meg.SoftwareFilters = []; + expectedCfgStructure.bids.meg.DigitizedLandmarks = []; + expectedCfgStructure.bids.meg.DigitizedHeadPoints = []; + + expectedCfgStructure.bids.datasetDescription.Name = ''; + expectedCfgStructure.bids.datasetDescription.BIDSVersion = ''; + expectedCfgStructure.bids.datasetDescription.License = ''; + expectedCfgStructure.bids.datasetDescription.Authors = {''}; + expectedCfgStructure.bids.datasetDescription.Acknowledgements = ''; + expectedCfgStructure.bids.datasetDescription.HowToAcknowledge = ''; + expectedCfgStructure.bids.datasetDescription.Funding = {''}; + expectedCfgStructure.bids.datasetDescription.ReferencesAndLinks = {''}; + expectedCfgStructure.bids.datasetDescription.DatasetDOI = ''; + + expectedCfgStructure = orderfields(expectedCfgStructure); + +end + +function checkSubFields(expectedStructure, cfg) + % check that that the structures match + % if it fails it check from which subfield the error comes from + + try + + assertEqual(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 + + disp(expectedStructure); + disp(cfg); + + rethrow(ME); + end +end