Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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

<!-- TOC -->
Expand All @@ -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)
Expand Down
66 changes: 50 additions & 16 deletions manualTests/test_makeRawDataset.m
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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
3 changes: 2 additions & 1 deletion src/convertSourceToRaw.m
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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-.*$'));
Expand Down
24 changes: 22 additions & 2 deletions src/createFilename.m
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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

Expand Down
6 changes: 6 additions & 0 deletions src/saveEventsFile.m
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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

Expand Down Expand Up @@ -281,6 +283,7 @@ function printHeaderExtraColumns(logFile)
printExtraColumns(logFile, iEvent);

fprintf(logFile(1).fileID, '\n');
fprintf(1, '\n');

end
end
Expand All @@ -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
Expand Down
12 changes: 6 additions & 6 deletions src/subfun/createTaskName.m → src/subfun/createValidName.m
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
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
% should have the same name. Task label is derived from this field by
% removing all non alphanumeric ([a-zA-Z0-9]) characters.

% camel case: upper case for first letter for all words but the first one
spaceIdx = regexp(taskName, '[a-zA-Z0-9]*', 'start');
taskName(spaceIdx(2:end)) = upper(taskName(spaceIdx(2:end)));
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
2 changes: 1 addition & 1 deletion src/subfun/transferInfoToBids.m
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion tests/test_checkCFG.m
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,4 @@ function test_checkCfgBasic()

expectedCfgStructure = orderfields(expectedCfgStructure);

end
end
46 changes: 46 additions & 0 deletions tests/test_createFilename.m
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
10 changes: 5 additions & 5 deletions tests/test_createTaskName.m → tests/test_createValidName.m
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -12,7 +12,7 @@ function test_createTaskNameRemoveInvalidCharacters()

taskName = '&|@#-_(§!{})[]ù%£+/=:;.?,\<> visual task';

[taskName, taskNameValid] = createTaskName(taskName);
[~, taskNameValid] = createValidName(taskName);

[unvalidCharacters] = regexp(taskNameValid, '[^a-zA-Z0-9]');

Expand All @@ -22,15 +22,15 @@ function test_createTaskNameRemoveInvalidCharacters()
%% set up
taskName = ' 09 visual task';

[taskName, taskNameValid] = createTaskName(taskName);
[~, taskNameValid] = createValidName(taskName);

[unvalidCharacters] = regexp(taskNameValid, '[^a-zA-Z0-9]');

%% test
assertTrue(isempty(unvalidCharacters));

taskName = 'foo bar';
[taskName, taskNameValid] = createTaskName(taskName);
[taskName, taskNameValid] = createValidName(taskName);
assert(isequal(taskName, 'foo Bar'));
assert(isequal(taskNameValid, 'fooBar'));

Expand All @@ -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');
Expand Down