diff --git a/README.md b/README.md index c840bb0c..152d1244 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,18 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-3-orange.svg?style=flat-square)](#contributors-) [![Build Status](https://travis-ci.com/cpp-lln-lab/CPP_BIDS.svg?branch=master)](https://travis-ci.com/cpp-lln-lab/CPP_BIDS) +**BIDS validator and linter** +[![Build Status](https://travis-ci.com/cpp-lln-lab/CPP_BIDS.svg?branch=master)](https://travis-ci.com/cpp-lln-lab/CPP_BIDS) + +**Unit tests** + +[![](https://img.shields.io/badge/Octave-CI-blue?logo=Octave&logoColor=white)](https://github.com/cpp-lln-lab/CPP_BIDS/actions) +![](https://github.com/cpp-lln-lab/CPP_BIDS/workflows/CI/badge.svg) + +**Contributors** + +[![All Contributors](https://img.shields.io/badge/all_contributors-3-orange.svg?style=flat-square)](#contributors-) + +--- + # CPP_BIDS @@ -18,6 +31,7 @@ - [How to install](#how-to-install) - [Download with git](#download-with-git) - [Add as a submodule](#add-as-a-submodule) + - [Example for submodule usage](#example-for-submodule-usage) - [Direct download](#direct-download) - [Contributing](#contributing) - [Guidestyle](#guidestyle) diff --git a/manualTests/test_makeRawDataset.m b/manualTests/test_makeRawDataset.m index 51b30262..9e4c8000 100644 --- a/manualTests/test_makeRawDataset.m +++ b/manualTests/test_makeRawDataset.m @@ -7,38 +7,35 @@ function test_makeRawDataset() end %% set up - - %%% set up - - cfg.subject.subjectNb = 1; - cfg.subject.runNb = 1; - - cfg.task.name = 'testtask'; - 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.bids.mri.RepetitionTime = 1.56; - cfg.testingDevice = 'mri'; + %% MRI task data + cfg.mri.repetitionTime = 1.56; + + cfg.subject.subjectNb = 1; + cfg.subject.runNb = 1; + + cfg.task.name = 'testtask'; + logFile.extraColumns.Speed.length = 1; logFile.extraColumns.LHL24.length = 3; logFile.extraColumns.is_Fixation.length = 1; - %%% do stuff - cfg = createFilename(cfg); - % create the events file and header - logFile = saveEventsFile('open', cfg, logFile); - createBoldJson(cfg); + createDatasetDescription(cfg); + % create the events file and header + logFile = saveEventsFile('open', cfg, logFile); + % ROW 2: normal events : all info is there logFile(1, 1).onset = 2; logFile(end, 1).trial_type = 'MotionUp'; @@ -74,11 +71,48 @@ function test_makeRawDataset() % add dummy functional data 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(funcDir, boldFilename)); - %% + %% MRI bold rest data and fancy suffixes + clear cfg; + + cfg.dir.output = outputDir; + + cfg.testingDevice = 'mri'; + + cfg.subject.subjectNb = 2; + cfg.subject.sessionNb = 3; + cfg.subject.runNb = 4; + + cfg.mri.reconstruction = 'fast recon'; + cfg.mri.contrastEnhancement = 'test'; + cfg.mri.phaseEncodingDirection = 'y pos'; + cfg.mri.echo = '1'; + cfg.mri.acquisition = ' new tYpe'; + cfg.mri.repetitionTime = 1.56; + + cfg.task.name = 'rest'; + + cfg = createFilename(cfg); + createBoldJson(cfg); + + % add dummy functional data + funcDir = fullfile(cfg.dir.output, 'source', 'sub-002', 'ses-003', 'func'); + boldFilename = ['sub-002_ses-003_task-rest', ... + '_acq-newTYpe_ce-test_dir-yPos_rec-fastRecon', ... + '_run-004_echo-1_bold.nii.gz']; + + copyfile( ... + fullfile('..', 'dummyData', 'dummyData.nii.gz'), ... + fullfile(funcDir, boldFilename)); + + %% + clear; + outputDir = fullfile(fileparts(mfilename('fullpath')), 'output'); + cfg.dir.output = outputDir; convertSourceToRaw(cfg); end diff --git a/src/convertSourceToRaw.m b/src/convertSourceToRaw.m index bc73606a..28e228a3 100644 --- a/src/convertSourceToRaw.m +++ b/src/convertSourceToRaw.m @@ -11,7 +11,7 @@ function convertSourceToRaw(cfg) sourceDir = fullfile(cfg.dir.output, 'source'); rawDir = fullfile(cfg.dir.output, 'rawdata'); - % add dummy readme and change file + % add dummy README and CHANGE file copyfile(fullfile( ... fileparts(mfilename('fullpath')), '..', 'dummyData', 'README'), ... sourceDir); @@ -28,6 +28,7 @@ function convertSourceToRaw(cfg) error('No subjects found in BIDS directory.'); end + % go through the subject files and parses them to remove the date suffix for su = 1:numel(subjects) sess = cellstr(file_utils('List', fullfile(rawDir, subjects{su}), 'dir', '^ses-.*$')); diff --git a/src/createFilename.m b/src/createFilename.m index 8f73628a..27a1f833 100644 --- a/src/createFilename.m +++ b/src/createFilename.m @@ -1,4 +1,6 @@ function cfg = createFilename(cfg) + % cfg = createFilename(cfg) + % % create the BIDS compliant directories and fileNames for the behavioral output % for this subject / session / run using the information from cfg and expParameters. % Will also create the right fileName for the eyetracking data file. @@ -99,21 +101,39 @@ cfg.fileName.suffix.run = ['_run-' sprintf(cfg.fileName.pattern, cfg.subject.runNb)]; + %% MRI % set values for the suffixes for the different fields in the BIDS name fields2Check = { ... + 'acquisition', ... 'contrastEnhancement', ... + 'echo', ... 'phaseEncodingDirection', ... 'reconstruction', ... + }; + + targetFields = { ... + 'acq', ... + 'ce', ... 'echo', ... - 'acquisition' + 'dir', ... + 'rec', ... }; for iField = 1:numel(fields2Check) + if isempty (cfg.mri.(fields2Check{iField})) %#ok<*GFLD> + cfg.fileName.suffix.mri.(fields2Check{iField}) = ''; %#ok<*SFLD> + else + + % upper camelCase and remove invalid characters + thisField = getfield(cfg.mri, fields2Check{iField}); + [~, validFieldName] = createValidName(thisField); + cfg.fileName.suffix.mri.(fields2Check{iField}) = ... - ['_' fields2Check{iField} '-' getfield(cfg.mri, fields2Check{iField})]; + ['_' targetFields{iField} '-' validFieldName]; + end end diff --git a/src/saveEventsFile.m b/src/saveEventsFile.m index 5f7388e9..1960359c 100644 --- a/src/saveEventsFile.m +++ b/src/saveEventsFile.m @@ -152,6 +152,7 @@ % print the basic BIDS columns fprintf(logFile.fileID, '%s\t%s\t%s', 'onset', 'duration', 'trial_type'); + fprintf(1, '%s\t%s\t%s', 'onset', 'duration', 'trial_type'); printHeaderExtraColumns(logFile); @@ -174,6 +175,7 @@ function printHeaderExtraColumns(logFile) headerName = returnHeaderName(namesExtraColumns{iExtraColumn}, nbCol, iCol); fprintf(logFile.fileID, '\t%s', headerName); + fprintf(1, '\t%s', headerName); end @@ -281,6 +283,7 @@ function printHeaderExtraColumns(logFile) printExtraColumns(logFile, iEvent); fprintf(logFile(1).fileID, '\n'); + fprintf(1, '\n'); end end @@ -307,12 +310,15 @@ function printData(output, data) % for numeric data we replace any nan by n/a if ischar(data) fprintf(output, '%s\t', data); + fprintf(1, '%s\t', data); else for i = 1:numel(data) if isnan(data(i)) fprintf(output, '%s\t', 'n/a'); + fprintf(1, '%s\t', 'n/a'); else fprintf(output, '%f\t', data(i)); + fprintf(1, '%f\t', data(i)); end end end diff --git a/src/subfun/createTaskName.m b/src/subfun/createValidName.m similarity index 55% rename from src/subfun/createTaskName.m rename to src/subfun/createValidName.m index 6b3fd62b..ef216ef1 100644 --- a/src/subfun/createTaskName.m +++ b/src/subfun/createValidName.m @@ -1,4 +1,4 @@ -function [taskName, taskNameValid] = createTaskName(taskName) +function [name, nameValid] = createValidName(name) % [taskName, taskNameValid] = createTaskName(taskName) % % Name of the task (for resting state use the "rest" prefix). No two tasks @@ -6,12 +6,12 @@ % removing all non alphanumeric ([a-zA-Z0-9]) characters. % camel case: upper case for first letter for all words but the first one - spaceIdx = regexp(taskName, '[a-zA-Z0-9]*', 'start'); - taskName(spaceIdx(2:end)) = upper(taskName(spaceIdx(2:end))); + spaceIdx = regexp(name, '[a-zA-Z0-9]*', 'start'); + name(spaceIdx(2:end)) = upper(name(spaceIdx(2:end))); % remove invalid characters - [unvalidCharacters] = regexp(taskName, '[^a-zA-Z0-9]'); - taskNameValid = taskName; - taskNameValid(unvalidCharacters) = []; + [unvalidCharacters] = regexp(name, '[^a-zA-Z0-9]'); + nameValid = name; + nameValid(unvalidCharacters) = []; end diff --git a/src/subfun/transferInfoToBids.m b/src/subfun/transferInfoToBids.m index 311a36da..ab00c0e7 100644 --- a/src/subfun/transferInfoToBids.m +++ b/src/subfun/transferInfoToBids.m @@ -5,7 +5,7 @@ % relevant field for its reuse for BIDS filenames or JSON later if isfield(cfg, 'task') && isfield(cfg.task, 'name') - [taskName, taskNameValid] = createTaskName(cfg.task.name); + [taskName, taskNameValid] = createValidName(cfg.task.name); fieldsToSet.fileName.task = taskNameValid; fieldsToSet.bids.meg.TaskName = taskName; fieldsToSet.bids.mri.TaskName = taskName; diff --git a/tests/test_checkCFG.m b/tests/test_checkCFG.m index 6ac50b99..3ed0d09f 100644 --- a/tests/test_checkCFG.m +++ b/tests/test_checkCFG.m @@ -124,4 +124,4 @@ function test_checkCfgBasic() expectedCfgStructure = orderfields(expectedCfgStructure); -end \ No newline at end of file +end diff --git a/tests/test_createFilename.m b/tests/test_createFilename.m index 772a536a..c7615604 100644 --- a/tests/test_createFilename.m +++ b/tests/test_createFilename.m @@ -94,6 +94,52 @@ function test_createFilenameMriEyetracker() end +function test_createFilenameMriSuffix() + + outputDir = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); + + %% set up + + cfg.verbose = false; + cfg.subject.subjectGrp = 'ssri'; + cfg.subject.subjectNb = 3; + cfg.subject.sessionNb = 4; + cfg.subject.runNb = 5; + cfg.task.name = 'rest'; + cfg.dir.output = outputDir; + + cfg.eyeTracker.do = false; + cfg.testingDevice = 'mri'; + + cfg.mri.reconstruction = 'fast recon'; + cfg.mri.contrastEnhancement = 'test'; + cfg.mri.phaseEncodingDirection = 'y pos'; + cfg.mri.echo = '1'; + cfg.mri.acquisition = ' new tYpe'; + + cfg = createFilename(cfg); + + %% data to test against + + funcDir = fullfile(outputDir, 'source', 'sub-ssri003', 'ses-004', 'func'); + + baseFilename = 'sub-ssri003_ses-004_task-rest'; + + eventFilename = ['sub-ssri003_ses-004_task-rest', ... + '_acq-newTYpe_ce-test_dir-yPos_rec-fastRecon', ... + '_run-005_echo-1_events_date-' ... + cfg.fileName.date '.tsv']; + + %% tests + % make sure the func dir is created + assertTrue(exist(funcDir, 'dir') == 7); + + % make sure the right filenames are created + assertEqual(cfg.fileName.base, baseFilename); + assertEqual(cfg.fileName.events, eventFilename); + +end + function test_createFilenameEeg() outputDir = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); diff --git a/tests/test_createTaskName.m b/tests/test_createValidName.m similarity index 77% rename from tests/test_createTaskName.m rename to tests/test_createValidName.m index f3aac9a4..d191c0ac 100644 --- a/tests/test_createTaskName.m +++ b/tests/test_createValidName.m @@ -1,4 +1,4 @@ -function test_suite = test_createTaskName %#ok<*STOUT> +function test_suite = test_createValidName %#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 @@ -12,7 +12,7 @@ function test_createTaskNameRemoveInvalidCharacters() taskName = '&|@#-_(§!{})[]ù%£+/=:;.?,\<> visual task'; - [taskName, taskNameValid] = createTaskName(taskName); + [~, taskNameValid] = createValidName(taskName); [unvalidCharacters] = regexp(taskNameValid, '[^a-zA-Z0-9]'); @@ -22,7 +22,7 @@ function test_createTaskNameRemoveInvalidCharacters() %% set up taskName = ' 09 visual task'; - [taskName, taskNameValid] = createTaskName(taskName); + [~, taskNameValid] = createValidName(taskName); [unvalidCharacters] = regexp(taskNameValid, '[^a-zA-Z0-9]'); @@ -30,7 +30,7 @@ function test_createTaskNameRemoveInvalidCharacters() assertTrue(isempty(unvalidCharacters)); taskName = 'foo bar'; - [taskName, taskNameValid] = createTaskName(taskName); + [taskName, taskNameValid] = createValidName(taskName); assert(isequal(taskName, 'foo Bar')); assert(isequal(taskNameValid, 'fooBar')); @@ -40,7 +40,7 @@ function test_createTaskNameCamelCase() %% set up taskName = 'foo bar'; - [taskName, taskNameValid] = createTaskName(taskName); + [taskName, taskNameValid] = createValidName(taskName); %% test assertEqual(taskName, 'foo Bar');