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
11 changes: 7 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
# Travis CI (https://travis-ci.org/)

language: c
dist: bionic
dist: bionic # based on bionic beaver ubuntu distribution
cache:
apt: true # only works with Pro version

env:
global:
global: # define environment variables for bash
- OCTFLAGS="--no-gui --no-window-system --silent"

before_install:
- travis_retry sudo apt-get -y -qq update
# install octave
- travis_retry sudo apt-get -y install octave
- travis_retry sudo apt-get -y install liboctave-dev
# install javascript
- travis_retry sudo apt-get -y install nodejs
- travis_retry sudo apt-get -y install npm
# install miss_hit linter
- cd .. && git clone https://github.com/florianschanda/miss_hit.git && export PATH=$PATH:`pwd`/miss_hit && cd CPP_BIDS


install:
# make octave file for JSONio
# make octave file the JSONio submodule
- cd lib/JSONio; mkoctfile --mex jsonread.c jsmn.c -DJSMN_PARENT_LINKS; cd ../..
- octave $OCTFLAGS --eval "addpath (pwd); savepath ();"
# Install BIDS-Validator
Expand All @@ -31,7 +34,7 @@ before_script:

jobs:
include:
- stage: "Tests"
- stage: "Tests"
name: "Unit and integration Tests"
script: octave $OCTFLAGS --eval "results = runTests; assert(all(~[results.Failed]))"
- stage: "BIDS validator"
Expand Down
147 changes: 120 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@
<!-- TOC -->

- [CPP_BIDS](#cpp_bids)
- [Output format](#output-format)
- [Modality agnostic aspect](#modality-agnostic-aspect)
- [Usage](#usage)
- [To save events.tsv file](#to-save-eventstsv-file)
- [Functions descriptions](#functions-descriptions)
- [userInputs](#userinputs)
- [createFilename](#createfilename)
- [saveEventsFile](#saveeventsfile)
- [checkCFG](#checkcfg)
- [CFG content](#cfg-content)
- [How to install](#how-to-install)
- [Use the matlab package manager](#use-the-matlab-package-manager)
- [Contributing](#contributing)
Expand All @@ -23,24 +26,42 @@

A set of function for matlab and octave to create [BIDS-compatible](https://bids-specification.readthedocs.io/en/stable/) folder structure and filenames for the output of behavioral, EEG, fMRI, eyetracking studies.

## Output format

## Modality agnostic aspect

Subjects, session and run number labels will be numbers with zero padding up to 3 values (e.g subject 1 will become `sub-001`).

A session folder will ALWAYS be created even if not requested (default will be `ses-001`).

Task labels will be printed in camelCase in the filenames.

Time stamps are added directly in the filename by adding a suffix `_date-YYYYMMDDHHMM` which makes the file name non-BIDS compliant. This was added to prevent overwriting files in case a certain run needs to be done a second time because of a crash (Some of us are paranoid about keeping even cancelled runs during my experiments). This suffix should be removed to make the data set BIDS compliant. See `convertSourceToRaw.m` for more details.

For example:

```
sub-090/ses-003/sub-090_ses-003_task-auditoryTask_run-023_events_date-202007291536.tsv
```

## Usage

### To save events.tsv file

```matlab

% define the folder where the data will be saved
expParameters.outputDir = fullfile(pwd, '..', 'output');
cfg.outputDir = fullfile(pwd, '..', 'output');

% define the name of the task
expParameters.task = 'testtask';
cfg.task = 'test task';

% can use the userInputs function to collect subject info
% expParameters = userInputs;
% cfg = userInputs;

% or declare it directly
expParameters.subjectNb = 1;
expParameters.runNb = 1;
cfg.subjectNb = 1;
cfg.runNb = 1;

% by default we assume you are running things on a behavioral PC with no eyetracker
% cfg.eyeTracker = false;
Expand All @@ -53,14 +74,14 @@ expParameters.runNb = 1;
% cfg.testingDevice = 'eeg';

% create the filenames: this include a step to check that all the information is there (checkCFG)
[cfg, expParameters] = createFilename(cfg, expParameters);
[cfg] = createFilename(cfg);

% initialize the events files with the typical BIDS columns (onsets, duration, trial_type)
% logFile = saveEventsFile('open', expParameters);
% logFile = saveEventsFile('open', cfg);

% You can add some more in this case (Speed and is_Fixation)
logFile.extraColumns = {'Speed', 'is_Fixation'};
logFile = saveEventsFile('open', expParameters, logFile);
logFile = saveEventsFile('open', cfg, logFile);

% The information about 2 events that we want to save
% NOTE : If the user DOES NOT provide `onset`, `trial_type`, this events will be skipped.
Expand All @@ -76,24 +97,24 @@ logFile(2,1).duration = 4;
logFile(2,1).is_Fixation = 3;

% add those 2 events to the events.tsv file
saveEventsFile('save', expParameters, logFile);
saveEventsFile('save', cfg, logFile);

% close the file
saveEventsFile('close', expParameters, logFile);
saveEventsFile('close', cfg, logFile);

```

If you want to save more complex events.tsv file you can save several columns at once.

```matlab
expParameters.subjectNb = 1;
expParameters.runNb = 1;
expParameters.task = 'testtask';
expParameters.outputDir = outputDir;
cfg.subjectNb = 1;
cfg.runNb = 1;
cfg.task = 'testtask';
cfg.outputDir = outputDir;

cfg.testingDevice = 'mri';

[cfg, expParameters] = createFilename(cfg, expParameters);
[cfg] = createFilename(cfg);

% You can specify how many columns we want for each variable
% will set 1 columns with name Speed
Expand All @@ -104,7 +125,7 @@ logFile.extraColumns.Speed.length = 1;
logFile.extraColumns.LHL24.length = 12;
logFile.extraColumns.is_Fixation.length = 1;

logFile = saveEventsFile('open', expParameters, logFile);
logFile = saveEventsFile('open', cfg, logFile);

logFile(1, 1).onset = 2;
logFile(end, 1).trial_type = 'motion_up';
Expand All @@ -113,9 +134,9 @@ logFile(end, 1).Speed = 2;
logFile(end, 1).is_Fixation = true;
logFile(end, 1).LHL24 = 1:12;

saveEventsFile('save', expParameters, logFile);
saveEventsFile('save', cfg, logFile);

saveEventsFile('close', expParameters, logFile);
saveEventsFile('close', cfg, logFile);

```

Expand All @@ -125,16 +146,16 @@ If you have many columns to define but only a few with several columns, you can
% define the extra columns: they will be added to the tsv files in the order the user input them
logFile.extraColumns = {'Speed', 'is_Fixation'};

[cfg, expParameters] = createFilename(cfg, expParameters);
[cfg] = createFilename(cfg);

% initialize the logFile variable
[logFile] = saveEventsFile('init', expParameters, logFile);
[logFile] = saveEventsFile('init', cfg, logFile);

% set the real length we really want
logFile.extraColumns.Speed.length = 12;

% open the file
logFile = saveEventsFile('open', expParameters, logFile);
logFile = saveEventsFile('open', cfg, logFile);
```


Expand All @@ -144,23 +165,23 @@ logFile = saveEventsFile('open', expParameters, logFile);

Get subject, run and session number and make sure they are positive integer values.

By default this will return `expParameters.session = 1` even if you asked it to omit enquiring about sessions. This means
By default this will return `cfg.subject.session = 1` even if you asked it to omit enquiring about sessions. This means
that the folder tree will always include a session folder.

```matlab
[expParameters] = userInputs(cfg, expParameters)
[cfg] = userInputs(cfg)
```

if you use it with `expParameters.askGrpSess = [0 0]`
if you use it with `cfg.subject.askGrpSess = [0 0]`
it won't ask you about group or session

if you use it with `expParameters.askGrpSess = [1]`
if you use it with `cfg.subject.askGrpSess = [1]`
it will only ask you about group

if you use it with `expParameters.askGrpSess = [0 1]`
if you use it with `cfg.subject.askGrpSess = [0 1]`
it will only ask you about session

if you use it with `expParameters.askGrpSess = [1 1]`
if you use it with `cfg.subject.askGrpSess = [1 1]`
it will ask you about both
this is the default

Expand Down Expand Up @@ -190,6 +211,74 @@ no value is provided.

Check that we have all the fields that we need in the experiment parameters.

## CFG content

```matlab
%% Can be modified by users
% but their effect might only be effective after running
% checkCFG

cfg.verbose = 0;

cfg.subject.subjectGrp = '';
cfg.subject.sessionNb = 1;
cfg.subject.askGrpSess = [true true];

% BOLD MRI details
% some of those will be transferred to the correct fields in cfg.bids by checkCFG
cfg.mri.repetitionTime = [];
cfg.mri.contrastEnhancement = [];
cfg.mri.phaseEncodingDirection = [];
cfg.mri.reconstruction = [];
cfg.mri.echo = [];
cfg.mri.acquisition = [];

cfg.fileName.task = '';
cfg.fileName.zeroPadding = 3; % amount of 0 padding the subject, session, run number

cfg.eyeTracker.do = false;

% content of the json side-car file for bold data
cfg.bids.mri.RepetitionTime = [];
cfg.bids.mri.SliceTiming = '';
cfg.bids.mri.TaskName = '';
cfg.bids.mri.Instructions = '';
cfg.bids.mri.TaskDescription = '';

% content of the json side-car file for MEG
cfg.bids.meg.TaskName = '';
cfg.bids.meg.SamplingFrequency = [];
cfg.bids.meg.PowerLineFrequency = [];
cfg.bids.meg.DewarPosition = [];
cfg.bids.meg.SoftwareFilters = [];
cfg.bids.meg.DigitizedLandmarks = [];
cfg.bids.meg.DigitizedHeadPoints = [];

% content of the datasetDescription.json file
cfg.bids.datasetDescription.Name = '';
cfg.bids.datasetDescription.BIDSVersion = '';
cfg.bids.datasetDescription.License = '';
cfg.bids.datasetDescription.Authors = {''};
cfg.bids.datasetDescription.Acknowledgements = '';
cfg.bids.datasetDescription.HowToAcknowledge = '';
cfg.bids.datasetDescription.Funding = {''};
cfg.bids.datasetDescription.ReferencesAndLinks = {''};
cfg.bids.datasetDescription.DatasetDOI = '';


%% Should not be modified by users
% many of those fields are set up by checkCFG and you might get non BIDS valid
% output if you touch those
cfg.fileName.dateFormat = 'yyyymmddHHMM'; % actual date of the experiment that is appended to the filename
cfg.fileName.modality
cgf.fileName.suffix.mri
cgf.fileName.suffix.meg
cfg.fileName.stim
cfg.fileName.events
cfg.fileName.datasetDescription

```

## How to install

### Use the matlab package manager
Expand Down Expand Up @@ -261,6 +350,10 @@ Here are the naming templates used.

`sub-<label>[_ses-<label>]_task-<label>[_run-<index>]_eeg.<manufacturer_specific_extension>`

- MEG

???

- Eyetracker

`sub-<participant_label>[_ses-<label>][_acq-<label>]_task-<task_label>_eyetrack.<manufacturer_specific_extension>`
Expand Down
2 changes: 2 additions & 0 deletions checkCFG.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
function cfg = checkCFG(cfg)
% cfg = checkCFG(cfg)
%
% check that we have all the fields that we need in the experiment parameters
% reuses a lot of code from the BIDS starter kit

Expand Down
6 changes: 4 additions & 2 deletions checkCppBidsDependencies.m
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
function checkCppBidsDependencies

function checkCppBidsDependencies()
% checkCppBidsDependencies()
%

pth = fileparts(mfilename('fullpath'));

checkSubmodule(fullfile(pth, 'lib', 'JSONio'));
Expand Down
8 changes: 8 additions & 0 deletions convertSourceToRaw.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
function convertSourceToRaw(cfg)
% convertSourceToRaw(cfg)
%
% attempts to convert a source dataset created with CPP_BIDS into a valid
% BIDS data set.
% - 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');
Expand Down
5 changes: 5 additions & 0 deletions createBoldJson.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
function createBoldJson(cfg)
% createBoldJson(cfg)
%
% Creates the side car JSON file for a BOLD functional run.
% This will only contain the minimum BIDS requirement and will likey be less
% complete than the info you could from DICOM conversion.

opts.Indent = ' ';

Expand Down
5 changes: 5 additions & 0 deletions createDataDictionary.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
function createDataDictionary(cfg, logFile)
% createDataDictionary(cfg, logFile)
%
% creates the data dictionary to be associated with a _events.tsv file
% will create empty field that you can then fill in manually in the JSON
% file

opts.Indent = ' ';

Expand Down
4 changes: 4 additions & 0 deletions createDatasetDescription.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
function createDatasetDescription(cfg)
% createDatasetDescription(cfg)
%
% creates the datasetDescription.json file that goes in the root of a BIDS
% dataset

opts.Indent = ' ';

Expand Down
2 changes: 1 addition & 1 deletion createFilename.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
%
% can work for behavioral experiment if cfg.device is set to 'PC'
% can work for fMRI experiment if cfg.device is set to 'scanner'
% can work for simple eyetracking data if cfg.eyeTracker is set to 1
% can work for simple eyetracking data if cfg.eyeTracker.do is set to 1
%
%
% See test_createFilename in the test folder for more details on how to use it.
Expand Down
4 changes: 3 additions & 1 deletion saveEventsFile.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
function [logFile] = saveEventsFile(action, cfg, logFile)
% [logFile] = saveEventsFile(action, cfg, logFile)
%
% Function to save output files for events that will be BIDS compliant.
%
% INPUTS
Expand Down Expand Up @@ -28,7 +30,7 @@
% This creates the header with the obligatory 'onset', 'trial_type', 'duration' required
% by BIDS and other columns can be specified in varargin.
%
% example : logFile = saveEventsFile('open', expParameters, [], 'direction', 'speed', 'target');
% example : logFile = saveEventsFile('open', cfg, [], 'direction', 'speed', 'target');
%
% - 'save': will save the data contained in logfile by using the file ID logFile.fileID;
% logfile must then contain:
Expand Down
Loading