diff --git a/.github/workflows/check_markdown.yml b/.github/workflows/check_markdown.yml new file mode 100644 index 00000000..1b997e3d --- /dev/null +++ b/.github/workflows/check_markdown.yml @@ -0,0 +1,32 @@ +name: Check Markdown + +on: + push: + branches: + - master + pull_request: + branches: '*' + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + + - uses: actions/checkout@v2 + with: + submodules: true + fetch-depth: 1 + + - uses: actions/setup-node@v2 + with: + node-version: '10' + + - name: Install dependencies and check markdown + run: | + npm install `cat npm-requirements.txt` + npx remark *.md --frail + npx remark ./docs/ --frail + npx remark ./demos/ --frail + npx remark ./tests/ --frail diff --git a/.github/workflows/check_md_links.yml b/.github/workflows/check_md_links.yml index ac21eb73..560a509e 100644 --- a/.github/workflows/check_md_links.yml +++ b/.github/workflows/check_md_links.yml @@ -6,7 +6,6 @@ on: push: branches: - master - - dev pull_request: branches: '*' diff --git a/.github/workflows/miss_hit.yml b/.github/workflows/miss_hit.yml index 4603893a..5c5e561d 100644 --- a/.github/workflows/miss_hit.yml +++ b/.github/workflows/miss_hit.yml @@ -4,7 +4,6 @@ on: push: branches: - master - - dev pull_request: branches: '*' @@ -28,12 +27,16 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip setuptools - pip3 install miss_hit + pip3 install -r requirements.txt - - name: Miss_hit code quality + - name: MISS_HIT Code style run: | - mh_metric . --ci + mh_style --process-slx - - name: Miss_hit code style + - name: MISS_HIT Metrics run: | - mh_style . + mh_metric --ci + + - name: MISS_HIT Bug finder + run: | + mh_lint diff --git a/.github/workflows/run_system_tests.yml b/.github/workflows/run_system_tests.yml new file mode 100644 index 00000000..88c96be0 --- /dev/null +++ b/.github/workflows/run_system_tests.yml @@ -0,0 +1,75 @@ +name: system tests + +# Uses the cron schedule for github actions +# +# https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows#scheduled-events +# +# ┌───────────── minute (0 - 59) +# │ ┌───────────── hour (0 - 23) +# │ │ ┌───────────── day of the month (1 - 31) +# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC) +# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT) +# │ │ │ │ │ +# │ │ │ │ │ +# │ │ │ │ │ +# * * * * * + +on: + push: + branches: + - master + - main + pull_request: + branches: + - 'master' + - 'main' + schedule: + - cron: "* * 1 * *" + +env: + OCTFLAGS: --no-gui --no-window-system --silent + +jobs: + build: + + runs-on: ubuntu-20.04 + + steps: + + - name: Install dependencies + run: | + sudo apt-get -y -qq update + sudo apt-get -y install octave liboctave-dev + sudo apt-get -y install nodejs npm + + - name: Clone cpp_spm + uses: actions/checkout@v2 + with: + submodules: true + fetch-depth: 2 + + - name: Install SPM + run: | + git clone https://github.com/spm/spm12.git --depth 1 + make -C spm12/src PLATFORM=octave distclean + make -C spm12/src PLATFORM=octave + make -C spm12/src PLATFORM=octave install + octave $OCTFLAGS --eval "addpath(fullfile(pwd, 'spm12')); savepath();" + + - name: Update octave path + run: | + octave $OCTFLAGS --eval "addpath(genpath(fullfile(pwd, 'lib'))); savepath();" + octave $OCTFLAGS --eval "addpath(genpath(fullfile(pwd, 'src'))); savepath();" + + - name: Prepare data + run: | + output_folder='demos/MoAE/output/' + mkdir $output_folder + curl http://www.fil.ion.ucl.ac.uk/spm/download/data/MoAEpilot/MoAEpilot.bids.zip --output $output_folder'MoAEpilot.zip' + unzip $output_folder'MoAEpilot.zip' -d $output_folder + + - name: Run system tests + run: | + cd demos/MoAE + octave $OCTFLAGS --eval "MoAEpilot_run" + diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml new file mode 100644 index 00000000..f45c7522 --- /dev/null +++ b/.github/workflows/run_tests.yml @@ -0,0 +1,83 @@ +name: tests and coverage + +# Uses the cron schedule for github actions +# +# https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows#scheduled-events +# +# ┌───────────── minute (0 - 59) +# │ ┌───────────── hour (0 - 23) +# │ │ ┌───────────── day of the month (1 - 31) +# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC) +# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT) +# │ │ │ │ │ +# │ │ │ │ │ +# │ │ │ │ │ +# * * * * * + +on: + push: + branches: + - dev + pull_request: + branches: '*' + schedule: + - cron: "* * 1 * *" + +env: + OCTFLAGS: --no-gui --no-window-system --silent + +jobs: + build: + + runs-on: ubuntu-20.04 + + steps: + + - name: Install dependencies + run: | + sudo apt-get -y -qq update + sudo apt-get -y install octave liboctave-dev + sudo apt-get -y install nodejs npm + + - name: Clone cpp_spm + uses: actions/checkout@v2 + with: + submodules: true + fetch-depth: 2 + + - name: Install SPM + run: | + git clone https://github.com/spm/spm12.git --depth 1 + make -C spm12/src PLATFORM=octave distclean + make -C spm12/src PLATFORM=octave + make -C spm12/src PLATFORM=octave install + octave $OCTFLAGS --eval "addpath(fullfile(pwd, 'spm12')); savepath();" + + - name: Install Moxunit and MOcov + run: | + git clone https://github.com/MOxUnit/MOxUnit.git --depth 1 + make -C MOxUnit install + git clone https://github.com/MOcov/MOcov.git --depth 1 + make -C MOcov install + + - name: Update octave path + run: | + octave $OCTFLAGS --eval "addpath(genpath(fullfile(pwd, 'lib'))); savepath();" + octave $OCTFLAGS --eval "addpath(genpath(fullfile(pwd, 'src'))); savepath();" + + - name: Prepare data + run: | + output_folder='demos/MoAE/output/' + mkdir $output_folder + curl http://www.fil.ion.ucl.ac.uk/spm/download/data/MoAEpilot/MoAEpilot.bids.zip --output $output_folder'MoAEpilot.zip' + unzip $output_folder'MoAEpilot.zip' -d $output_folder + cd tests + sh createDummyDataSet.sh + cd .. + + - name: Run tests + run: | + octave $OCTFLAGS --eval "runTests" + cat test_report.log | grep 0 + bash <(curl -s https://codecov.io/bash) + diff --git a/.gitignore b/.gitignore index 8d7cf746..910d5fa3 100644 --- a/.gitignore +++ b/.gitignore @@ -9,23 +9,31 @@ *.zip *.ps -options_task-*.json +options_task-*date*.json onsets*_events.mat # files in the demo folder related to running the demo analysis -demos/MoAE/*.zip +demos/*/*.zip +demos/*/derivatives/* demos/MoAE/output/* -demos/MoAE/derivatives/* +demos/spm*/raw +demos/spm*/source -# test folder +# test folder and dummy data tests/sub-01/* tests/group/* +tests/models/*.json +tests/dummyData/derivatives/cpp_spm/sub-*/*/*/*.nii* +tests/dummyData/derivatives/cpp_spm/sub-*/*/*/*.tsv +tests/dummyData/derivatives/cpp_spm/sub-*/*/*/*.txt +tests/dummyData/derivatives/cpp_spm/sub-*/*/*/*.json +tests/dummyData/derivatives/cpp_spm/sub-*/stats/*/*/*.nii* # ignore content of the build folder of the doc docs/build/* # ignore virtual env -cpp_bids_spm/* +cpp_spm/* # visual studio code stuff .vscode diff --git a/.gitmodules b/.gitmodules index b939c69b..4fd1373b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "lib/bids-matlab"] path = lib/bids-matlab - url = https://github.com/cpp-lln-lab/bids-matlab.git + url = https://github.com/bids-standard/bids-matlab.git [submodule "lib/spmup"] path = lib/spmup url = https://github.com/CPernet/spmup.git diff --git a/.readthedocs.yml b/.readthedocs.yml index 6f095f08..ec0951f4 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -23,4 +23,4 @@ formats: python: version: 3.7 install: - - requirements: docs/requirements.txt \ No newline at end of file + - requirements: requirements.txt \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 63e8b15f..00000000 --- a/.travis.yml +++ /dev/null @@ -1,105 +0,0 @@ -# Travis CI (https://travis-ci.org/) -# This will only work on your repo if you have an account on travis and you -# have set it up to run continuous integration on this this repo - -dist: bionic - -language: node_js -node_js: - - "10" - -cache: - apt: true - directories: - - node_modules - -# only run the CI for those branches -branches: - only: - - master - - dev - -env: - global: - - OCTFLAGS="--no-gui --no-window-system --silent" - -# TODO maybe this could be refactored as this is not needed to check the -# the markdown linting -install: - # install octave - - travis_retry sudo apt-get -y -qq update - - travis_retry sudo apt-get -y install octave - - travis_retry sudo apt-get -y install liboctave-dev - - # install SPM and the relevant patches for octave - - git clone https://github.com/spm/spm12.git --depth 1 - - make -C spm12/src PLATFORM=octave distclean - - make -C spm12/src PLATFORM=octave - - make -C spm12/src PLATFORM=octave install - - # update octave path - - octave $OCTFLAGS --eval "addpath(genpath(fullfile(pwd, 'lib'))); savepath();" - - octave $OCTFLAGS --eval "addpath(genpath(fullfile(pwd, 'src'))); savepath();" - - octave $OCTFLAGS --eval "addpath(fullfile(pwd, 'spm12')); savepath();" - -jobs: - include: - - #------------------------------------------------------------------------- - # first job - #------------------------------------------------------------------------- - - name: "Unit tests and coverage" - - before_script: - - # install MOX unit - - git clone https://github.com/MOxUnit/MOxUnit.git --depth 1 - - cd MOxUnit - - make install - - cd .. - - # install MOcov - - git clone https://github.com/MOcov/MOcov.git --depth 1 - - cd MOcov - - make install - - cd .. - - # get data - - output_folder='demos/MoAE/output/' - - mkdir $output_folder - - curl http://www.fil.ion.ucl.ac.uk/spm/download/data/MoAEpilot/MoAEpilot.bids.zip --output $output_folder'MoAEpilot.zip' - - unzip $output_folder'MoAEpilot.zip' -d $output_folder - - script: - - octave $OCTFLAGS --eval "runTests" - - cat test_report.log | grep 0 - - # to send the results to codecov to get our code coverage - after_success: - - bash <(curl -s https://codecov.io/bash) - - #------------------------------------------------------------------------- - # second job - #------------------------------------------------------------------------- - - name: "Check markdown" - - before_script: - # install node.js dependencies - - npm install `cat npm-requirements.txt` - - script: - - remark *.md --frail - - remark ./demos/ --frail - - remark ./docs/ --frail - - remark ./tests/ --frail - - #------------------------------------------------------------------------- - # third job - #------------------------------------------------------------------------- - - name: "Run demo" - - if: branch = master - - script: - - cd demos/MoAE - - octave $OCTFLAGS --eval "MoAEpilot_run" \ No newline at end of file diff --git a/.zenodo.json b/.zenodo.json new file mode 100644 index 00000000..d6a937ec --- /dev/null +++ b/.zenodo.json @@ -0,0 +1,52 @@ +{ + "title": "CPP_SPM: Octave/MATLAB tools for analyzing BIDS datasets with SPM", + "description": "CPP_SPM is a set pipelines and tools for Octave/MATLAB to process and analyze BIDS data sets using SPM.", + "creators": [ + { + "affiliation": "Université Catholique de Louvain", + "name": "Gau, Rémi", + "orcid": "0000-0002-1535-9767" + }, + { + "affiliation": "Université Catholique de Louvain", + "name": "Barilari, Marco", + "orcid": "0000-0002-3313-3120" + }, + { + "affiliation": "Université Catholique de Louvain", + "name": "Battal, Ceren", + "orcid": "0000-0002-9844-7630" + }, + { + "affiliation": "Université Catholique de Louvain", + "name": "Rezk, Mohamed", + "orcid": "0000-0002-1866-8645" + }, + { + "affiliation": "Université Catholique de Louvain", + "name": "Collignon, Olivier", + "orcid": "0000-0003-1882-3550" + }, + { + "affiliation": "Université Catholique de Louvain", + "name": "Gurtubay, Ane", + "orcid": "0000-0003-3824-2219" + }, + { + "affiliation": "Université Catholique de Louvain", + "name": "Falagiarda, Federica", + "orcid": "0000-0001-7844-1605" + } + ], + "keywords": [ + "BIDS", + "brain imaging data structure", + "neuroscience", + "neuroimaging", + "MATLAB", + "Octave", + "SPM" + ], + "license": "GPL-3", + "upload_type": "software" +} diff --git a/README.md b/README.md index 1063d0bf..4f90d642 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ +**Documentation** + +[![Documentation Status: stable](https://readthedocs.org/projects/cpp-bids-spm/badge/?version=stable)](https://cpp-bids-spm.readthedocs.io/en/stable/?badge=stable) + **Code quality and style** [![](https://img.shields.io/badge/Octave-CI-blue?logo=Octave&logoColor=white)](https://github.com/cpp-lln-lab/CPP_BIDS_SPM_pipeline/actions) @@ -8,7 +12,7 @@ **Unit tests and coverage** [![Build Status](https://travis-ci.com/cpp-lln-lab/CPP_BIDS_SPM_pipeline.svg?branch=master)](https://travis-ci.com/cpp-lln-lab/CPP_BIDS_SPM_pipeline) -[![codecov](https://codecov.io/gh/cpp-lln-lab/CPP_BIDS_SPM_pipeline/branch/master/graph/badge.svg)](https://codecov.io/gh/cpp-lln-lab/CPP_BIDS_SPM_pipeline) +[![codecov](https://codecov.io/gh/Remi-Gau/CPP_SPM/branch/master/graph/badge.svg?token=8IoRQtbFUV)](https://codecov.io/gh/Remi-Gau/CPP_SPM) **How to cite** @@ -22,7 +26,7 @@ -# CPPL SPM12 Pipeline +# CPP SPM This is a set of functions to fMRI analysis on a [BIDS data set](https://bids.neuroimaging.io/) using SPM12. @@ -78,6 +82,17 @@ For instructions see the following links: +## Contributing + +Feel free to open issues to report a bug and ask for improvements. + +If you want to contribute, have a look at our +[contributing guidelines](https://github.com/cpp-lln-lab/.github/blob/main/CONTRIBUTING.md) +that are meant to guide you and help you get started. If something is not clear +or you get stuck: it is more likely we did not do good enough a job at +explaining things. So do not hesitate to open an issue, just to ask for +clarification. + ## Contributors Thanks goes to these wonderful people diff --git a/demos/MoAE/MoAEpilot_getOption.m b/demos/MoAE/MoAEpilot_getOption.m index dfe42339..fc448180 100644 --- a/demos/MoAE/MoAEpilot_getOption.m +++ b/demos/MoAE/MoAEpilot_getOption.m @@ -4,9 +4,7 @@ % returns a structure that contains the options chosen by the user to run % slice timing correction, pre-processing, FFX, RFX. - if nargin < 1 - opt = []; - end + opt = []; % task to analyze opt.taskName = 'auditory'; @@ -15,27 +13,57 @@ opt.dataDir = fullfile(fileparts(mfilename('fullpath')), 'output', 'MoAEpilot'); opt.derivativesDir = fullfile(fileparts(mfilename('fullpath'))); + % Uncomment the lines below to run preprocessing + % - don't use realign and unwarp + % opt.realign.useUnwarp = false; + % - in "native" space: don't do normalization + % opt.space = 'individual'; + opt.model.file = fullfile(fileparts(mfilename('fullpath')), ... 'models', 'model-MoAE_smdl.json'); + % to add the hrf temporal derivative = [1 0] + % to add the hrf temporal and dispersion derivative = [1 1] + % opt.model.hrfDerivatives = [0 0]; + + % Specify the result to compute + opt.result.Steps(1) = returnDefaultResultsStructure(); + + opt.result.Steps(1).Level = 'subject'; + + opt.result.Steps(1).Contrasts(1).Name = 'listening'; + + % For each contrats, you can adapt: + % - voxel level (p) + % - cluster (k) level threshold + % - type of multiple comparison: + % - 'FWE' is the defaut + % - 'FDR' + % - 'none' + % + % opt.result.Steps(1).Contrasts(2).Name = 'listening_inf_baseline'; + % opt.result.Steps(1).Contrasts(2).MC = 'none'; + % opt.result.Steps(1).Contrasts(2).p = 0.01; + % opt.result.Steps(1).Contrasts(2).k = 0; + + % Specify how you want your output (all the following are on false by default) + opt.result.Steps(1).Output.png = true(); + + opt.result.Steps(1).Output.csv = true(); + + opt.result.Steps(1).Output.thresh_spm = true(); + + opt.result.Steps(1).Output.binary = true(); + + % MONTAGE FIGURE OPTIONS + opt.result.Steps(1).Output.montage.do = true(); + opt.result.Steps(1).Output.montage.slices = -8:3:15; % in mm + % axial is default 'sagittal', 'coronal' + opt.result.Steps(1).Output.montage.orientation = 'axial'; + % will use the MNI T1 template by default but the underlay image can be changed. + opt.result.Steps(1).Output.montage.background = ... + fullfile(spm('dir'), 'canonical', 'avg152T1.nii,1'); - % specify the result to compute - opt.result.Steps(1) = struct( ... - 'Level', 'subject', ... - 'Contrasts', struct( ... - 'Name', 'listening', ... % has to match - 'Mask', false, ... - 'MC', 'FWE', ... FWE, none, FDR - 'p', 0.05, ... - 'k', 0, ... - 'NIDM', true)); - - opt.result.Steps(1).Contrasts(2) = struct( ... - 'Name', 'listening_inf_baseline', ... - 'Mask', false, ... - 'MC', 'none', ... FWE, none, FDR - 'p', 0.01, ... - 'k', 0, ... - 'NIDM', true); + opt.result.Steps(1).Output.NIDM_results = true(); %% DO NOT TOUCH opt = checkOptions(opt); diff --git a/demos/MoAE/MoAEpilot_run.m b/demos/MoAE/MoAEpilot_run.m index 1d9a065f..0d56f37b 100644 --- a/demos/MoAE/MoAEpilot_run.m +++ b/demos/MoAE/MoAEpilot_run.m @@ -25,15 +25,14 @@ %% Set options opt = MoAEpilot_getOption(); -% Uncomment the line below to run preprocessing in "native" space. -% - use realign and unwarp -% - don't do normalization -opt.space = 'individual'; - %% Get data -fprintf('%-40s:', 'Downloading dataset...'); +fprintf('%-10s:', 'Downloading dataset...'); urlwrite(URL, 'MoAEpilot.zip'); +fprintf(1, ' Done\n\n'); + +fprintf('%-10s:', 'Unzipping dataset...'); unzip('MoAEpilot.zip', fullfile(WD, 'output')); +fprintf(1, ' Done\n\n'); checkDependencies(); @@ -50,9 +49,9 @@ bidsSpatialPrepro(opt); % The following do not run on octave for now (because of spmup) -% anatomicalQA(opt); -% bidsResliceTpmToFunc(opt); -% functionalQA(opt); +anatomicalQA(opt); +bidsResliceTpmToFunc(opt); +functionalQA(opt); bidsSmoothing(FWHM, opt); diff --git a/demos/MoAE/options_task-auditory.json b/demos/MoAE/options_task-auditory.json new file mode 100644 index 00000000..00982156 --- /dev/null +++ b/demos/MoAE/options_task-auditory.json @@ -0,0 +1,50 @@ +{ + "STC_referenceSlice": [], + "anatReference": { + "type": "T1w", + "session": 1 + }, + "contrastList": [], + "dataDir": "/home/remi/github/CPP_SPM/demos/MoAE/output/MoAEpilot", + "derivativesDir": "/home/remi/github/CPP_SPM/demos/MoAE/derivatives/default", + "funcVoxelDims": [], + "groups": [""], + "model": { + "file": "/home/remi/github/CPP_SPM/demos/MoAE/models/model-MoAE_smdl.json" + }, + "realign": { + "useUnwarp": true + }, + "result": { + "Steps": { + "Contrasts": [ + { + "MC": "FWE", + "Mask": false, + "NIDM": true, + "Name": "listening", + "k": 0, + "p": 0.05 + }, + { + "MC": "none", + "Mask": false, + "NIDM": true, + "Name": "listening_inf_baseline", + "k": 0, + "p": 0.01 + } + ], + "Level": "subject" + } + }, + "skullstrip": { + "threshold": 0.75 + }, + "sliceOrder": [], + "space": "MNI", + "subjects": [[]], + "taskName": "auditory", + "useFieldmaps": true, + "zeropad": 2 +} \ No newline at end of file diff --git a/demos/MoAE/options_task-auditory_space-individual.json b/demos/MoAE/options_task-auditory_space-individual.json new file mode 100644 index 00000000..346b482b --- /dev/null +++ b/demos/MoAE/options_task-auditory_space-individual.json @@ -0,0 +1,48 @@ +{ + "STC_referenceSlice": [], + "anatReference": { + "type": "T1w", + "session": 1 + }, + "contrastList": [], + "dataDir": "/home/remi/github/CPP_SPM/demos/MoAE/output/MoAEpilot", + "derivativesDir": "/home/remi/github/CPP_SPM/demos/MoAE/derivatives/native", + "funcVoxelDims": [], + "model": { + "file": "/home/remi/github/CPP_SPM/demos/MoAE/models/model-MoAE_smdl.json" + }, + "realign": { + "useUnwarp": true + }, + "result": { + "Steps": { + "Contrasts": [ + { + "MC": "FWE", + "Mask": false, + "NIDM": true, + "Name": "listening", + "k": 0, + "p": 0.05 + }, + { + "MC": "none", + "Mask": false, + "NIDM": true, + "Name": "listening_inf_baseline", + "k": 0, + "p": 0.01 + } + ], + "Level": "subject" + } + }, + "skullstrip": { + "threshold": 0.75 + }, + "sliceOrder": [], + "space": "individual", + "taskName": "auditory", + "useFieldmaps": true, + "zeropad": 2 +} \ No newline at end of file diff --git a/demos/MoAE/options_task-auditory_unwarp-0.json b/demos/MoAE/options_task-auditory_unwarp-0.json new file mode 100644 index 00000000..009b8147 --- /dev/null +++ b/demos/MoAE/options_task-auditory_unwarp-0.json @@ -0,0 +1,48 @@ +{ + "STC_referenceSlice": [], + "anatReference": { + "type": "T1w", + "session": 1 + }, + "contrastList": [], + "dataDir": "/home/remi/github/CPP_SPM/demos/MoAE/output/MoAEpilot", + "derivativesDir": "/home/remi/github/CPP_SPM/demos/MoAE/derivatives/unwarp-0", + "funcVoxelDims": [], + "model": { + "file": "/home/remi/github/CPP_SPM/demos/MoAE/models/model-MoAE_smdl.json" + }, + "realign": { + "useUnwarp": false + }, + "result": { + "Steps": { + "Contrasts": [ + { + "MC": "FWE", + "Mask": false, + "NIDM": true, + "Name": "listening", + "k": 0, + "p": 0.05 + }, + { + "MC": "none", + "Mask": false, + "NIDM": true, + "Name": "listening_inf_baseline", + "k": 0, + "p": 0.01 + } + ], + "Level": "subject" + } + }, + "skullstrip": { + "threshold": 0.75 + }, + "sliceOrder": [], + "space": "MNI", + "taskName": "auditory", + "useFieldmaps": true, + "zeropad": 2 +} \ No newline at end of file diff --git a/demos/MoAE/options_task-auditory_unwarp-0_space-individual.json b/demos/MoAE/options_task-auditory_unwarp-0_space-individual.json new file mode 100644 index 00000000..6dd7198f --- /dev/null +++ b/demos/MoAE/options_task-auditory_unwarp-0_space-individual.json @@ -0,0 +1,48 @@ +{ + "STC_referenceSlice": [], + "anatReference": { + "type": "T1w", + "session": 1 + }, + "contrastList": [], + "dataDir": "/home/remi/github/CPP_SPM/demos/MoAE/output/MoAEpilot", + "derivativesDir": "/home/remi/github/CPP_SPM/demos/MoAE/derivatives/unwarp-0_native", + "funcVoxelDims": [], + "model": { + "file": "/home/remi/github/CPP_SPM/demos/MoAE/models/model-MoAE_smdl.json" + }, + "realign": { + "useUnwarp": false + }, + "result": { + "Steps": { + "Contrasts": [ + { + "MC": "FWE", + "Mask": false, + "NIDM": true, + "Name": "listening", + "k": 0, + "p": 0.05 + }, + { + "MC": "none", + "Mask": false, + "NIDM": true, + "Name": "listening_inf_baseline", + "k": 0, + "p": 0.01 + } + ], + "Level": "subject" + } + }, + "skullstrip": { + "threshold": 0.75 + }, + "sliceOrder": [], + "space": "individual", + "taskName": "auditory", + "useFieldmaps": true, + "zeropad": 2 +} \ No newline at end of file diff --git a/demos/openneuro/ds000001_getOption.m b/demos/openneuro/ds000001_getOption.m index 0ff2bced..b468ef5b 100644 --- a/demos/openneuro/ds000001_getOption.m +++ b/demos/openneuro/ds000001_getOption.m @@ -17,6 +17,26 @@ % The directory where the data are located opt.dataDir = '/home/remi/openneuro/ds000001/raw'; + % Uncomment the lines below to run preprocessing + % - don't use realign and unwarp + % opt.realign.useUnwarp = false; + % - in "native" space: don't do normalization + % opt.space = 'individual'; + + opt.model.file = fullfile(fileparts(mfilename('fullpath')), ... + 'models', ... + 'model-defaultBalloonanalogrisktask_smdl.json'); + + % specify the result to compute + opt.result.Steps(1) = struct( ... + 'Level', 'subject', ... + 'Contrasts', struct( ... + 'Name', 'explode_demean', ... % has to match + 'Mask', false, ... + 'MC', 'FWE', ... FWE, none, FDR + 'p', 0.05, ... + 'k', 0)); + %% DO NOT TOUCH opt = checkOptions(opt); saveOptions(opt); diff --git a/demos/openneuro/ds000001_run.m b/demos/openneuro/ds000001_run.m index c3da5d6d..38d9a18e 100644 --- a/demos/openneuro/ds000001_run.m +++ b/demos/openneuro/ds000001_run.m @@ -7,6 +7,7 @@ % Smoothing to apply FWHM = 6; +conFWHM = 6; % directory with this script becomes the current directory WD = fileparts(mfilename('fullpath')); @@ -29,10 +30,17 @@ bidsSpatialPrepro(opt); anatomicalQA(opt); +bidsResliceTpmToFunc(opt); +functionalQA(opt); bidsSmoothing(FWHM, opt); -% Not implemented yet -% bidsFFX('specifyAndEstimate', opt, FWHM); -% bidsFFX('contrasts', opt, FWHM); +bidsFFX('specifyAndEstimate', opt, FWHM); +bidsFFX('contrasts', opt, FWHM); +bidsResults(opt, FWHM); + +bidsRFX('smoothContrasts', opt, FWHM, conFWHM); +bidsRFX('RFX', opt, FWHM, conFWHM); + +% WIP: group level results % bidsResults(opt, FWHM); diff --git a/demos/openneuro/ds000114_getOption.m b/demos/openneuro/ds000114_getOption.m index b3527bfc..5797314e 100644 --- a/demos/openneuro/ds000114_getOption.m +++ b/demos/openneuro/ds000114_getOption.m @@ -20,7 +20,11 @@ opt.anatReference.type = 'T1w'; opt.anatReference.session = 2; - opt.space = 'individual'; + % Uncomment the lines below to run preprocessing + % - don't use realign and unwarp + % opt.realign.useUnwarp = false; + % - in "native" space: don't do normalization + % opt.space = 'individual'; opt.model.file = fullfile(fileparts(mfilename('fullpath')), ... 'models', ... @@ -34,8 +38,11 @@ 'Mask', false, ... 'MC', 'FWE', ... FWE, none, FDR 'p', 0.05, ... - 'k', 0, ... - 'NIDM', true)); + 'k', 0)); + + opt.parallelize.do = true; + opt.parallelize.nbWorkers = 2; + opt.parallelize.killOnExit = false; %% DO NOT TOUCH opt = checkOptions(opt); diff --git a/demos/openneuro/ds000114_run.m b/demos/openneuro/ds000114_run.m index 1ccce4db..0c72597d 100644 --- a/demos/openneuro/ds000114_run.m +++ b/demos/openneuro/ds000114_run.m @@ -7,6 +7,7 @@ % Smoothing to apply FWHM = 6; +conFWHM = 6; % directory with this script becomes the current directory WD = fileparts(mfilename('fullpath')); @@ -24,9 +25,9 @@ reportBIDS(opt); -% bidsCopyRawFolder(opt, 1); -% -% bidsSTC(opt); +bidsCopyRawFolder(opt, 1); + +bidsSTC(opt); bidsSpatialPrepro(opt); @@ -39,3 +40,9 @@ bidsFFX('specifyAndEstimate', opt, FWHM); bidsFFX('contrasts', opt, FWHM); bidsResults(opt, FWHM); + +bidsRFX('smoothContrasts', opt, FWHM, conFWHM); +bidsRFX('RFX', opt, FWHM, conFWHM); + +% WIP: group level results +% bidsResults(opt, FWHM); diff --git a/demos/openneuro/ds001168_getOption.m b/demos/openneuro/ds001168_getOption.m index 028da8b9..6a6ebfb4 100644 --- a/demos/openneuro/ds001168_getOption.m +++ b/demos/openneuro/ds001168_getOption.m @@ -20,7 +20,11 @@ opt.anatReference.type = 'T1w'; opt.anatReference.session = 1; - opt.space = 'individual'; + % Uncomment the lines below to run preprocessing + % - don't use realign and unwarp + % opt.realign.useUnwarp = false; + % - in "native" space: don't do normalization + % opt.space = 'individual'; %% DO NOT TOUCH opt = checkOptions(opt); diff --git a/demos/openneuro/models/model-defaultBalloonanalogrisktask_smdl.json b/demos/openneuro/models/model-defaultBalloonanalogrisktask_smdl.json new file mode 100644 index 00000000..5a5f697d --- /dev/null +++ b/demos/openneuro/models/model-defaultBalloonanalogrisktask_smdl.json @@ -0,0 +1,104 @@ +{ + "Name": "balloonanalogrisktask", + "Description": "default model for balloonanalogrisktask", + "Input": { + "task": "balloonanalogrisktask" + }, + "Steps": [ + { + "Level": "subject", + "Transformations": [ + { + "Name": "Factor", + "Inputs": ["trial_type"] + }, + { + "Name": "Convolve", + "Model": "spm", + "Inputs": [" "] + } + ], + "Model": { + "X": [ + "trial_type.cash_demean", + "trial_type.control_pumps_demean", + "trial_type.explode_demean", + "trial_type.pumps_demean", + "trans_x", + "trans_y", + "trans_z", + "rot_x", + "rot_y", + "rot_z" + ], + "Options": { + "high_pass_filter_cutoff_secs": 128 + }, + "Software": { + "SPM": { + "whitening": "FAST" + } + }, + "Mask": " " + }, + "AutoContrasts": [ + "trial_type.cash_demean", + "trial_type.control_pumps_demean", + "trial_type.explode_demean", + "trial_type.pumps_demean" + ] + }, + { + "Level": "run", + "Transformations": [ + { + "Name": "Factor", + "Inputs": ["trial_type"] + }, + { + "Name": "Convolve", + "Model": "spm", + "Inputs": [" "] + } + ], + "Model": { + "X": [ + "trial_type.cash_demean", + "trial_type.control_pumps_demean", + "trial_type.explode_demean", + "trial_type.pumps_demean", + "trans_x", + "trans_y", + "trans_z", + "rot_x", + "rot_y", + "rot_z" + ], + "Options": { + "high_pass_filter_cutoff_secs": 128 + }, + "Software": { + "SPM": { + "whitening": "FAST" + } + }, + "Mask": " " + }, + "AutoContrasts": [ + "trial_type.cash_demean", + "trial_type.control_pumps_demean", + "trial_type.explode_demean", + "trial_type.pumps_demean" + ] + }, + { + "Level": "dataset", + "AutoContrasts": [ + "trial_type.cash_demean", + "trial_type.control_pumps_demean", + "trial_type.explode_demean", + "trial_type.pumps_demean" + ] + } + ] +} \ No newline at end of file diff --git a/demos/openneuro/models/model-ds000114-linebisection_smdl.json b/demos/openneuro/models/model-ds000114-linebisection_smdl.json index 31d58071..7bd81d2c 100644 --- a/demos/openneuro/models/model-ds000114-linebisection_smdl.json +++ b/demos/openneuro/models/model-ds000114-linebisection_smdl.json @@ -32,6 +32,10 @@ ] }, "AutoContrasts": ["trial_type.Correct_Task", "trial_type.Incorrect_Task"] + }, + { + "Level": "dataset", + "AutoContrasts": ["trial_type.Correct_Task", "trial_type.Incorrect_Task"] } ] } \ No newline at end of file diff --git a/demos/openneuro/options/options_task-balloonanalogrisktask.json b/demos/openneuro/options/options_task-balloonanalogrisktask.json new file mode 100644 index 00000000..4d8368e9 --- /dev/null +++ b/demos/openneuro/options/options_task-balloonanalogrisktask.json @@ -0,0 +1,43 @@ +{ + "STC_referenceSlice": [], + "anatReference": { + "type": "T1w", + "session": 1 + }, + "contrastList": [], + "dataDir": "/home/remi/openneuro/ds000001/raw", + "derivativesDir": "/home/remi/openneuro/ds000001/derivatives/default", + "funcVoxelDims": [], + "groups": [""], + "model": { + "file": "" + }, + "realign": { + "useUnwarp": true + }, + "result": { + "Steps": { + "Level": "", + "Contrasts": { + "Name": "", + "Mask": false, + "MC": "FWE", + "p": 0.05, + "k": 0, + "NIDM": true + } + } + }, + "skullstrip": { + "threshold": 0.75 + }, + "sliceOrder": [], + "space": "MNI", + "subjects": [ + "01", + "02" + ], + "taskName": "balloonanalogrisktask", + "useFieldmaps": true, + "zeropad": 2 +} \ No newline at end of file diff --git a/demos/openneuro/options/options_task-balloonanalogrisktask_space-individual.json b/demos/openneuro/options/options_task-balloonanalogrisktask_space-individual.json new file mode 100644 index 00000000..424c2bdd --- /dev/null +++ b/demos/openneuro/options/options_task-balloonanalogrisktask_space-individual.json @@ -0,0 +1,43 @@ +{ + "STC_referenceSlice": [], + "anatReference": { + "type": "T1w", + "session": 1 + }, + "contrastList": [], + "dataDir": "/home/remi/openneuro/ds000001/raw", + "derivativesDir": "/home/remi/openneuro/ds000001/derivatives/native", + "funcVoxelDims": [], + "groups": [""], + "model": { + "file": "" + }, + "realign": { + "useUnwarp": true + }, + "result": { + "Steps": { + "Level": "", + "Contrasts": { + "Name": "", + "Mask": false, + "MC": "FWE", + "p": 0.05, + "k": 0, + "NIDM": true + } + } + }, + "skullstrip": { + "threshold": 0.75 + }, + "sliceOrder": [], + "space": "individual", + "subjects": [ + "01", + "02" + ], + "taskName": "balloonanalogrisktask", + "useFieldmaps": true, + "zeropad": 2 +} \ No newline at end of file diff --git a/demos/openneuro/options/options_task-balloonanalogrisktask_unwarp-0.json b/demos/openneuro/options/options_task-balloonanalogrisktask_unwarp-0.json new file mode 100644 index 00000000..d84df460 --- /dev/null +++ b/demos/openneuro/options/options_task-balloonanalogrisktask_unwarp-0.json @@ -0,0 +1,43 @@ +{ + "STC_referenceSlice": [], + "anatReference": { + "type": "T1w", + "session": 1 + }, + "contrastList": [], + "dataDir": "/home/remi/openneuro/ds000001/raw", + "derivativesDir": "/home/remi/openneuro/ds000001/derivatives/unwarp-0", + "funcVoxelDims": [], + "groups": [""], + "model": { + "file": "" + }, + "realign": { + "useUnwarp": false + }, + "result": { + "Steps": { + "Level": "", + "Contrasts": { + "Name": "", + "Mask": false, + "MC": "FWE", + "p": 0.05, + "k": 0, + "NIDM": true + } + } + }, + "skullstrip": { + "threshold": 0.75 + }, + "sliceOrder": [], + "space": "MNI", + "subjects": [ + "01", + "02" + ], + "taskName": "balloonanalogrisktask", + "useFieldmaps": true, + "zeropad": 2 +} \ No newline at end of file diff --git a/demos/openneuro/options/options_task-balloonanalogrisktask_unwarp-0_space-individual.json b/demos/openneuro/options/options_task-balloonanalogrisktask_unwarp-0_space-individual.json new file mode 100644 index 00000000..53083c7e --- /dev/null +++ b/demos/openneuro/options/options_task-balloonanalogrisktask_unwarp-0_space-individual.json @@ -0,0 +1,43 @@ +{ + "STC_referenceSlice": [], + "anatReference": { + "type": "T1w", + "session": 1 + }, + "contrastList": [], + "dataDir": "/home/remi/openneuro/ds000001/raw", + "derivativesDir": "/home/remi/openneuro/ds000001/derivatives/unwarp-0_native", + "funcVoxelDims": [], + "groups": [""], + "model": { + "file": "" + }, + "realign": { + "useUnwarp": false + }, + "result": { + "Steps": { + "Level": "", + "Contrasts": { + "Name": "", + "Mask": false, + "MC": "FWE", + "p": 0.05, + "k": 0, + "NIDM": true + } + } + }, + "skullstrip": { + "threshold": 0.75 + }, + "sliceOrder": [], + "space": "individual", + "subjects": [ + "01", + "02" + ], + "taskName": "balloonanalogrisktask", + "useFieldmaps": true, + "zeropad": 2 +} \ No newline at end of file diff --git a/demos/openneuro/options/options_task-linebisection.json b/demos/openneuro/options/options_task-linebisection.json new file mode 100644 index 00000000..f09054a2 --- /dev/null +++ b/demos/openneuro/options/options_task-linebisection.json @@ -0,0 +1,43 @@ +{ + "STC_referenceSlice": [], + "anatReference": { + "session": 2, + "type": "T1w" + }, + "contrastList": [], + "dataDir": "/home/remi/openneuro/ds000114/raw", + "derivativesDir": "/home/remi/openneuro/ds000114/derivatives/default", + "funcVoxelDims": [], + "groups": [""], + "model": { + "file": "/home/remi/github/CPP_SPM/demos/openneuro/models/model-ds000114-linebisection_smdl.json" + }, + "realign": { + "useUnwarp": true + }, + "result": { + "Steps": { + "Contrasts": { + "MC": "FWE", + "Mask": false, + "NIDM": true, + "Name": "Correct_Task", + "k": 0, + "p": 0.05 + }, + "Level": "subject" + } + }, + "skullstrip": { + "threshold": 0.75 + }, + "sliceOrder": [], + "space": "MNI", + "subjects": [ + "01", + "02" + ], + "taskName": "linebisection", + "useFieldmaps": true, + "zeropad": 2 +} \ No newline at end of file diff --git a/demos/openneuro/options/options_task-linebisection_space-individual.json b/demos/openneuro/options/options_task-linebisection_space-individual.json new file mode 100644 index 00000000..1ba31ce9 --- /dev/null +++ b/demos/openneuro/options/options_task-linebisection_space-individual.json @@ -0,0 +1,43 @@ +{ + "STC_referenceSlice": [], + "anatReference": { + "session": 2, + "type": "T1w" + }, + "contrastList": [], + "dataDir": "/home/remi/openneuro/ds000114/raw", + "derivativesDir": "/home/remi/openneuro/ds000114/derivatives/native", + "funcVoxelDims": [], + "groups": [""], + "model": { + "file": "/home/remi/github/CPP_SPM/demos/openneuro/models/model-ds000114-linebisection_smdl.json" + }, + "realign": { + "useUnwarp": true + }, + "result": { + "Steps": { + "Contrasts": { + "MC": "FWE", + "Mask": false, + "NIDM": true, + "Name": "Correct_Task", + "k": 0, + "p": 0.05 + }, + "Level": "subject" + } + }, + "skullstrip": { + "threshold": 0.75 + }, + "sliceOrder": [], + "space": "individual", + "subjects": [ + "01", + "02" + ], + "taskName": "linebisection", + "useFieldmaps": true, + "zeropad": 2 +} \ No newline at end of file diff --git a/demos/openneuro/options/options_task-linebisection_unwarp-0.json b/demos/openneuro/options/options_task-linebisection_unwarp-0.json new file mode 100644 index 00000000..a35827b3 --- /dev/null +++ b/demos/openneuro/options/options_task-linebisection_unwarp-0.json @@ -0,0 +1,43 @@ +{ + "STC_referenceSlice": [], + "anatReference": { + "session": 2, + "type": "T1w" + }, + "contrastList": [], + "dataDir": "/home/remi/openneuro/ds000114/raw", + "derivativesDir": "/home/remi/openneuro/ds000114/derivatives/unwarp-0", + "funcVoxelDims": [], + "groups": [""], + "model": { + "file": "/home/remi/github/CPP_SPM/demos/openneuro/models/model-ds000114-linebisection_smdl.json" + }, + "realign": { + "useUnwarp": false + }, + "result": { + "Steps": { + "Contrasts": { + "MC": "FWE", + "Mask": false, + "NIDM": true, + "Name": "Correct_Task", + "k": 0, + "p": 0.05 + }, + "Level": "subject" + } + }, + "skullstrip": { + "threshold": 0.75 + }, + "sliceOrder": [], + "space": "MNI", + "subjects": [ + "01", + "02" + ], + "taskName": "linebisection", + "useFieldmaps": true, + "zeropad": 2 +} \ No newline at end of file diff --git a/demos/openneuro/options/options_task-linebisection_unwarp-0_space-individual.json b/demos/openneuro/options/options_task-linebisection_unwarp-0_space-individual.json new file mode 100644 index 00000000..1a2e4675 --- /dev/null +++ b/demos/openneuro/options/options_task-linebisection_unwarp-0_space-individual.json @@ -0,0 +1,43 @@ +{ + "STC_referenceSlice": [], + "anatReference": { + "session": 2, + "type": "T1w" + }, + "contrastList": [], + "dataDir": "/home/remi/openneuro/ds000114/raw", + "derivativesDir": "/home/remi/openneuro/ds000114/derivatives/unwarpt-0_native", + "funcVoxelDims": [], + "groups": [""], + "model": { + "file": "/home/remi/github/CPP_SPM/demos/openneuro/models/model-ds000114-linebisection_smdl.json" + }, + "realign": { + "useUnwarp": false + }, + "result": { + "Steps": { + "Contrasts": { + "MC": "FWE", + "Mask": false, + "NIDM": true, + "Name": "Correct_Task", + "k": 0, + "p": 0.05 + }, + "Level": "subject" + } + }, + "skullstrip": { + "threshold": 0.75 + }, + "sliceOrder": [], + "space": "individual", + "subjects": [ + "01", + "02" + ], + "taskName": "linebisection", + "useFieldmaps": true, + "zeropad": 2 +} \ No newline at end of file diff --git a/demos/sourceDataProcessing/batchSource.m b/demos/sourceDataProcessing/batchSource.m new file mode 100644 index 00000000..7334115f --- /dev/null +++ b/demos/sourceDataProcessing/batchSource.m @@ -0,0 +1,24 @@ +% (C) Copyright 2020 CPP BIDS SPM-pipeline developers + +clear; +clc; + +% Directory with this script becomes the current directory +pth = fileparts(mfilename('fullpath')); + +% We add all the subfunctions that are in the sub directories +addpath(genpath(fullfile(pth, '..', '..', 'src'))); +addpath(genpath(fullfile(pth, '..', '..', 'lib'))); + +%% Run batches + +optSource = getOptionSource(); + +% Single volumes to 4D volumes conversion + remove n dummies +convert3Dto4D(optSource); + +% Deface anatomical volumes in a raw folder +% defaceAnat(optSource); COMING SOON + +% GZip the volumes in a raw folder +bidsGZipRawFolder(optSource, 0); diff --git a/demos/sourceDataProcessing/getOptionSource.m b/demos/sourceDataProcessing/getOptionSource.m new file mode 100644 index 00000000..27689884 --- /dev/null +++ b/demos/sourceDataProcessing/getOptionSource.m @@ -0,0 +1,53 @@ +% (C) Copyright 2019 CPP BIDS SPM-pipeline developers + +function optSource = getOptionSource() + % + % Returns a structure that contains the options chosen by the user to run the source processing + % batch workflow + % + % USAGE:: + % + % optSource = getOptionSource() + % + % :returns: - :optSource: (struct) + + if nargin < 1 + optSource = []; + end + + % Set the folder where sequences folders exist + optSource.sourceDir = '/Users/barilari/Desktop/DICOM_UCL_leuven/renamed/sub-pilot001/ses-002/MRI'; + + optSource.dataDir = '/Users/barilari/Desktop/DICOM_UCL_leuven/raw'; + + % List of the sequences that you want to skip (folder name pattern) + optSource.sequenceToIgnore = {'AAHead_Scout', ... + 'b1map', ... + 't1', ... + 'gre_field'}; + + % Number of volumes to discard ad dummies, (0 is default) + optSource.nbDummies = 5; + + % List of the sequences where you want to remove dummies (folder name pattern) + optSource.sequenceRmDummies = {'cmrr_mbep2d_p3_mb2_1.6iso_AABrain', ... + 'cmrr_mbep2d_p4_mb2_750um_AAbrain'}; + + % Set data format conversion (0 is default) + + % 0: SAME + % 2: UINT8 - unsigned char + % 4: INT16 - signed short + % 8: INT32 - signed int + % 16: FLOAT32 - single prec. float + % 64: FLOAT64 - double prec. float + + optSource.dataType = 0; + + % Boolean to enable gzip of the new 4D file (0 is default) + optSource.zip = 0; + + % Check the options provided + optSource = checkOptionsSource(optSource); + +end diff --git a/demos/spm_face_rep/FaceRep_getOption.m b/demos/spm_face_rep/FaceRep_getOption.m new file mode 100644 index 00000000..c20e020a --- /dev/null +++ b/demos/spm_face_rep/FaceRep_getOption.m @@ -0,0 +1,21 @@ +% (C) Copyright 2019 Remi Gau + +function opt = FaceRep_getOption() + % returns a structure that contains the options chosen by the user to run + % slice timing correction, pre-processing, FFX, RFX. + + opt = []; + + % task to analyze + opt.taskName = 'facerepetition'; + + % The directory where the data are located + opt.dataDir = fullfile(fileparts(mfilename('fullpath')), 'raw'); + + opt.model.hrfDerivatives = [1 1]; + + %% DO NOT TOUCH + opt = checkOptions(opt); + saveOptions(opt); + +end diff --git a/demos/spm_face_rep/face_rep_convert2BIDS.m b/demos/spm_face_rep/face_rep_convert2BIDS.m new file mode 100644 index 00000000..fffea5da --- /dev/null +++ b/demos/spm_face_rep/face_rep_convert2BIDS.m @@ -0,0 +1,229 @@ +% (C) Copyright 2020 CPP BIDS SPM-pipeline developers + +function face_rep_convert2BIDS() + % + % downloads the fare repetition dataset from SPM and convert it to BIDS + % + % Adapted from its counterpart for MoAE + % + % + + subject = 'sub-01'; + task_name = 'face repetition'; + nb_slices = 24; + repetition_time = 2; + echo_time = 0.04; + + opt.indent = ' '; + + % URL of the data set to download + URL = 'http://www.fil.ion.ucl.ac.uk/spm/download/data/face_rep/face_rep.zip'; + + % Working directory + WD = fileparts(mfilename('fullpath')); + + %% Get data + fprintf('%-10s:', 'Downloading dataset...'); + urlwrite(URL, 'face_rep.zip'); + fprintf(1, ' Done\n\n'); + + fprintf('%-10s:', 'Unzipping dataset...'); + unzip('face_rep.zip', WD); + movefile('face_rep', 'source'); + fprintf(1, ' Done\n\n'); + + %% Create file structure hierarchy + spm_mkdir(WD, 'raw', subject, {'anat', 'func'}); + + %% Structural MRI + anat_hdr = spm_vol(fullfile(WD, 'source', 'Structural', 'sM03953_0007.img')); + anat_data = spm_read_vols(anat_hdr); + anat_hdr.fname = fullfile(WD, 'raw', 'sub-01', 'anat', 'sub-01_T1w.nii'); + spm_write_vol(anat_hdr, anat_data); + + %% Functional MRI + func_files = spm_select('FPList', fullfile(WD, 'source', 'RawEPI'), '^sM.*\.img$'); + spm_file_merge( ... + func_files, ... + fullfile(WD, 'raw', 'sub-01', 'func', ... + ['sub-01_task-' strrep(task_name, ' ', '') '_bold.nii']), ... + 0, ... + repetition_time); + delete(fullfile(WD, 'raw', 'sub-01', 'func', ... + ['sub-01_task-' strrep(task_name, ' ', '') '_bold.mat'])); + + %% And everything else + create_events_tsv_file(WD, task_name, repetition_time); + create_readme(WD); + create_changelog(WD); + create_datasetdescription(WD, opt); + create_bold_json(WD, task_name, repetition_time, nb_slices, echo_time, opt); + +end + +function create_events_tsv_file(WD, task_name, repetition_time) + + % TODO + % add the lag between presentations of each item necessary for the parametric + % analysis. + + load(fullfile(WD, 'source', 'all_conditions.mat'), ... + 'names', 'onsets', 'durations'); + + onset_column = []; + duration_column = []; + trial_type_column = []; + + for iCondition = 1:numel(names) + onset_column = [onset_column; onsets{iCondition}]; %#ok<*USENS> + duration_column = [duration_column; durations{iCondition}']; %#ok<*AGROW> + trial_type_column = [trial_type_column; repmat( ... + names{iCondition}, ... + size(onsets{iCondition}, 1), 1)]; + end + + % sort trials by their presentation time + [onset_column, idx] = sort(onset_column); + duration_column = duration_column(idx); + trial_type_column = trial_type_column(idx, :); + + onset_column = repetition_time * onset_column; + + tsv_content = struct( ... + 'onset', onset_column, ... + 'duration', duration_column, ... + 'trial_type', {cellstr(trial_type_column)}); + + spm_save(fullfile(WD, 'raw', 'sub-01', 'func', ... + ['sub-01_task-' strrep(task_name, ' ', '') '_events.tsv']), ... + tsv_content); + +end + +function create_readme(WD) + + rdm = { + ' ___ ____ __ __' + '/ __)( _ \( \/ ) Statistical Parametric Mapping' + '\__ \ )___/ ) ( Wellcome Centre for Human Neuroimaging' + '(___/(__) (_/\/\_) https://www.fil.ion.ucl.ac.uk/spm/' + + '' + ' Face repetition example event-related fMRI dataset' + '________________________________________________________________________' + '' + '???' + '' + 'Summary:' + '7 Files, 79.32MB' + '1 - Subject' + '1 - Session' + '' + 'Available Tasks:' + 'face repetition' + '' + 'Available Modalities:' + 'T1w' + 'bold' + 'events' + '' + 'These whole brain BOLD/EPI images were acquired on a modified ???T' + 'Siemens MAGNETOM Vision system.' + '351 acquisitions were made.' + 'Each EPI acquisition consisted of 24 descending slices:' + '- matrix size: 64x64' + '- voxel size: 3mm x 3mm x 3mm with 1.5mm gap' + '- repatition time: 2s' + '- echo time: 40ms' + '' + 'Experimental design:' + '- 2x2 factorial event-related fMRI' + '- One session (one subject)' + '- (Famous vs. Nonfamous) x (1st vs 2nd presentation) of faces ' + ' against baseline of chequerboard' + '- 2 presentations of 26 Famous and 26 Nonfamous Greyscale photographs, ' + ' for 0.5s, randomly intermixed, for fame judgment task ' + ' (one of two right finger key presses).' + '- Parameteric factor "lag" = number of faces intervening ' + ' between repetition of a specific face + 1' + '- Minimal SOA=4.5s, with probability 2/3 (ie 1/3 null events)' + '' + 'A structural image was also acquired.'}; + + % TODO + % use spm_save to actually write this file? + fid = fopen(fullfile(WD, 'raw', 'README'), 'wt'); + for i = 1:numel(rdm) + fprintf(fid, '%s\n', rdm{i}); + end + fclose(fid); + +end + +function create_changelog(WD) + + cg = { ... + '1.0.1 2020-11-26', ' - BIDS version.', ... + '1.0.0 1999-05-13', ' - Initial release.'}; + fid = fopen(fullfile(WD, 'raw', 'CHANGES'), 'wt'); + + for i = 1:numel(cg) + fprintf(fid, '%s\n', cg{i}); + end + fclose(fid); + +end + +function create_datasetdescription(WD, opt) + + descr = struct( ... + 'BIDSVersion', '1.4.0', ... + 'Name', 'Mother of All Experiments', ... + 'Authors', {{ ... + 'Henson, R.N.A.', ... + 'Shallice, T.', ... + 'Gorno-Tempini, M.-L.', ... + 'Dolan, R.J.'}}, ... + 'ReferencesAndLinks', ... + {{'https://www.fil.ion.ucl.ac.uk/spm/data/face_rep/', ... + ['Henson, R.N.A., Shallice, T., Gorno-Tempini, M.-L. ' ... + 'and Dolan, R.J. (2002),', ... + 'Face repetition effects in implicit and explicit memory tests as', ... + 'measured by fMRI. Cerebral Cortex, 12, 178-186.'], ... + 'doi:10.1093/cercor/12.2.178'}} ... + ); + + spm_save(fullfile(WD, 'raw', 'dataset_description.json'), ... + descr, ... + opt); + +end + +function create_bold_json(WD, task_name, repetition_time, nb_slices, echo_time, opt) + + acquisition_time = repetition_time - repetition_time / nb_slices; + slice_timing = linspace(acquisition_time, 0, nb_slices); + + task = struct( ... + 'RepetitionTime', repetition_time, ... + 'EchoTime', echo_time, ... + 'SliceTiming', slice_timing, ... + 'NumberOfVolumesDiscardedByScanner', 0, ... + 'NumberOfVolumesDiscardedByUser', 0, ... + 'TaskName', task_name, ... + 'TaskDescription', ... + ['2 presentations of 26 Famous and 26 Nonfamous Greyscale photographs, ', ... + 'for 0.5s, randomly intermixed, for fame judgment task ', ... + '(one of two right finger key presses).'], ... + 'Manufacturer', 'Siemens', ... + 'ManufacturersModelName', 'MAGNETOM Vision', ... + 'MagneticFieldStrength', 2); + + spm_save(fullfile( ... + WD, ... + 'raw', ... + ['task-' strrep(task_name, ' ', '') '_bold.json']), ... + task, ... + opt); + +end diff --git a/demos/spm_face_rep/face_rep_run.m b/demos/spm_face_rep/face_rep_run.m new file mode 100644 index 00000000..5ea3695e --- /dev/null +++ b/demos/spm_face_rep/face_rep_run.m @@ -0,0 +1,73 @@ +% (C) Copyright 2019 Remi Gau + +% This script will download the face repetition dataset from the FIL +% and will run the basic preprocessing, FFX and contrasts on it. +% +% Results might be a bit different from those in the manual as some +% default options are slightly different in this pipeline (e.g use of FAST +% instead of AR(1), motion regressors added) +% +% TODO +% - add derivatives to the model +% - compute the relevant contrasts +% - compute motion effect +% - run parametric model +% + +clear; +clc; + +% Smoothing to apply +FWHM = 8; + +DownloadData = true; + +% URL of the data set to download +% directory with this script becomes the current directory +WD = fileparts(mfilename('fullpath')); + +% we add all the subfunctions that are in the sub directories +addpath(genpath(fullfile(WD, '..', '..', 'src'))); +addpath(genpath(fullfile(WD, '..', '..', 'lib'))); + +%% Set options +opt = FaceRep_getOption(); + +%% Removes previous analysis, gets data and converts it to BIDS +if DownloadData + try %#ok<*UNRCH> + rmdir('source', 's'); + rmdir('raw', 's'); + catch + end + + face_rep_convert2BIDS(); + +end + +%% +checkDependencies(); + +%% Run batches +reportBIDS(opt); +bidsCopyRawFolder(opt, 1); + +bidsSTC(opt); + +bidsSpatialPrepro(opt); + +% The following do not run on octave for now (because of spmup) +anatomicalQA(opt); +bidsResliceTpmToFunc(opt); + +% DEBUG +% functionalQA(opt); + +bidsSmoothing(FWHM, opt); + +% The following crash on Travis CI +bidsFFX('specifyAndEstimate', opt, FWHM); +bidsFFX('contrasts', opt, FWHM); + +% TODO +bidsResults(opt, FWHM); diff --git a/demos/vismotion/batch.m b/demos/vismotion/batch.m index 75e9ff72..8c52b50d 100644 --- a/demos/vismotion/batch.m +++ b/demos/vismotion/batch.m @@ -39,10 +39,10 @@ % group level univariate conFWHM = 6; -bidsRFX('smoothContrasts', funcFWHM, conFWHM, opt); +bidsRFX('smoothContrasts', opt, funcFWHM, conFWHM); % Not implemented yet -% bidsRFX('RFX', funcFWHM, conFWHM, opt); +% bidsRFX(action, opt, funcFWHM, conFWHM); % Not implemented yet % subject level multivariate diff --git a/docs/README.md b/docs/README.md index 24a540c3..4f1ca4e7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,8 +3,8 @@ ## Set up virtual environment ```bash -virtualenv -p python3 cpp_bids_spm -source cpp_bids_spm/bin/activate +virtualenv -p python3 cpp_spm +source cpp_spm/bin/activate pip install -r requirements.txt ``` diff --git a/docs/source/conf.py b/docs/source/conf.py index 20cb5e99..6a74fafa 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -17,9 +17,9 @@ # -- Project information ----------------------------------------------------- -project = 'CPP BIDS SPM' -copyright = '2020, the CPP BIDS SPM pipeline dev team' -author = 'the CPP BIDS SPM pipeline dev team' +project = 'CPP SPM' +copyright = '2020, the CPP SPM pipeline dev team' +author = 'the CPP SPM pipeline dev team' # The full version, including alpha/beta/rc tags release = 'v0.1.0' diff --git a/docs/source/set_up.rst b/docs/source/set_up.rst index 33c1fbfa..d4e1064e 100644 --- a/docs/source/set_up.rst +++ b/docs/source/set_up.rst @@ -4,11 +4,27 @@ Set up Configuration of the pipeline ============================= -All the details specific to your analysis should be set in the `getOptions.m`. +Options +------- -There is a getOption_template file that shows you would set up the getOption -file if one wanted to analyse the -`ds001 data set from OpenNeuro `_. +Most of the options you have chosen for your analysis will be set in a variable +``opt`` an Octave/Matlab structure. + +The content of that structure can be defined: + +- "at run" time in a script or a function (that we often label ``getOption``) +- in a separate json file that can be loaded with ``loadAndCheckOptions()``. + +You can find examples of both in the ``demos`` folder. You can also find a +template function for ``getOption`` in the ``src/templates`` folder. + +Required options +++++++++++++++++ + +Set the task to analyze in the BIDS data set ``opt.taskName = 'auditory'`` + +Selecting groups and subjects ++++++++++++++++++++++++++++++ Set the group of subjects to analyze:: @@ -38,11 +54,11 @@ run:: opt.groups = {''}; opt.subjects = {'01', 'cont01', 'cat02', 'ctrl02', 'blind01'}; -Set the task to analyze in the BIDS data set ``opt.taskName = 'auditory'`` + BIDS model JSON files -===================== +--------------------- This files allow you to specify the GLM to run and which contrasts to run. It follows the BIDS statistical model extension and as implemented by diff --git a/docs/source/workflows.rst b/docs/source/workflows.rst index 21f5872c..06ffadd8 100644 --- a/docs/source/workflows.rst +++ b/docs/source/workflows.rst @@ -5,7 +5,7 @@ Workflows List of the different workflows of the pipeline. -Each has to be run for each task independently. All parameters should preferably +Each has to be run for each task independently. All parameters should be changed in the `opt` structure. See the set up section. @@ -22,19 +22,6 @@ Slice Time Correction .. autofunction:: bidsSTC -Performs Slice Time Correction (STC) of the functional volumes by running the script: -``bidsSTC.m`` - -STC will be performed using the information provided in the BIDS data set. It -will use the mid-volume acquisition time point as as reference. - -The ``getOption.m`` fields related to STC can still be used to do some slice -timing correction even no information is can be found in the BIDS data set. - -In general slice order and reference slice is entered in time unit (ms) (this is -the BIDS way of doing things) instead of the slice index of the reference slice -(the "SPM" way of doing things). - More info available on this page of the `SPM wikibook `_. @@ -76,20 +63,25 @@ do a search for "slice timing AND before" in the archives of the list.* Spatial Preprocessing ===================== -Performs spatial preprocessing by running the script: ``bidsSpatialPrepro.m`` +Perform spatial preprocessing by running ``bidsSpatialPrepro`` .. autofunction:: bidsSpatialPrepro +.. autofunction:: bidsRealignReslice + +.. autofunction:: bidsRealignUnwarp + Smoothing ========= -Performs smoothing of the functional data by running the function: ``bidsSmoothing.m`` + +Perform smoothing of the functional data by running ``bidsSmoothing`` .. autofunction:: bidsSmoothing Subject level analysis ====================== -Performs the subject level analysis by running the ffx script: ``bidsFFX.m``. +Perform the subject level analysis by running the ffx script: ``bidsFFX``. This will run twice, once for model specification and another time for model estimation. See the function for more details. @@ -103,20 +95,27 @@ of every run as confound regressors. Group level analysis ==================== -Performs the group level analysis by running the RFX script: ``bidsRFX.m`` +Perform the group level analysis by running the RFX script: ``bidsRFX``. .. autofunction:: bidsRFX -Comput results -============== +Compute results +=============== .. autofunction:: bidsResults +Other +===== ---- - -.. autofunction:: bidsRealignReslice -.. autofunction:: bidsRealignUnwarp .. autofunction:: bidsCreateVDM .. autofunction:: bidsResliceTpmToFunc .. autofunction:: bidsSegmentSkullStrip + +Helper functions +================ + +To be used if you want to create a new workflow. + +.. autofunction:: setUpWorkflow +.. autofunction:: saveAndRunWorkflow + diff --git a/lib/bids-matlab b/lib/bids-matlab index cc946cf7..063f8853 160000 --- a/lib/bids-matlab +++ b/lib/bids-matlab @@ -1 +1 @@ -Subproject commit cc946cf7d63c5ad42aa7258ef4ef73a2d24c97e5 +Subproject commit 063f8853af07533bd92052e6a00d63e21f6990fa diff --git a/src/mancoreg/mancoreg.m b/lib/mancoreg/mancoreg.m similarity index 94% rename from src/mancoreg/mancoreg.m rename to lib/mancoreg/mancoreg.m index 717decd0..a161e846 100644 --- a/src/mancoreg/mancoreg.m +++ b/lib/mancoreg/mancoreg.m @@ -4,28 +4,36 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers function mancoreg(targetimage, sourceimage) - % mancoreg(targetimage, sourceimage) % - % Manual coregistration tool + % This function displays 2 SPM ortho-views of a ``targetimage`` and + % a ``sourceimage`` image that can be manually coregistered. + % + % USAGE:: % - % FORMAT mancoreg(targetimage,sourceimage); + % mancoreg([targetimage,] [sourceimage]) % - % This function displays 2 ortho views of a TARGET and - % a SOURCE image that can be manually coregistered. + % :param targetimage: Filename or fullpath of the target image. If none is provided + % you will be asked by SPM to select one. + % :type targetimage: type + % :param sourceimage: Filename or fullpath of the source image. If none is provided + % you will be asked by SPM to select one. + % :type sourceimage: string + % + % Manual coregistration tool % % The source image (bottom graph) can be manually % rotated and translated with 6 slider controls. In the source % graph the source image can be exchanged with the target image % using a radio button toggle. This is helpful for visual fine control % of the coregistration. The transformation matrix can be applied - % to a selected set of volumes with the "apply transformation" button. + % to a selected set of volumes with the ``apply transformation`` button. % If the transformation is to be applied to the original source file % that file will also need to be selected. If the sourceimage or % targetimage are not passed the user will be prompted with a file browser. % - % The code is loosely based on spm_image.m and spm_orthoviews.m + % The code is loosely based on ``spm_image()`` and ``spm_orthoviews()`` % It requires the m-file with the callback functions for the user - % controls (mancoregCallbacks.m). + % controls (``mancoregCallbacks()``). % JH 10.01.2004 % modified DSS 10/02/2009 diff --git a/src/mancoreg/mancoregCallbacks.m b/lib/mancoreg/mancoregCallbacks.m similarity index 94% rename from src/mancoreg/mancoregCallbacks.m rename to lib/mancoreg/mancoregCallbacks.m index 9257e85d..c92167bf 100644 --- a/src/mancoreg/mancoregCallbacks.m +++ b/lib/mancoreg/mancoregCallbacks.m @@ -3,10 +3,19 @@ % (C) Copyright 2012_2019 Remi Gau % (C) Copyright 2020 CPP BIDS SPM-pipeline developers -function mancoregCallbacks(operation, varargin) - % mancoreg_callbacks(op, varargin) +function mancoregCallbacks(operation) + % + % Callback routines for ``mancoreg()``: defines the different actions for the + % different buttons. + % + % USAGE:: + % + % mancoreg_callbacks(operation) + % + % :param operation: Can be any of the following: ``move``, ``toggle_off``, ``toggle_on``, + % ``reset``, ``apply``, ``plotmat`` + % :type operation: string % - % Callback routines for mancoreg.m % Change LOG % @@ -23,12 +32,12 @@ function mancoregCallbacks(operation, varargin) case 'move' % Update the position of the bottom (source) image according to user settings - moveImage(); % Toggles between source and target display in bottom window case 'toggle_off' toggleOff(); + case 'toggle_on' toggleOn(); @@ -119,6 +128,7 @@ function toggleOn() st.vols{2}.window = mancoregvar.sourceimage.window; st.vols{2}.area = mancoregvar.sourceimage.area; spm_orthviews('redraw'); + end end diff --git a/lib/spmup b/lib/spmup index 198c980d..d889eb49 160000 --- a/lib/spmup +++ b/lib/spmup @@ -1 +1 @@ -Subproject commit 198c980d6d7520b1a996f0e56269e2ceab72cc83 +Subproject commit d889eb49454f029f1b92ac2e3ebb8c78c6b427c5 diff --git a/lib/utils/plot_power_spectra_of_GLM_residuals.m b/lib/utils/plot_power_spectra_of_GLM_residuals.m new file mode 100644 index 00000000..e4b55182 --- /dev/null +++ b/lib/utils/plot_power_spectra_of_GLM_residuals.m @@ -0,0 +1,191 @@ +function plot_power_spectra_of_GLM_residuals(path_to_results, TR, cutoff_freq, assumed_exper_freq, true_exper_freq) + + % -By Wiktor Olszowy, University of Cambridge, olszowyw@gmail.com + % + % -Written following study + % 'Accurate autocorrelation modeling substantially improves fMRI reliability' + % -https://www.nature.com/articles/s41467-019-09230-w.pdf + % -May 2018 + % + % -Given fMRI task results in AFNI, FSL or SPM, + % this script plots power spectra of GLM residuals. + % -If there is strong structure visible in the GLM residuals + % the power spectra are not flat), the first level results are likely confounded. + + % -tested on Linux + % -you need on your path >= MATLAB 2017b, AFNI and FSL + + % -specify the default values for the cutoff frequency used by the high-pass filter, + % -for the assumed experimental design frequency + % and for the true experimental design frequency; + % -10 chosen, as it is beyond the plotted frequencies + + if ~exist('cutoff_freq', 'var') + cutoff_freq = 10; + end + if ~exist('assumed_exper_freq', 'var') + assumed_exper_freq = 10; + end + if ~exist('true_exper_freq', 'var') + true_exper_freq = 10; + end + + Fontsize = 10; + + % -Fast Fourier Transform (FFT) will pad the voxel-wise time series + % to that length with trailing zeros (if no. of time points lower) + % or truncate to that length (if no. of time points higher) + fft_n = 512; + + initial_path = pwd; + cd(path_to_results); + + % -read GLM residuals + + % -for AFNI + AFNI_res4d_name = dir('whitened_errts.*.BRIK'); + if length(AFNI_res4d_name) > 0 + AFNI_res4d_name = AFNI_res4d_name.name; + system(['3dcalc -a ' AFNI_res4d_name ' -expr "a" -prefix res4d.nii']); + res4d = niftiread('res4d.nii'); + + % -for FSL + elseif exist('stats/res4d.nii.gz', 'file') == 2 + res4d = niftiread('stats/res4d.nii.gz'); + elseif exist('stats/res4d.nii', 'file') == 2 + res4d = niftiread('stats/res4d.nii'); + + % -for SPM + elseif exist('Res_0001.nii', 'file') == 2 + + SPM_res4d_name = dir('Res_*.nii'); + SPM_res4d_all = ''; + + % -in case we get a crash, we rely on FSL functions + try + SPM_res4d_all = char({SPM_res4d_name.name}'); + spm_file_merge(SPM_res4d_all, 'res4d.nii'); + res4d = spm_read_vols(spm_vol('res4d.nii')); + + catch + for i = 1:length(SPM_res4d_name) + SPM_res4d_all = [SPM_res4d_all ' ' SPM_res4d_name(i).name]; + end + system(['fslmerge -t res4d ' SPM_res4d_all]); + res4d = niftiread('res4d.nii.gz'); + + end + + else + + disp(['No GLM residuals found! ', ... + 'If you run SPM, remember to put command ', ... + 'VRes = spm_write_residuals(SPM, NaN) at the end of the SPM script. ', ... + 'Otherwise, SPM by default deletes the GLM residuals.']); + return + + end + + % -calculate the power spectra + + dims = size(res4d); + power_spectra_of_GLM_residuals = zeros(fft_n, 1); + no_of_brain_voxels = 0; + + for i1 = 1:dims(1) + + for i2 = 1:dims(2) + + for i3 = 1:dims(3) + + ts = squeeze(res4d(i1, i2, i3, :)); + + if sum(isnan(ts)) == 0 + + if std(ts) ~= 0 + + % -make signal variance equal to 1 + ts = ts / (std(ts) + eps); + + % -compute the discrete Fourier transform (DFT) + DFT = fft(ts, fft_n); + + power_spectra_of_GLM_residuals = power_spectra_of_GLM_residuals + ... + ((abs(DFT)).^2) / min(dims(4), fft_n); + + no_of_brain_voxels = no_of_brain_voxels + 1; + + end + + end + + end + + end + + end + + % -average power spectra over all brain voxels + power_spectra_of_GLM_residuals = power_spectra_of_GLM_residuals / no_of_brain_voxels; + + % -save the power spectra + clear res4d; + save('power_spectra_of_GLM_residuals.mat'); + + % -make the plot + figure('rend', 'painters', 'pos', [0 0 600 400], 'Visible', 'on'); + hold on; + + f = linspace(0, 0.5 / TR, 257); + max_y = max(power_spectra_of_GLM_residuals); + h1 = plot(f, power_spectra_of_GLM_residuals(1:257), 'r'); + h4 = plot(cutoff_freq, 0, 'k*'); + h5 = plot(cutoff_freq, max_y, 'k*'); + h6 = plot(assumed_exper_freq, 0, 'c*'); + h7 = plot(assumed_exper_freq, max_y, 'c*'); + h8 = plot(true_exper_freq, 0, 'm*'); + h9 = plot(true_exper_freq, max_y, 'm*'); + h10 = plot([0 0.5 / TR], [1 1], 'k--'); + hx = xlabel({' ', 'Frequency [Hz]', ' '}); + htitle = title('Power spectra of GLM residuals', 'interpreter', 'none'); + + xlim([0 0.5 / TR]); + ylim([0 max_y]); + + set([h1 h10], 'LineWidth', 1.25); + set([hx htitle], 'FontSize', Fontsize); + set(gca, 'XTick', linspace(0, 0.5 / TR, 6)); + set(gca, 'XTickLabel', round(linspace(0, 0.5 / TR, 6), 2)); + set(gca, 'FontSize', Fontsize); + + location = 'southeast'; + if (power_spectra_of_GLM_residuals(257) < 0) || (max_y > 2) + location = 'northeast'; + end + + to_plot = [h1 h10 h4 h6 h8]; + legend_content = { ... + 'Actual power spectrum', ... + 'Ideal power spectrum', ... + 'High pass filter frequency cutoff', ... + 'Assumed design frequency', ... + 'True design frequency'}; + + if any([cutoff_freq, assumed_exper_freq, true_exper_freq] == 10) + to_plot = [h1 h10]; + legend_content = { ... + 'Actual power spectrum', ... + 'Ideal power spectrum'}; + end + + legend(to_plot, legend_content, ... + 'box', 'off', ... + 'FontSize', Fontsize, ... + 'Location', location); + + figname = 'power_spectra_of_GLM_residuals'; + print (figname, '-dpng'); + + cd(initial_path); + +end diff --git a/lib/utils/resize_img.m b/lib/utils/resize_img.m new file mode 100644 index 00000000..fdd5407d --- /dev/null +++ b/lib/utils/resize_img.m @@ -0,0 +1,149 @@ +function resize_img(imnames, Voxdim, BB, ismask) + % resize_img -- resample images to have specified voxel dims and BBox + % + % resize_img(imnames, voxdim, bb, ismask) + % + % Output images will be prefixed with 'r', and will have voxel dimensions + % equal to voxdim. Use NaNs to determine voxdims from transformation matrix + % of input image(s). + % If bb == nan(2,3), bounding box will include entire original image + % Origin will move appropriately. Use world_bb to compute bounding box from + % a different image. + % + % Pass ismask=true to re-round binary mask values (avoid + % growing/shrinking masks due to linear interp) + % + % See also voxdim, world_bb + + % Based on John Ashburner's reorient.m + % http://www.sph.umich.edu/~nichols/JohnsGems.html#Gem7 + % http://www.sph.umich.edu/~nichols/JohnsGems5.html#Gem2 + % Adapted by Ged Ridgway -- email bugs to drc.spm@gmail.com + + % This version doesn't check spm_flip_analyze_images -- the handedness of + % the output image and matrix should match those of the input. + + % DONWLOADED FROM ON THE 2021 02 17 + % https://blogs.warwick.ac.uk/nichols/entry/spm5_gem_3/ + + % Check spm version: + if exist('spm_select', 'file') % should be true for spm5 + spm5 = 1; + elseif exist('spm_get', 'file') % should be true for spm2 + spm5 = 0; + else + error('Can''t find spm_get or spm_select; please add SPM to path'); + end + + % spm_defaults; + + % prompt for missing arguments + if ~exist('imnames', 'var') || isempty(char(imnames)) + if spm5 + imnames = spm_select(inf, 'image', 'Choose images to resize'); + else + imnames = spm_get(inf, 'img', 'Choose images to resize'); + end + end + % check if inter fig already open, don't close later if so... + Fint = spm_figure('FindWin', 'Interactive'); + Fnew = []; + if ~exist('Voxdim', 'var') || isempty(Voxdim) + Fnew = spm_figure('GetWin', 'Interactive'); + Voxdim = spm_input('Vox Dims (NaN for "as input")? ', ... + '+1', 'e', '[nan nan nan]', 3); + end + if ~exist('BB', 'var') || isempty(BB) + Fnew = spm_figure('GetWin', 'Interactive'); + BB = spm_input('Bound Box (NaN => original)? ', ... + '+1', 'e', '[nan nan nan; nan nan nan]', [2 3]); + end + if ~exist('ismask', 'var') + ismask = false; + end + if isempty(ismask) + ismask = false; + end + + % reslice images one-by-one + vols = spm_vol(imnames); + for V = vols' + % (copy to allow defaulting of NaNs differently for each volume) + voxdim = Voxdim; + bb = BB; + % default voxdim to current volume's voxdim, (from mat parameters) + if any(isnan(voxdim)) + vprm = spm_imatrix(V.mat); + vvoxdim = vprm(7:9); + voxdim(isnan(voxdim)) = vvoxdim(isnan(voxdim)); + end + voxdim = voxdim(:)'; + + mn = bb(1, :); + mx = bb(2, :); + % default BB to current volume's + if any(isnan(bb(:))) + vbb = world_bb(V); + vmn = vbb(1, :); + vmx = vbb(2, :); + mn(isnan(mn)) = vmn(isnan(mn)); + mx(isnan(mx)) = vmx(isnan(mx)); + end + + % voxel [1 1 1] of output should map to BB mn + % (the combination of matrices below first maps [1 1 1] to [0 0 0]) + mat = spm_matrix([mn 0 0 0 voxdim]) * spm_matrix([-1 -1 -1]); + % voxel-coords of BB mx gives number of voxels required + % (round up if more than a tenth of a voxel over) + imgdim = ceil(mat \ [mx 1]' - 0.1)'; + + % output image + VO = V; + [pth, nam, ext] = fileparts(V.fname); + VO.fname = fullfile(pth, ['r' nam ext]); + VO.dim(1:3) = imgdim(1:3); + VO.mat = mat; + VO = spm_create_vol(VO); + spm_progress_bar('Init', imgdim(3), 'reslicing...', 'planes completed'); + for i = 1:imgdim(3) + M = inv(spm_matrix([0 0 -i]) * inv(VO.mat) * V.mat); + img = spm_slice_vol(V, M, imgdim(1:2), 1); % (linear interp) + if ismask + img = round(img); + end + spm_write_plane(VO, img, i); + spm_progress_bar('Set', i); + end + spm_progress_bar('Clear'); + end + % call spm_close_vol if spm2 + if ~spm5 + spm_close_vol(VO); + end + if isempty(Fint) && ~isempty(Fnew) + % interactive figure was opened by this script, so close it again. + close(Fnew); + end + disp('Done.'); + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function bb = world_bb(V) + % world-bb -- get bounding box in world (mm) coordinates + + d = V.dim(1:3); + % corners in voxel-space + c = [1 1 1 1 + 1 1 d(3) 1 + 1 d(2) 1 1 + 1 d(2) d(3) 1 + d(1) 1 1 1 + d(1) 1 d(3) 1 + d(1) d(2) 1 1 + d(1) d(2) d(3) 1]'; + % corners in world-space + tc = V.mat(1:3, 1:4) * c; + + % bounding box (world) min and max + mn = min(tc, [], 2)'; + mx = max(tc, [], 2)'; + bb = [mn; mx]; diff --git a/manualTests/systemTestDS114.m b/manualTests/systemTestDS114.m new file mode 100644 index 00000000..b9dfb487 --- /dev/null +++ b/manualTests/systemTestDS114.m @@ -0,0 +1,73 @@ +% (C) Copyright 2019 Remi Gau + +% Script to run to check that the whole pipeline works fine with different +% options encoded in json files +% +% - default options (that is realign & unwarp + space = MNI) +% - indidivual space +% - realign only +% - indidivual space + realign only +% +% +% +% Rudimentary attempt of a "system-level" "smoke-test" of the "happy path"... +% + +clear; +clc; + +% Smoothing to apply +FWHM = 6; + +% directory with this script becomes the current directory +WD = fullfile(fileparts(mfilename('fullpath')), '..', 'demos', 'openneuro'); +cd(WD); + +% we add all the subfunctions that are in the sub directories +addpath(genpath(fullfile(WD, '..', '..', 'src'))); +addpath(genpath(fullfile(WD, '..', '..', 'lib'))); + +checkDependencies(); + +%% Set up +delete(fullfile(pwd, 'options_task-*date-*.json')); + +optionsFilesList = { ... + 'options_task-linebisection.json'; ... + 'options_task-linebisection_unwarp-0.json'; ... + 'options_task-linebisection_unwarp-0_space-individual.json'; ... + 'options_task-linebisection_space-individual.json'}; + +% run the pipeline with different options +for iOption = 1:size(optionsFilesList, 1) + + fprintf(1, repmat('\n', 1, 5)); + + optionJsonFile = fullfile(WD, 'options', optionsFilesList{iOption}); + opt = loadAndCheckOptions(optionJsonFile); + + %% Run batches + + reportBIDS(opt); + + bidsCopyRawFolder(opt, 1); + + bidsSTC(opt); + + bidsSpatialPrepro(opt); + + % The following do not run on octave for now (because of spmup) + anatomicalQA(opt); + bidsResliceTpmToFunc(opt); + functionalQA(opt); + + bidsSmoothing(FWHM, opt); + + % The following crash on Travis CI + bidsFFX('specifyAndEstimate', opt, FWHM); + bidsFFX('contrasts', opt, FWHM); + bidsResults(opt, FWHM); + + cd(WD); + +end diff --git a/manualTests/systemTestMoAE.m b/manualTests/systemTestMoAE.m new file mode 100644 index 00000000..a2b87437 --- /dev/null +++ b/manualTests/systemTestMoAE.m @@ -0,0 +1,83 @@ +% (C) Copyright 2019 Remi Gau + +% Script to run to check that the whole pipeline works fine with different +% options encoded in json files +% +% - default options (that is realign & unwarp + space = MNI) +% - indidivual space +% - realign only +% - indidivual space + realign only +% +% Rudimentary attempt of a "system-level" "smoke-test" of the "happy path"... +% + +clear; +clc; + +% Smoothing to apply +FWHM = 6; + +% URL of the data set to download +URL = 'http://www.fil.ion.ucl.ac.uk/spm/download/data/MoAEpilot/MoAEpilot.bids.zip'; + +% directory with this script becomes the current directory +WD = fullfile(fileparts(mfilename('fullpath')), '..', 'demos', 'MoAE'); +cd(WD); + +% we add all the subfunctions that are in the sub directories +addpath(genpath(fullfile(WD, '..', '..', 'src'))); +addpath(genpath(fullfile(WD, '..', '..', 'lib'))); + +%% Get data +fprintf('%-10s:', 'Downloading dataset...'); +urlwrite(URL, 'MoAEpilot.zip'); +fprintf(1, ' Done\n\n'); + +fprintf('%-10s:', 'Unzipping dataset...'); +unzip('MoAEpilot.zip', fullfile(WD, 'output')); +fprintf(1, ' Done\n\n'); + +checkDependencies(); + +%% Set up +delete(fullfile(pwd, 'options_task-*date-*.json')); + +optionsFilesList = { ... + 'options_task-auditory.json'; ... + 'options_task-auditory_unwarp-0.json'; ... + 'options_task-auditory_unwarp-0_space-individual.json'; ... + 'options_task-auditory_space-individual.json'}; + +% run the pipeline with different options +for iOption = 1:size(optionsFilesList, 1) + + fprintf(1, repmat('\n', 1, 5)); + + optionJsonFile = optionsFilesList{iOption}; + opt = loadAndCheckOptions(optionJsonFile); + + %% Run batches + + reportBIDS(opt); + + bidsCopyRawFolder(opt, 1); + + bidsSTC(opt); + + bidsSpatialPrepro(opt); + + % The following do not run on octave for now (because of spmup) + anatomicalQA(opt); + bidsResliceTpmToFunc(opt); + functionalQA(opt); + + bidsSmoothing(FWHM, opt); + + % The following crash on Travis CI + bidsFFX('specifyAndEstimate', opt, FWHM); + bidsFFX('contrasts', opt, FWHM); + bidsResults(opt, FWHM); + + cd(WD); + +end diff --git a/manualTests/test_bidsSegmentSkullStrip.m b/manualTests/test_bidsSegmentSkullStrip.m new file mode 100644 index 00000000..f10ea4b2 --- /dev/null +++ b/manualTests/test_bidsSegmentSkullStrip.m @@ -0,0 +1,29 @@ +% (C) Copyright 2020 CPP BIDS SPM-pipeline developers + +function test_suite = test_bidsSegmentSkullStrip %#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_bidsSegmentSkullStripBasic() + + % smoke test + + % directory with this script becomes the current directory + opt.dataDir = fullfile( ... + fileparts(mfilename('fullpath')), ... + '..', 'demos', 'MoAE', 'output', 'MoAEpilot'); + % task to analyze + opt.taskName = 'auditory'; + opt = checkOptions(opt); + + %% Run batches + checkDependencies(); + reportBIDS(opt); + bidsCopyRawFolder(opt, 1); + bidsSegmentSkullStrip(opt); + +end diff --git a/manualTests/test_setBatchCoregistrationFmap.m b/manualTests/test_setBatchCoregistrationFmap.m new file mode 100644 index 00000000..eaa1f88c --- /dev/null +++ b/manualTests/test_setBatchCoregistrationFmap.m @@ -0,0 +1,65 @@ +% (C) Copyright 2020 CPP BIDS SPM-pipeline developers + +function test_suite = test_setBatchCoregistrationFmap %#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_setBatchCoregistrationFmapBasic() + + subID = '01'; + + opt.taskName = 'vismotion'; + opt.derivativesDir = fullfile(fileparts(mfilename('fullpath')), 'dummyData'); + + opt = checkOptions(opt); + + [~, opt, BIDS] = getData(opt); + + matlabbatch = []; + matlabbatch = setBatchCoregistrationFmap(matlabbatch, BIDS, opt, subID); + + directory = fullfile(opt.derivativesDir, ... + 'sub-01', ... + 'ses-01'); + ref = fullfile(directory, ... + 'func', ... + 'mean_sub-01_ses-01_task-vismotion_run-1_bold.nii'); + + expectedbatch = []; + + src = fullfile(directory, ... + 'fmap', ... + 'sub-01_ses-01_run-2_magnitude1.nii'); + other = {fullfile(directory, ... + 'fmap', ... + 'sub-01_ses-01_run-2_magnitude2.nii'); ... + fullfile(directory, ... + 'fmap', ... + 'sub-01_ses-01_run-2_phasediff.nii')}; + expectedbatch{end + 1}.spm.spatial.coreg.estimate.ref = { ref }; + expectedbatch{end}.spm.spatial.coreg.estimate.source = { src }; + expectedbatch{end}.spm.spatial.coreg.estimate.other = other; + + directory = fullfile(opt.derivativesDir, ... + 'sub-01', ... + 'ses-02'); + src = fullfile(directory, ... + 'fmap', ... + 'sub-01_ses-02_run-2_magnitude1.nii'); + other = {fullfile(directory, ... + 'fmap', ... + 'sub-01_ses-02_run-2_magnitude2.nii'); ... + fullfile(directory, ... + 'fmap', ... + 'sub-01_ses-02_run-2_phasediff.nii')}; + expectedbatch{end + 1}.spm.spatial.coreg.estimate.ref = { ref }; + expectedbatch{end}.spm.spatial.coreg.estimate.source = { src }; + expectedbatch{end}.spm.spatial.coreg.estimate.other = other; + + assertEqual(matlabbatch, expectedbatch); + +end diff --git a/manualTests/test_setBatchCreateVDMs.m b/manualTests/test_setBatchCreateVDMs.m new file mode 100644 index 00000000..314008e5 --- /dev/null +++ b/manualTests/test_setBatchCreateVDMs.m @@ -0,0 +1,32 @@ +% (C) Copyright 2020 CPP BIDS SPM-pipeline developers + +function test_suite = test_setBatchCreateVDMs %#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_setBatchCreateVDMsBasic() + + subID = '01'; + + opt.taskName = 'vismotion'; + opt.derivativesDir = fullfile(fileparts(mfilename('fullpath')), 'dummyData'); + opt.taskName = 'vismotion'; + + opt = checkOptions(opt); + + [~, opt, BIDS] = getData(opt); + + matlabbatch = []; + matlabbatch = setBatchCreateVDMs(matlabbatch, BIDS, opt, subID); + + % matlabbatch{1}.spm.tools.fieldmap.calculatevdm.subj + + % expectedBatch = returnExpectedBatch(refImage); + % + % assertEqual(matlabbatch, expectedBatch); + +end diff --git a/docs/requirements.txt b/requirements.txt similarity index 82% rename from docs/requirements.txt rename to requirements.txt index 42ea70da..992d0ece 100644 --- a/docs/requirements.txt +++ b/requirements.txt @@ -2,4 +2,4 @@ Sphinx sphinxcontrib-matlabdomain sphinxcontrib-napoleon sphinx_rtd_theme -miss_hit \ No newline at end of file +miss_hit==0.9.15 \ No newline at end of file diff --git a/src/QA/anatomicalQA.m b/src/QA/anatomicalQA.m index 4016af5a..25d70fa9 100644 --- a/src/QA/anatomicalQA.m +++ b/src/QA/anatomicalQA.m @@ -1,7 +1,21 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers function anatomicalQA(opt) - % anatomicalQA(opt) + % + % Computes several metrics for anatomical image. + % + % USAGE:: + % + % anatomicalQA(opt) + % + % :param opt: Options chosen for the analysis. See ``checkOptions()``. + % :type opt: structure + % + + if isOctave() + warning('\nanatomicalQA is not yet supported on Octave. This step will be skipped.'); + return + end % if input has no opt, load the opt.mat file if nargin < 1 @@ -18,7 +32,7 @@ function anatomicalQA(opt) groupName = group(iGroup).name; - for iSub = 1:group(iGroup).numSub + parfor iSub = 1:group(iGroup).numSub subID = group(iGroup).subNumber{iSub}; @@ -38,7 +52,7 @@ function anatomicalQA(opt) % This is useful to check coregistration worked fine anatQA = spmup_anatQA(anatImage, TPMs(1, :), TPMs(2, :)); %#ok<*NASGU> - anatQA.avgDistToSurf = spmup_comp_dist2surf(fullfile(anatDataDir, anatImage)); + anatQA.avgDistToSurf = spmup_comp_dist2surf(anatImage); spm_jsonwrite( ... strrep(anatImage, '.nii', '_qa.json'), ... diff --git a/src/QA/functionalQA.m b/src/QA/functionalQA.m index 823e7ac5..f804344b 100644 --- a/src/QA/functionalQA.m +++ b/src/QA/functionalQA.m @@ -1,11 +1,33 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers function functionalQA(opt) - % functionalQA(opt) % - % For functional data, QA is consists in getting temporal SNR and then + % For functional data, QA consists in getting temporal SNR and then % check for motion - here we also compute additional regressors to - % account for motion + % account for motion. + % + % USAGE:: + % + % functionalQA(opt) + % + % :param opt: Options chosen for the analysis. See ``checkOptions()``. + % :type opt: structure + % + % ASSUMPTIONS: + % + % The previous step must have already been run: + % + % - the functional images have been realigned and resliced using etiher + % ``bidsSpatialPrepro()``, ``bidsRealignUnwarp()``, ``bidsRealignReslice()`` + % - the quality analysis of the anatomical data has been done with ``anatomicalQA()`` + % - the tissue probability maps have been generated in the "native" space of each subject + % (using ``bidsSpatialPrepro()`` or ``bidsSegmentSkullStrip()``) and have been + % resliced to the dimension of the functional with ``bidsResliceTpmToFunc()`` + + if isOctave() + warning('\nfunctionalQA is not yet supported on Octave. This step will be skipped.'); + return + end % if input has no opt, load the opt.mat file if nargin < 1 @@ -55,7 +77,7 @@ function functionalQA(opt) runs{iRun}, ... opt); - prefix = getPrefix('smoothing_space-individual', opt); + prefix = getPrefix('funcQA', opt); funcImage = validationInputFile(subFuncDataDir, fileName, prefix); % sanity check that all images are in the same space. @@ -64,12 +86,19 @@ function functionalQA(opt) fMRIQA = computeFuncQAMetrics(funcImage, TPMs, anatQA.avgDistToSurf, opt); - spm_jsonwrite( ... - fullfile( ... - subFuncDataDir, ... - strrep(fileName, '.nii', '_qa.json')), ... - fMRIQA, ... - struct('indent', ' ')); + % TODO + % find an ouput format that is leaner than a 3 Gb json file!!! + % spm_jsonwrite( ... + % fullfile( ... + % subFuncDataDir, ... + % strrep(fileName, '.nii', '_qa.json')), ... + % fMRIQA, ... + % struct('indent', ' ')); + % save( ... + % fullfile( ... + % subFuncDataDir, ... + % strrep(fileName, '.nii', '_qa.mat')), ... + % 'fMRIQA'); outputFiles = spmup_first_level_qa( ... funcImage, ... @@ -95,10 +124,16 @@ function functionalQA(opt) createDataDictionary(subFuncDataDir, fileName, size(confounds, 2)); % create carpet plot - % spmup_timeseriesplot(P, c1, c2, c3, ... - % 'motion', 'on', ... - % 'nuisances', 'on', ... - % 'correlation', 'on'); + + % horrible hack to prevent the "abrupt" way spmup_volumecorr crashes + % if nansum is not there + if exist('nansum', 'file') == 2 + spmup_timeseriesplot(funcImage, TPMs(1, :), TPMs(2, :), TPMs(3, :), ... + 'motion', 'on', ... + 'nuisances', 'on', ... + 'correlation', 'on', ... + 'makefig', 'on'); + end end @@ -114,7 +149,7 @@ function functionalQA(opt) [subFuncDataDir, fileName, ext] = spm_fileparts(funcImage); - prefix = getPrefix('smoothing_space-individual', opt); + prefix = getPrefix('funcQA', opt); fMRIQA.tSNR = spmup_temporalSNR( ... funcImage, ... diff --git a/src/batches/setBatch3Dto4D.m b/src/batches/setBatch3Dto4D.m new file mode 100644 index 00000000..90e761bb --- /dev/null +++ b/src/batches/setBatch3Dto4D.m @@ -0,0 +1,52 @@ +% (C) Copyright 2020 CPP BIDS SPM-pipeline developers + +function matlabbatch = setBatch3Dto4D(matlabbatch, volumesList, RT, outputName, dataType) + % + % Set the batch for 3D to 4D conversion + % + % USAGE:: + % + % matlabbatch = setBatch3Dto4D(matlabbatch, volumesList, RT, [outputName], [dataType]) + % + % :param matlabbatch: + % :type matlabbatch: structure + % :param volumesList: List of volumes to be converted in a single 4D brain + % :type volumesList: array + % :param outputName: The string that will be used to save the 4D brain + % :type outputName: string + % :param dataType: It identifies the data format conversion + % :type dataType: integer + % :param RT: It identifies the TR in seconds of the volumes + % to be written in the 4D file header + % :type RT: float + % + % :returns: - :matlabbatch: (structure) The matlabbatch ready to run the spm job + % + % ``dataType``: + % + % - 0: SAME + % - 2: UINT8 - unsigned char + % - 4: INT16 - signed short + % - 8: INT32 - signed int + % - 16: FLOAT32 - single prec. float + % - 64: FLOAT64 - double prec. float + % + + if nargin < 5 || isempty(dataType) + dataType = 0; + end + + if nargin < 4 || isempty(outputName) + outputName = deblank(volumesList(1, :)); + [pth, filename, ext] = spm_fileparts(outputName); + outputName = fullfile(pth, [filename, '_4D', ext]); + end + + printBatchName('3D to 4D conversion'); + + matlabbatch{end + 1}.spm.util.cat.vols = volumesList; + matlabbatch{end}.spm.util.cat.name = outputName; + matlabbatch{end}.spm.util.cat.dtype = dataType; + matlabbatch{end}.spm.util.cat.RT = RT; + +end diff --git a/src/batches/setBatchComputeVDM.m b/src/batches/setBatchComputeVDM.m index b464a2e0..00321ae4 100644 --- a/src/batches/setBatchComputeVDM.m +++ b/src/batches/setBatchComputeVDM.m @@ -1,6 +1,22 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers function matlabbatch = setBatchComputeVDM(matlabbatch, fmapType, refImage) + % + % Short description of what the function does goes here. + % + % USAGE:: + % + % matlabbatch = setBatchComputeVDM(matlabbatch, fmapType, refImage) + % + % :param matlabbatch: list of SPM batches + % :type matlabbatch: structure + % :param fmapType: + % :type fmapType: + % :param refImage: Reference image + % :type refImage: + % + % :returns: - :matlabbatch: (structure) The matlabbatch ready to run the spm job + % % matlabbatch = setBatchComputeVDM(type) % % adapted from spmup get_FM_workflow.m (@ commit @@ -12,7 +28,7 @@ matlabbatch{end}.spm.tools.fieldmap.calculatevdm.subj(1).data.presubphasemag.magnitude = ''; case 'phase&mag' - matlabbatch{end}.spm.tools.fieldmap.calculatevdm.subj(1).data.phasemag.shortphase = ''; + matlabbatch{end + 1}.spm.tools.fieldmap.calculatevdm.subj(1).data.phasemag.shortphase = ''; matlabbatch{end}.spm.tools.fieldmap.calculatevdm.subj(1).data.phasemag.shortmag = ''; matlabbatch{end}.spm.tools.fieldmap.calculatevdm.subj(1).data.phasemag.longphase = ''; matlabbatch{end}.spm.tools.fieldmap.calculatevdm.subj(1).data.phasemag.longmag = ''; diff --git a/src/batches/setBatchContrasts.m b/src/batches/setBatchContrasts.m new file mode 100644 index 00000000..ea047235 --- /dev/null +++ b/src/batches/setBatchContrasts.m @@ -0,0 +1,27 @@ +% (C) Copyright 2019 CPP BIDS SPM-pipeline developers + +function matlabbatch = setBatchContrasts(matlabbatch, spmMatFile, consess) + % + % Short description of what the function does goes here. + % + % USAGE:: + % + % matlabbatch = setBatchContrasts(matlabbatch, spmMatFile, consess) + % + % :param matlabbatch: + % :type matlabbatch: structure + % :param spmMatFile: + % :type spmMatFile: string + % :param consess: + % :type consess: cell + % + % :returns: - :matlabbatch: (structure) + % + + printBatchName('contrasts specification'); + + matlabbatch{end + 1}.spm.stats.con.spmmat = spmMatFile; + matlabbatch{end}.spm.stats.con.consess = consess; + matlabbatch{end}.spm.stats.con.delete = 1; + +end diff --git a/src/batches/setBatchCoregistration.m b/src/batches/setBatchCoregistration.m index 9562319e..8fb4d51f 100644 --- a/src/batches/setBatchCoregistration.m +++ b/src/batches/setBatchCoregistration.m @@ -1,11 +1,28 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers function matlabbatch = setBatchCoregistration(matlabbatch, ref, src, other) + % + % Set the batch for coregistering the source images into the reference image + % + % USAGE:: + % + % matlabbatch = setBatchCoregistration(matlabbatch, ref, src, other) + % + % :param matlabbatch: list of SPM batches + % :type matlabbatch: structure + % :param ref: Reference image + % :type ref: string + % :param src: Source image + % :type src: string + % :param other: ? + % :type other: cell string + % + % :returns: - :matlabbatch: (structure) The matlabbatch ready to run the spm job + % % matlabbatch = setBatchCoregistrationGeneral(matlabbatch, ref, src, other) % - % ref: string - % src: string - % other: cell string + + printBatchName('coregistration'); matlabbatch{end + 1}.spm.spatial.coreg.estimate.ref = { ref }; matlabbatch{end}.spm.spatial.coreg.estimate.source = { src }; diff --git a/src/batches/setBatchCoregistrationFmap.m b/src/batches/setBatchCoregistrationFmap.m index 98a913cb..ca220f41 100644 --- a/src/batches/setBatchCoregistrationFmap.m +++ b/src/batches/setBatchCoregistrationFmap.m @@ -1,50 +1,68 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers -function matlabbatch = setBatchCoregistrationFmap(BIDS, opt, subID) +function matlabbatch = setBatchCoregistrationFmap(matlabbatch, BIDS, opt, subID) + % + % Set the batch for the coregistration of field maps + % + % USAGE:: + % + % matlabbatch = setBatchCoregistrationFmap(matlabbatch, BIDS, opt, subID) + % + % :param BIDS: BIDS layout returned by ``getData``. + % :type BIDS: structure + % :param opt: structure or json filename containing the options. See + % ``checkOptions()`` and ``loadAndCheckOptions()``. + % :type opt: structure + % :param subID: subject ID + % :type subID: string + % + % :returns: - :matlabbatch: (structure) The matlabbatch ready to run the spm job + % % TODO - % assumes all the fieldmap relate to the current task - % - use the "for" metadata field % - implement for 'phase12', 'fieldmap', 'epi' - fprintf(1, ' FIELDMAP WORKFLOW: COREGISTERING FIELD MAPS TO FIRST FUNC IMAGE\n'); + printBatchName('coregister fieldmaps data to functional'); - % Create rough mean of the 1rst run to improve SNR for coregistration - % TODO use the slice timed EPI if STC was used ? + % Use a rough mean of the 1rst run to improve SNR for coregistration + % created by spmup [sessions, nbSessions] = getInfo(BIDS, subID, opt, 'Sessions'); runs = getInfo(BIDS, subID, opt, 'Runs', sessions{1}); [fileName, subFuncDataDir] = getBoldFilename(BIDS, subID, sessions{1}, runs{1}, opt); + refImage = validationInputFile(subFuncDataDir, fileName, 'mean_'); - spmup_basics(fullfile(subFuncDataDir, fileName), 'mean'); + for iSes = 1:nbSessions - refImage = fullfile(subFuncDataDir, ['mean_', fileName]); + runs = bids.query(BIDS, 'runs', ... + 'modality', 'fmap', ... + 'sub', subID, ... + 'ses', sessions{iSes}); - matlabbatch = []; + for iRun = 1:numel(runs) - for iSes = 1:nbSessions + metadata = bids.query(BIDS, 'metadata', ... + 'modality', 'fmap', ... + 'sub', subID, ... + 'ses', sessions{iSes}, ... + 'run', runs{iRun}); - runs = spm_BIDS(BIDS, 'runs', ... - 'modality', 'fmap', ... - 'sub', subID, ... - 'ses', sessions{iSes}); + if strfind(metadata.IntendedFor, opt.taskName) - for iRun = 1:numel(runs) + fmapFiles = bids.query(BIDS, 'data', ... + 'modality', 'fmap', ... + 'sub', subID, ... + 'ses', sessions{iSes}, ... + 'run', runs{iRun}); - % TODO - % - Move to getInfo - fmapFiles = spm_BIDS(BIDS, 'data', ... - 'modality', 'fmap', ... - 'sub', subID, ... - 'ses', sessions{iSes}, ... - 'run', runs{iRun}); + srcImage = strrep(fmapFiles{1}, 'phasediff', 'magnitude1'); - srcImage = strrep(fmapFiles{1}, 'phasediff', 'magnitude1'); + otherImages = cell(2, 1); + otherImages{1} = strrep(fmapFiles{1}, 'phasediff', 'magnitude2'); + otherImages{2} = fmapFiles{1}; - otherImages = cell(2, 1); - otherImages{1} = strrep(fmapFiles{1}, 'phasediff', 'magnitude2'); - otherImages{2} = fmapFiles{1}; + matlabbatch = setBatchCoregistration(matlabbatch, refImage, srcImage, otherImages); - matlabbatch = setBatchCoregistration(matlabbatch, refImage, srcImage, otherImages); + end end diff --git a/src/batches/setBatchCoregistrationFuncToAnat.m b/src/batches/setBatchCoregistrationFuncToAnat.m index 02984f87..f1b54fe5 100644 --- a/src/batches/setBatchCoregistrationFuncToAnat.m +++ b/src/batches/setBatchCoregistrationFuncToAnat.m @@ -1,8 +1,28 @@ -% (C) Copyright 2019 CPP BIDS SPM-pipeline developers - -function matlabbatch = setBatchCoregistrationFuncToAnat(matlabbatch, BIDS, subID, opt) - - fprintf(1, ' BUILDING SPATIAL JOB : COREGISTER\n'); +% (C) Copyright 2020 CPP BIDS SPM-pipeline developers + +function matlabbatch = setBatchCoregistrationFuncToAnat(matlabbatch, BIDS, opt, subID) + % + % Set the batch for corregistering the functional images to the + % anatomical image + % + % USAGE:: + % + % matlabbatch = setBatchCoregistrationFuncToAnat(matlabbatch, BIDS, subID, opt) + % + % :param matlabbatch: list of SPM batches + % :type matlabbatch: structure + % :param BIDS: BIDS layout returned by ``getData``. + % :type BIDS: structure + % :param opt: structure or json filename containing the options. See + % ``checkOptions()`` and ``loadAndCheckOptions()``. + % :type opt: structure + % :param subID: subject ID + % :type subID: string + % + % :returns: - :matlabbatch: (structure) The matlabbatch ready to run the spm job + % + + printBatchName('coregister functional data to anatomical'); matlabbatch{end + 1}.spm.spatial.coreg.estimate.ref(1) = ... cfg_dep('Named File Selector: Anatomical(1) - Files', ... @@ -15,12 +35,11 @@ % SOURCE IMAGE : DEPENDENCY FROM REALIGNEMENT % Mean Image - - meanImageToUse = 'rmean'; - otherImageToUse = 'cfiles'; - if strcmp(opt.space, 'individual') - meanImageToUse = 'meanuwr'; - otherImageToUse = 'uwrfiles'; + meanImageToUse = 'meanuwr'; + otherImageToUse = 'uwrfiles'; + if ~opt.realign.useUnwarp + meanImageToUse = 'rmean'; + otherImageToUse = 'cfiles'; end matlabbatch{end}.spm.spatial.coreg.estimate.source(1) = ... diff --git a/src/batches/setBatchCreateVDMs.m b/src/batches/setBatchCreateVDMs.m index 2d3e7fd8..54f4a08e 100644 --- a/src/batches/setBatchCreateVDMs.m +++ b/src/batches/setBatchCreateVDMs.m @@ -1,26 +1,29 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers -function matlabbatch = setBatchCreateVDMs(BIDS, opt, subID) +function matlabbatch = setBatchCreateVDMs(matlabbatch, BIDS, opt, subID) % % Short description of what the function does goes here. % % USAGE:: % - % matlabbatch = setBatchCreateVDMs(BIDS, opt, subID) + % matlabbatch = setBatchCreateVDMs(matlabbatch, BIDS, opt, subID) % - % :param BIDS: + % :param matlabbatch: + % :type matlabbatch: structure + % :param BIDS: BIDS layout returned by ``getData``. % :type BIDS: structure - % :param opt: + % :param opt: structure or json filename containing the options. See + % ``checkOptions()`` and ``loadAndCheckOptions()``. % :type opt: structure - % :param subID: + % :param subID: subject ID + % :type subID: string % - % :returns: - :matlabbatch: (structure) (dimension) + % :returns: - :matlabbatch: (structure) The matlabbatch ready to run the spm job % % TODO - % assumes all the fieldmap relate to the current task % - implement for 'phase12', 'fieldmap', 'epi' - fprintf(1, ' FIELDMAP WORKFLOW: CREATING VDMs \n'); + printBatchName('create voxel displacement map'); [sessions, nbSessions] = getInfo(BIDS, subID, opt, 'Sessions'); @@ -28,43 +31,54 @@ [fileName, subFuncDataDir] = getBoldFilename(BIDS, subID, sessions{1}, runs{1}, opt); refImage = validationInputFile(subFuncDataDir, fileName, 'mean_'); - matlabbatch = []; for iSes = 1:nbSessions - runs = spm_BIDS(BIDS, 'runs', ... - 'modality', 'fmap', ... - 'sub', subID, ... - 'ses', sessions{iSes}); + runs = bids.query(BIDS, 'runs', ... + 'modality', 'fmap', ... + 'sub', subID, ... + 'ses', sessions{iSes}); for iRun = 1:numel(runs) - matlabbatch = setBatchComputeVDM(matlabbatch, 'phasediff', refImage); + metadata = bids.query(BIDS, 'metadata', ... + 'modality', 'fmap', ... + 'sub', subID, ... + 'ses', sessions{iSes}, ... + 'run', runs{iRun}); - % TODO - % Move to getInfo ? - fmapFiles = spm_BIDS(BIDS, 'data', ... - 'modality', 'fmap', ... - 'sub', subID, ... - 'ses', sessions{iSes}, ... - 'run', runs{iRun}); + if strfind(metadata.IntendedFor, opt.taskName) - phaseImage = fmapFiles{1}; - matlabbatch{end}.spm.tools.fieldmap.calculatevdm.subj.data.presubphasemag.phase = ... - {phaseImage}; + matlabbatch = setBatchComputeVDM(matlabbatch, 'phasediff', refImage); - magnitudeImage = strrep(phaseImage, 'phasediff', 'magnitude1'); - matlabbatch{end}.spm.tools.fieldmap.calculatevdm.subj.data.presubphasemag.magnitude = ... - {magnitudeImage}; + % TODO + % Move to getInfo ? + fmapFiles = bids.query(BIDS, 'data', ... + 'modality', 'fmap', ... + 'sub', subID, ... + 'ses', sessions{iSes}, ... + 'run', runs{iRun}); - [echotimes, isEPI, totReadTime, blipDir] = getMetadataForVDM(BIDS, ... - subID, ... - sessions{iSes}, ... - runs{iRun}); + phaseImage = fmapFiles{1}; + matlabbatch{end}.spm.tools.fieldmap.calculatevdm.subj.data.presubphasemag.phase = ... + {phaseImage}; - matlabbatch{end}.spm.tools.fieldmap.calculatevdm.subj.defaults.defaultsval.et = echotimes; - matlabbatch{end}.spm.tools.fieldmap.calculatevdm.subj.defaults.defaultsval.tert = totReadTime; - matlabbatch{end}.spm.tools.fieldmap.calculatevdm.subj.defaults.defaultsval.blipdir = blipDir; - matlabbatch{end}.spm.tools.fieldmap.calculatevdm.subj.defaults.defaultsval.epifm = isEPI; + magnitudeImage = strrep(phaseImage, 'phasediff', 'magnitude1'); + matlabbatch{end}.spm.tools.fieldmap.calculatevdm.subj.data.presubphasemag.magnitude = ... + {magnitudeImage}; + + [echotimes, isEPI, totReadTime, blipDir] = getMetadataForVDM(BIDS, ... + subID, ... + sessions{iSes}, ... + runs{iRun}); + + defaultsval = matlabbatch{end}.spm.tools.fieldmap.calculatevdm.subj.defaults.defaultsval; + defaultsval.et = echotimes; + defaultsval.tert = totReadTime; + defaultsval.blipdir = blipDir; + defaultsval.epifm = isEPI; + matlabbatch{end}.spm.tools.fieldmap.calculatevdm.subj.defaults.defaultsval = defaultsval; + + end end @@ -75,11 +89,11 @@ function varargout = getMetadataForVDM(BIDS, subID, sessionID, runID) % get metadata fmap and its associated func files - fmapMetadata = spm_BIDS(BIDS, 'metadata', ... - 'modality', 'fmap', ... - 'sub', subID, ... - 'ses', sessionID, ... - 'run', runID); + fmapMetadata = bids.query(BIDS, 'metadata', ... + 'modality', 'fmap', ... + 'sub', subID, ... + 'ses', sessionID, ... + 'run', runID); if numel(fmapMetadata) > 1 fmapMetadata = fmapMetadata{1}; end @@ -111,7 +125,7 @@ isEPI = 0; if isfield(fmapMetadata, 'PulseSequenceType') && ... - sum(strfind(fmapMetadata.PulseSequenceType, 'EPI')) ~= 0 + sum(strfind(fmapMetadata.PulseSequenceType, 'EPI')) ~= 0 isEPI = 1; end diff --git a/src/batches/setBatchEstimateModel.m b/src/batches/setBatchEstimateModel.m new file mode 100644 index 00000000..73601a25 --- /dev/null +++ b/src/batches/setBatchEstimateModel.m @@ -0,0 +1,59 @@ +% (C) Copyright 2019 CPP BIDS SPM-pipeline developers + +function matlabbatch = setBatchEstimateModel(matlabbatch, grpLvlCon, opt) + % + % Short description of what the function does goes here. + % + % USAGE:: + % + % matlabbatch = setBatchEstimateModel(matlabbatch, grpLvlCon) + % + % :param matlabbatch: + % :type matlabbatch: structure + % :param grpLvlCon: + % :type grpLvlCon: + % + % :returns: - :matlabbatch: (structure) + % + + switch nargin + + case 1 + + printBatchName('estimate subject level fmri model'); + + spmMatFile = cfg_dep( ... + 'fMRI model specification SPM file', ... + substruct( ... + '.', 'val', '{}', {1}, ... + '.', 'val', '{}', {1}, ... + '.', 'val', '{}', {1}), ... + substruct('.', 'spmmat')); + + matlabbatch = returnEstimateModelBatch(matlabbatch, spmMatFile); + + case 3 + + printBatchName('estimate group level fmri model'); + + for j = 1:size(grpLvlCon, 1) + + conName = rmTrialTypeStr(grpLvlCon{j}); + + spmMatFile = { fullfile(opt.rfxDir, conName, 'SPM.mat') }; + + matlabbatch = returnEstimateModelBatch(matlabbatch, spmMatFile); + + end + + end + +end + +function matlabbatch = returnEstimateModelBatch(matlabbatch, spmMatFile) + + matlabbatch{end + 1}.spm.stats.fmri_est.method.Classical = 1; + matlabbatch{end}.spm.stats.fmri_est.write_residuals = 1; + matlabbatch{end}.spm.stats.fmri_est.spmmat = spmMatFile; + +end diff --git a/src/batches/setBatchFactorialDesign.m b/src/batches/setBatchFactorialDesign.m index 4c26f327..f183b9d4 100644 --- a/src/batches/setBatchFactorialDesign.m +++ b/src/batches/setBatchFactorialDesign.m @@ -1,8 +1,26 @@ % (C) Copyright 2019 CPP BIDS SPM-pipeline developers -function matlabbatch = setBatchFactorialDesign(grpLvlCon, group, conFWHM, rfxDir) - - fprintf(1, 'BUILDING JOB: Factorial Design Specification'); +function matlabbatch = setBatchFactorialDesign(matlabbatch, opt, funcFWHM, conFWHM) + % + % Short description of what the function does goes here. + % + % USAGE:: + % + % matlabbatch = setBatchFactorialDesign(matlabbatch, opt, funcFWHM, conFWHM) + % + % :param matlabbatch: + % :type matlabbatch: structure + % :param opt: + % :type opt: structure + % :param funcFWHM: + % :type funcFWHM: + % :param conFWHM: + % :type conFWHM: + % + % :returns: - :matlabbatch: (structure) + % + + printBatchName('specify group level fmri model'); % Check which level of CON smoothing is desired smoothPrefix = ''; @@ -10,7 +28,11 @@ smoothPrefix = ['s', num2str(conFWHM)]; end - con = 0; + [group, opt] = getData(opt); + + rfxDir = getRFXdir(opt, funcFWHM, conFWHM); + + grpLvlCon = getGrpLevelContrastToCompute(opt); % For each contrast for j = 1:size(grpLvlCon, 1) @@ -20,15 +42,24 @@ % at the subject level conName = rmTrialTypeStr(grpLvlCon{j}); - con = con + 1; + fprintf(1, '\n\n Group contrast: %s\n\n', conName); + + directory = fullfile(rfxDir, conName); + + % If it exists, issue a warning that it has been overwritten + if exist(directory, 'dir') + warning('overwriting directory: %s \n', directory); + rmdir(directory, 's'); + end + + mkdir(directory); % For each group for iGroup = 1:length(group) groupName = group(iGroup).name; - matlabbatch{j}.spm.stats.factorial_design.des.fd.icell(iGroup).levels = ... - iGroup; %#ok<*AGROW> + icell(iGroup).levels = iGroup; %#ok<*AGROW> for iSub = 1:group(iGroup).numSub @@ -46,42 +77,43 @@ fileName = sprintf('con_%0.4d.nii', conIdx); file = validationInputFile(ffxDir, fileName, smoothPrefix); - matlabbatch{j}.spm.stats.factorial_design.des.fd.icell(iGroup).scans(iSub, :) = ... - {file}; + icell(iGroup).scans(iSub, :) = {file}; + + fprintf(1, ' %s\n\n', file); end end - % GROUP and the number of levels in the group. if 2 groups , - % then number of levels = 2 - matlabbatch{j}.spm.stats.factorial_design.des.fd.fact.name = 'GROUP'; - matlabbatch{j}.spm.stats.factorial_design.des.fd.fact.levels = 1; - matlabbatch{j}.spm.stats.factorial_design.des.fd.fact.dept = 0; - - % 1: Assumes that the variance is not the same across groups - % 0: There is no difference in the variance between groups - matlabbatch{j}.spm.stats.factorial_design.des.fd.fact.variance = 1; - matlabbatch{j}.spm.stats.factorial_design.des.fd.fact.gmsca = 0; - matlabbatch{j}.spm.stats.factorial_design.des.fd.fact.ancova = 0; - % matlabbatch{j}.spm.stats.factorial_design.cov = []; - matlabbatch{j}.spm.stats.factorial_design.masking.tm.tm_none = 1; - matlabbatch{j}.spm.stats.factorial_design.masking.im = 1; - matlabbatch{j}.spm.stats.factorial_design.masking.em = { ... - fullfile(rfxDir, 'MeanMask.nii')}; - matlabbatch{j}.spm.stats.factorial_design.globalc.g_omit = 1; - matlabbatch{j}.spm.stats.factorial_design.globalm.gmsca.gmsca_no = 1; - matlabbatch{j}.spm.stats.factorial_design.globalm.glonorm = 1; + matlabbatch = returnFactorialDesignBatch(matlabbatch, directory, icell); - % If it exists, issue a warning that it has been overwritten - if exist(fullfile(rfxDir, conName), 'dir') - warning('overwriting directory: %s \n', fullfile(rfxDir, conName)); - rmdir(fullfile(rfxDir, conName), 's'); - end + end - mkdir(fullfile(rfxDir, conName)); - matlabbatch{j}.spm.stats.factorial_design.dir = { fullfile(rfxDir, conName) }; +end - end +function matlabbatch = returnFactorialDesignBatch(matlabbatch, directory, icell) + + matlabbatch{end + 1}.spm.stats.factorial_design.dir = {directory}; + + matlabbatch{end}.spm.stats.factorial_design.des.fd.icell = icell; + + % GROUP and the number of levels in the group. + % If 2 groups, then number of levels = 2 + matlabbatch{end}.spm.stats.factorial_design.des.fd.fact.name = 'GROUP'; + matlabbatch{end}.spm.stats.factorial_design.des.fd.fact.levels = 1; + matlabbatch{end}.spm.stats.factorial_design.des.fd.fact.dept = 0; + + % 1: Assumes that the variance is not the same across groups + % 0: There is no difference in the variance between groups + matlabbatch{end}.spm.stats.factorial_design.des.fd.fact.variance = 1; + matlabbatch{end}.spm.stats.factorial_design.des.fd.fact.gmsca = 0; + matlabbatch{end}.spm.stats.factorial_design.des.fd.fact.ancova = 0; + % matlabbatch{end}.spm.stats.factorial_design.cov = []; + matlabbatch{end}.spm.stats.factorial_design.masking.tm.tm_none = 1; + matlabbatch{end}.spm.stats.factorial_design.masking.im = 1; + matlabbatch{end}.spm.stats.factorial_design.masking.em = {''}; + matlabbatch{end}.spm.stats.factorial_design.globalc.g_omit = 1; + matlabbatch{end}.spm.stats.factorial_design.globalm.gmsca.gmsca_no = 1; + matlabbatch{end}.spm.stats.factorial_design.globalm.glonorm = 1; end diff --git a/src/batches/setBatchGZip.m b/src/batches/setBatchGZip.m new file mode 100644 index 00000000..81fb9fa5 --- /dev/null +++ b/src/batches/setBatchGZip.m @@ -0,0 +1,31 @@ +% (C) Copyright 2020 CPP BIDS SPM-pipeline developers + +function matlabbatch = setBatchGZip(matlabbatch, unzippedNiifiles, keepUnzippedNii) + % + % Set the batch for GZip the 4D volumes + % + % USAGE:: + % + % matlabbatch = setBatchGZip(matlabbatch, unzippedNiifiles, keepUnzippedNii = false) + % + % :param matlabbatch: + % :type matlabbatch: structure + % :param unzippedNiifiles: List of volumes to be gzipped + % :type unzippedNiifiles: array + % :param keepUnzippedNii: Boolean to decide to delete the unzipped files + % :type keepUnzippedNii: boolean + % + % :returns: - :matlabbatch: (struct) The matlabbath ready to run the spm job + + if nargin < 3 || isempty(keepUnzippedNii) + % delete the original unzipped .nii + keepUnzippedNii = false; + end + + printBatchName('zipping'); + + matlabbatch{end + 1}.cfg_basicio.file_dir.file_ops.cfg_gzip_files.files = unzippedNiifiles; + matlabbatch{end}.cfg_basicio.file_dir.file_ops.cfg_gzip_files.outdir = {''}; + matlabbatch{end}.cfg_basicio.file_dir.file_ops.cfg_gzip_files.keep = keepUnzippedNii; + +end diff --git a/src/batches/setBatchGroupLevelContrasts.m b/src/batches/setBatchGroupLevelContrasts.m new file mode 100644 index 00000000..5d83966c --- /dev/null +++ b/src/batches/setBatchGroupLevelContrasts.m @@ -0,0 +1,21 @@ +% (C) Copyright 2019 CPP BIDS SPM-pipeline developers + +function matlabbatch = setBatchGroupLevelContrasts(matlabbatch, grpLvlCon, rfxDir) + + printBatchName('group level contrast estimation'); + + for j = 1:size(grpLvlCon, 1) + + conName = rmTrialTypeStr(grpLvlCon{j}); + + spmMatFile = {fullfile(rfxDir, conName, 'SPM.mat')}; + + consess{1}.tcon.name = 'GROUP'; + consess{1}.tcon.convec = 1; + consess{1}.tcon.sessrep = 'none'; + + matlabbatch = setBatchContrasts(matlabbatch, spmMatFile, consess); + + end + +end diff --git a/src/batches/setBatchImageCalculation.m b/src/batches/setBatchImageCalculation.m index 90923d33..771aee1e 100644 --- a/src/batches/setBatchImageCalculation.m +++ b/src/batches/setBatchImageCalculation.m @@ -1,6 +1,28 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers function matlabbatch = setBatchImageCalculation(matlabbatch, input, output, outDir, expression) + % + % Set a batch for a image calculation + % + % USAGE:: + % + % matlabbatch = setBatchImageCalculation(matlabbatch, input, output, outDir, expression) + % + % :param matlabbatch: + % :type matlabbatch: structure + % :param input: list of images + % :type input: cell + % :param output: name of the output file + % :type output: string + % :param outDir: output directory + % :type outDir: string + % :param expression: mathematical expression to apply (for example '(i1+i2)>3') + % :type expression: string + % + % :returns: - :matlabbatch: + % + + printBatchName('image calculation'); matlabbatch{end + 1}.spm.util.imcalc.input = input; matlabbatch{end}.spm.util.imcalc.output = output; diff --git a/src/batches/setBatchMeanAnatAndMask.m b/src/batches/setBatchMeanAnatAndMask.m index bc4a02ce..bd845192 100644 --- a/src/batches/setBatchMeanAnatAndMask.m +++ b/src/batches/setBatchMeanAnatAndMask.m @@ -1,15 +1,30 @@ % (C) Copyright 2019 CPP BIDS SPM-pipeline developers -function matlabbatch = setBatchMeanAnatAndMask(opt, funcFWHM, rfxDir) +function matlabbatch = setBatchMeanAnatAndMask(matlabbatch, opt, funcFWHM, outputDir) + % + % Creates batxh to create mean anatomical image and a grop mask + % + % USAGE:: + % + % matlabbatch = setBatchMeanAnatAndMask(matlabbatch, opt, funcFWHM, outputDir) + % + % :param matlabbatch: + % :type matlabbatch: structure + % :param opt: Options chosen for the analysis. See ``checkOptions()``. + % :type opt: structure + % :param funcFWHM: + % :param outputDir: + % :type outputDir: tring + % + % :returns: - :matlabbatch: (structure) + % [group, opt, BIDS] = getData(opt); - matlabbatch = {}; + printBatchName('create mean anatomical image and mask'); - % Create Mean Structural Image - fprintf(1, 'BUILDING JOB: Create Mean Structural Image...'); - - subCounter = 0; + inputAnat = {}; + inputMask = {}; for iGroup = 1:length(group) @@ -17,13 +32,11 @@ for iSub = 1:group(iGroup).numSub - subCounter = subCounter + 1; - subID = group(iGroup).subNumber{iSub}; printProcessingSubject(groupName, iSub, subID); - %% STRUCTURAL + %% Anat [anatImage, anatDataDir] = getAnatFilename(BIDS, subID, opt); anatImage = validationInputFile( ... @@ -32,50 +45,50 @@ [spm_get_defaults('normalise.write.prefix'), ... spm_get_defaults('deformations.modulate.prefix')]); - matlabbatch{1}.spm.util.imcalc.input{subCounter, :} = anatImage; + inputAnat{end + 1, 1} = anatImage; %#ok<*AGROW> %% Mask ffxDir = getFFXdir(subID, funcFWHM, opt); files = validationInputFile(ffxDir, 'mask.nii'); - matlabbatch{2}.spm.util.imcalc.input{subCounter, :} = files; + inputMask{end + 1, 1} = files; end end %% Generate the equation to get the mean of the mask and structural image % example : if we have 5 subjects, Average equation = '(i1+i2+i3+i4+i5)/5' - nbImg = subCounter; - imgRange = 1:subCounter; + nbImg = numel(inputAnat); + imgRange = 1:nbImg; tmpImg = sprintf('+i%i', imgRange); tmpImg = tmpImg(2:end); sumEquation = ['(', tmpImg, ')']; - % meanStruct_equation = '(i1+i2+i3+i4+i5)/5' - meanStruct_equation = ['(', tmpImg, ')/', num2str(nbImg)]; + %% The mean structural will be saved in the group level folder + % meanStructEquation = '(i1+i2+i3+i4+i5)/5' + meanAnatEquation = [sumEquation, '/', num2str(nbImg)]; + + matlabbatch = setBatchImageCalculation(matlabbatch, ... + inputAnat, ... + 'meanAnat.nii', ... + outputDir, ... + meanAnatEquation); + + %% The mean mask will be saved in the group level folder - % ------ % TODO % not sure this makes sense for the mask as voxels that have no data for one % subject are excluded anyway !!!! - % meanMask_equation = '(i1+i2+i3+i4+i5)>0.75*5' - meanMask_equation = strcat(sumEquation, '>0.75*', num2str(nbImg)); - - %% The mean structural will be saved in the RFX folder - matlabbatch{1}.spm.util.imcalc.output = 'meanAnat.nii'; - matlabbatch{1}.spm.util.imcalc.outdir{:} = rfxDir; - matlabbatch{1}.spm.util.imcalc.expression = meanStruct_equation; - % matlabbatch{1}.spm.util.imcalc.options.interp = 1; - % matlabbatch{1}.spm.util.imcalc.options.dtype = 4; - - %% The mean mask will be saved in the RFX folder - matlabbatch{2}.spm.util.imcalc.output = 'meanMask.nii'; - matlabbatch{2}.spm.util.imcalc.outdir{:} = rfxDir; - matlabbatch{2}.spm.util.imcalc.expression = meanMask_equation; - % matlabbatch{2}.spm.util.imcalc.options.interp = 1; - % matlabbatch{2}.spm.util.imcalc.options.dtype = 4; + % meanMaskEquation = '(i1+i2+i3+i4+i5)>0.75*5' + meanMaskEquation = [sumEquation, '>0.75*', num2str(nbImg)]; + + matlabbatch = setBatchImageCalculation(matlabbatch, ... + inputMask, ... + 'meanMask.nii', ... + outputDir, ... + meanMaskEquation); end diff --git a/src/batches/setBatchNormalizationSpatialPrepro.m b/src/batches/setBatchNormalizationSpatialPrepro.m index 6c79ab42..c34f7cd5 100644 --- a/src/batches/setBatchNormalizationSpatialPrepro.m +++ b/src/batches/setBatchNormalizationSpatialPrepro.m @@ -1,8 +1,22 @@ % (C) Copyright 2019 CPP BIDS SPM-pipeline developers -function matlabbatch = setBatchNormalizationSpatialPrepro(matlabbatch, voxDim, opt) - - fprintf(1, ' BUILDING SPATIAL JOB : NORMALIZE FUNCTIONALS\n'); +function matlabbatch = setBatchNormalizationSpatialPrepro(matlabbatch, opt, voxDim) + % + % Short description of what the function does goes here. + % + % USAGE:: + % + % matlabbatch = setBatchNormalizationSpatialPrepro(matlabbatch, opt, voxDim) + % + % :param matlabbatch: + % :type matlabbatch: structure + % :param opt: + % :type opt: structure + % :param voxDim: + % :type opt: array + % + % :returns: - :matlabbatch: (structure) + % jobsToAdd = numel(matlabbatch) + 1; @@ -22,7 +36,7 @@ end - fprintf(1, ' BUILDING SPATIAL JOB : NORMALIZE FUNCIONAL\n'); + printBatchName('normalise functional images'); matlabbatch{jobsToAdd}.spm.spatial.normalise.write.subj.resample(1) = ... cfg_dep('Coregister: Estimate: Coregistered Images', ... substruct( ... @@ -33,7 +47,7 @@ substruct('.', 'cfiles')); % NORMALIZE STRUCTURAL - fprintf(1, ' BUILDING SPATIAL JOB : NORMALIZE STRUCTURAL\n'); + printBatchName('normalise anatomical images'); matlabbatch{jobsToAdd + 1}.spm.spatial.normalise.write.subj.resample(1) = ... cfg_dep('Segment: Bias Corrected (1)', ... substruct( ... @@ -47,7 +61,7 @@ matlabbatch{jobsToAdd + 1}.spm.spatial.normalise.write.woptions.vox = [1 1 1]; % NORMALIZE GREY MATTER - fprintf(1, ' BUILDING SPATIAL JOB : NORMALIZE GREY MATTER\n'); + printBatchName('normalise grey matter tissue probability map'); matlabbatch{jobsToAdd + 2}.spm.spatial.normalise.write.subj.resample(1) = ... cfg_dep('Segment: c1 Images', ... substruct( ... @@ -59,7 +73,7 @@ '.', 'c', '()', {':'})); % NORMALIZE WHITE MATTER - fprintf(1, ' BUILDING SPATIAL JOB : NORMALIZE WHITE MATTER\n'); + printBatchName('normalise white matter tissue probability map'); matlabbatch{jobsToAdd + 3}.spm.spatial.normalise.write.subj.resample(1) = ... cfg_dep('Segment: c2 Images', ... substruct( ... @@ -71,7 +85,7 @@ '.', 'c', '()', {':'})); % NORMALIZE CSF MATTER - fprintf(1, ' BUILDING SPATIAL JOB : NORMALIZE CSF\n'); + printBatchName('normalise csf tissue probability map'); matlabbatch{jobsToAdd + 4}.spm.spatial.normalise.write.subj.resample(1) = ... cfg_dep('Segment: c3 Images', ... substruct( ... diff --git a/src/batches/setBatchNormalize.m b/src/batches/setBatchNormalize.m index 831c0682..4416d37d 100644 --- a/src/batches/setBatchNormalize.m +++ b/src/batches/setBatchNormalize.m @@ -1,6 +1,24 @@ % (C) Copyright 2019 CPP BIDS SPM-pipeline developers function matlabbatch = setBatchNormalize(matlabbatch, deformField, voxDim, imgToResample) + % + % Short description of what the function does goes here. + % + % USAGE:: + % + % matlabbatch = setBatchNormalize(matlabbatch [, deformField] [, voxDim] [, imgToResample]) + % + % :param matlabbatch: + % :type matlabbatch: structure + % :param deformField: + % :type deformField: + % :param voxDim: + % :param voxDim: + % :param imgToResample: + % :param imgToResample: + % + % :returns: - :matlabbatch: (structure) + % if nargin > 1 && ~isempty(deformField) matlabbatch{end + 1}.spm.spatial.normalise.write.subj.def(1) = deformField; diff --git a/src/batches/setBatchPrintFigure.m b/src/batches/setBatchPrintFigure.m new file mode 100644 index 00000000..d7d5d3e7 --- /dev/null +++ b/src/batches/setBatchPrintFigure.m @@ -0,0 +1,28 @@ +% (C) Copyright 2020 CPP BIDS SPM-pipeline developers + +function matlabbatch = setBatchPrintFigure(matlabbatch, figureName) + % + % template to creae new setBatch functions + % + % USAGE:: + % + % matlabbatch = setBatchPrintFigure(matlabbatch, figureName) + % + % :param matlabbatch: + % :type matlabbatch: + % :param figureName: + % :type figureName: string + % + % :returns: - :matlabbatch: (structure) The matlabbatch ready to run the spm job + + if ~spm('CmdLine', true) + + printBatchName('print figure'); + + matlabbatch{end + 1}.spm.util.print.fname = figureName; + matlabbatch{1}.spm.util.print.fig.figname = 'Graphics'; + matlabbatch{1}.spm.util.print.opts = 'png'; + + end + +end diff --git a/src/batches/setBatchRealign.m b/src/batches/setBatchRealign.m index 172535a2..e509008e 100644 --- a/src/batches/setBatchRealign.m +++ b/src/batches/setBatchRealign.m @@ -6,53 +6,60 @@ % % USAGE:: % - % [matlabbatch, voxDim] = setBatchRealign(matlabbatch, BIDS, subID, opt, [action = 'realign']) + % [matlabbatch, voxDim] = setBatchRealign(matlabbatch, [action = 'realign'], BIDS, opt, subID) % % :param matlabbatch: SPM batch % :type matlabbatch: structure - % :param BIDS: BIDS layout returned by ``getData`` + % :param BIDS: BIDS layout returned by ``getData``. % :type BIDS: structure - % :param subID: subject label - % :type subID: string - % :param opt: options - % :type opt: structure % :param action: ``realign``, ``realignReslice``, ``realignUnwarp`` % :type action: string + % :param opt: Options chosen for the analysis. See ``checkOptions()``. + % :type opt: structure + % :type subID: string + % :param subID: subject label % % :returns: - :matlabbatch: (structure) (dimension) % - :voxDim: (array) (dimension) + % TODO: + % make which image is resliced more consistent 'which = []' + if numel(varargin) < 5 - [matlabbatch, BIDS, subID, opt] = deal(varargin{:}); + [matlabbatch, BIDS, opt, subID] = deal(varargin{:}); action = ''; else - [matlabbatch, BIDS, subID, opt, action] = deal(varargin{:}); + [matlabbatch, action, BIDS, opt, subID] = deal(varargin{:}); end if isempty(action) - action = 'realign'; + action = 'realignUnwarp'; end % TODO hide this wart in a subfunction ? switch action case 'realignUnwarp' - msg = ' & UNWARP'; + msg = 'REALIGN & UNWARP'; matlabbatch{end + 1}.spm.spatial.realignunwarp.eoptions.weight = {''}; matlabbatch{end}.spm.spatial.realignunwarp.uwroptions.uwwhich = [2 1]; case 'realignReslice' - msg = ' & RESLICE'; + msg = 'REALIGN & RESLICE'; matlabbatch{end + 1}.spm.spatial.realign.estwrite.eoptions.weight = {''}; matlabbatch{1}.spm.spatial.realign.estwrite.roptions.which = [2 1]; case 'realign' - msg = []; + msg = 'REALIGN'; matlabbatch{end + 1}.spm.spatial.realign.estwrite.eoptions.weight = {''}; matlabbatch{end}.spm.spatial.realign.estwrite.roptions.which = [0 1]; + case 'reslice' + msg = 'RESLICE'; + matlabbatch{end + 1}.spm.spatial.realign.write.roptions.which = [2 0]; + end - fprintf(1, ' BUILDING SPATIAL JOB : REALIGN%s\n', msg); + printBatchName(msg); [sessions, nbSessions] = getInfo(BIDS, subID, opt, 'Sessions'); @@ -75,7 +82,7 @@ % check that the file with the right prefix exist and we get and % save its voxeldimension - prefix = getPrefix('preprocess', opt); + prefix = getPrefix('realign', opt); file = validationInputFile(subFuncDataDir, boldFilename, prefix); [voxDim, opt] = getFuncVoxelDims(opt, subFuncDataDir, prefix, boldFilename); @@ -85,20 +92,37 @@ error(errorStruct); end - fprintf(1, ' %s\n', file); + switch action + + % we reslice images that come from a previous batch so we can return + % early + case 'reslice' + + matlabbatch{end}.spm.spatial.realign.write.data(1) = ... + cfg_dep('Coregister: Estimate: Coregistered Images', ... + substruct( ... + '.', 'val', '{}', {opt.orderBatches.coregister}, ... + '.', 'val', '{}', {1}, ... + '.', 'val', '{}', {1}, ... + '.', 'val', '{}', {1}), ... + substruct('.', 'cfiles')); - if strcmp(action, 'realignUnwarp') + return - vdmFile = getVdmFile(BIDS, opt, boldFilename); - matlabbatch{end}.spm.spatial.realignunwarp.data(1, runCounter).pmscan = { vdmFile }; - matlabbatch{end}.spm.spatial.realignunwarp.data(1, runCounter).scans = { file }; + case 'realignUnwarp' - else + vdmFile = getVdmFile(BIDS, opt, boldFilename); + matlabbatch{end}.spm.spatial.realignunwarp.data(1, runCounter).pmscan = { vdmFile }; + matlabbatch{end}.spm.spatial.realignunwarp.data(1, runCounter).scans = { file }; - matlabbatch{end}.spm.spatial.realign.estwrite.data{1, runCounter} = { file }; + otherwise + + matlabbatch{end}.spm.spatial.realign.estwrite.data{1, runCounter} = { file }; end + fprintf(1, ' %s\n', file); + runCounter = runCounter + 1; end diff --git a/src/batches/setBatchReslice.m b/src/batches/setBatchReslice.m index 2c103b3f..cda6fa0b 100644 --- a/src/batches/setBatchReslice.m +++ b/src/batches/setBatchReslice.m @@ -1,14 +1,44 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers -function matlabbatch = setBatchReslice(referenceImg, sourceImages) - % matlabbatch = bidsSmoothing(referenceImg, sourceImages) +function matlabbatch = setBatchReslice(matlabbatch, referenceImg, sourceImages, interp) % - % referenceImg + % Set the batch for reslicing source images into the reference image??? % - % sourceImages: a cell + % USAGE:: + % + % matlabbatch = setBatchReslice(matlabbatch, referenceImg, sourceImages) + % + % :param matlabbatch: list of SPM batches + % :type matlabbatch: structure + % :param referenceImg: Reference image + % :type referenceImg: string + % :param sourceImages: Source images + % :type sourceImages: cell + % + % + % :returns: - :matlabbatch: (structure) The matlabbatch ready to run the spm job + % + + printBatchName('reslicing'); + + if nargin < 4 || isempty(interp) + interp = 4; + end + + matlabbatch{end + 1}.spm.spatial.coreg.write.roptions.interp = interp; + + if ischar(referenceImg) + matlabbatch{end}.spm.spatial.coreg.write.ref = {referenceImg}; + + else + matlabbatch{end}.spm.spatial.coreg.write.ref(1) = referenceImg; + end + + if iscell(sourceImages) + matlabbatch{end}.spm.spatial.coreg.write.source = sourceImages; - matlabbatch = []; - matlabbatch{1}.spm.spatial.coreg.write.ref = {referenceImg}; - matlabbatch{1}.spm.spatial.coreg.write.source = sourceImages; + else + matlabbatch{end}.spm.spatial.coreg.write.source(1) = referenceImg; + end end diff --git a/src/batches/setBatchResults.m b/src/batches/setBatchResults.m new file mode 100644 index 00000000..364315a6 --- /dev/null +++ b/src/batches/setBatchResults.m @@ -0,0 +1,98 @@ +% (C) Copyright 2019 CPP BIDS SPM-pipeline developers + +function matlabbatch = setBatchResults(matlabbatch, result) + % + % Outputs the typical matlabbatch to compute the results for a given contrast + % + % USAGE:: + % + % matlabbatch = setBatchResults(matlabbatch, opt, iStep, iCon, results) + % + % :param matlabbatch: + % :type matlabbatch: structure + % :param results: + % :type results: structure + % + % results.dir = ffxDir; + % results.contrastNb = conNb; + % results.label = subID; + % results.nbSubj = 1; + % + % :returns: - :matlabbatch: (structure) + % + % + + fieldsToSet = returnDefaultResultsStructure(); + result = setDefaultFields(result, fieldsToSet); + result.Contrasts = replaceEmptyFields(result.Contrasts, fieldsToSet.Contrasts); + + matlabbatch{end + 1}.spm.stats.results.spmmat = {fullfile(result.dir, 'SPM.mat')}; + + matlabbatch{end}.spm.stats.results.conspec.titlestr = returnName(result); + + matlabbatch{end}.spm.stats.results.conspec.contrasts = result.contrastNb; + matlabbatch{end}.spm.stats.results.conspec.threshdesc = result.Contrasts.MC; + matlabbatch{end}.spm.stats.results.conspec.thresh = result.Contrasts.p; + matlabbatch{end}.spm.stats.results.conspec.extent = result.Contrasts.k; + matlabbatch{end}.spm.stats.results.conspec.conjunction = 1; + matlabbatch{end}.spm.stats.results.conspec.mask.none = ~result.Contrasts.useMask; + + matlabbatch{end}.spm.stats.results.units = 1; + + matlabbatch{end}.spm.stats.results.export = []; + if result.Output.png + matlabbatch{end}.spm.stats.results.export{end + 1}.png = true; + end + + if result.Output.csv + matlabbatch{end}.spm.stats.results.export{end + 1}.csv = true; + end + + if result.Output.thresh_spm + matlabbatch{end}.spm.stats.results.export{end + 1}.tspm.basename = returnName(result); + end + + if result.Output.binary + matlabbatch{end}.spm.stats.results.export{end + 1}.binary.basename = [returnName(result), ... + '_mask']; + end + + if result.Output.NIDM_results + + matlabbatch{end}.spm.stats.results.export{end + 1}.nidm.modality = 'FMRI'; + + matlabbatch{end}.spm.stats.results.export{end}.nidm.refspace = 'ixi'; + if strcmp(result.space, 'individual') + matlabbatch{end}.spm.stats.results.export{end}.nidm.refspace = 'subject'; + end + + matlabbatch{end}.spm.stats.results.export{end}.nidm.group.nsubj = result.nbSubj; + + matlabbatch{end}.spm.stats.results.export{end}.nidm.group.label = result.label; + + end + + if result.Output.montage.do + + matlabbatch{end}.spm.stats.results.export{end + 1}.montage = setMontage(result); + + % Not sure why the name of the figure does not come out right + matlabbatch{end + 1}.spm.util.print.fname = ['Montage_' returnName(result)]; + matlabbatch{end}.spm.util.print.fig.figname = 'SliceOverlay'; + matlabbatch{end}.spm.util.print.opts = 'png'; + + end + +end + +function struct = replaceEmptyFields(struct, fieldsToCheck) + + fieldsList = fieldnames(fieldsToCheck); + + for iField = 1:numel(fieldsList) + if isfield(struct, fieldsList{iField}) && isempty(struct.(fieldsList{iField})) + struct.(fieldsList{iField}) = fieldsToCheck.(fieldsList{iField}); + end + end + +end diff --git a/src/batches/setBatchSTC.m b/src/batches/setBatchSTC.m index a0335a53..473687dd 100644 --- a/src/batches/setBatchSTC.m +++ b/src/batches/setBatchSTC.m @@ -1,13 +1,28 @@ % (C) Copyright 2019 CPP BIDS SPM-pipeline developers -function matlabbatch = setBatchSTC(BIDS, opt, subID) - % matlabbatch = setBatchSTC(BIDS, opt, subID) +function matlabbatch = setBatchSTC(matlabbatch, BIDS, opt, subID) % - % Slice timing units is in milliseconds to be BIDS compliant and not in slice number + % Creates batch for slice timing correction + % + % USAGE:: + % + % matlabbatch = setBatchSTC(matlabbatch, BIDS, opt, subID) + % + % :param BIDS: BIDS layout returned by ``getData``. + % :type BIDS: structure + % :param opt: structure or json filename containing the options. See + % ``checkOptions()`` and ``loadAndCheckOptions()``. + % :type opt: structure + % :param subID: subject ID + % :type subID: string + % + % :returns: - :matlabbatch: (structure) The matlabbatch ready to run the spm job + % + % Slice timing units is in seconds to be BIDS compliant and not in slice number % as is more traditionally the case with SPM. % % In the case the slice timing information was not specified in the json FILES - % in the BIDS data set (e.g it couldnt be extracted from the trento old scanner), + % in the BIDS data set (e.g it couldn't be extracted from the trento old scanner), % then add this information manually in opt.sliceOrder field. % % If this is empty the slice timing correction will not be performed @@ -15,9 +30,6 @@ % If not specified this function will take the mid-volume time point as reference % to do the slice timing correction % - % See README.md for more information about slice timing correction - - matlabbatch = []; % get slice order sliceOrder = getSliceOrder(opt, 1); @@ -26,7 +38,7 @@ return end - fprintf(1, ' BUILDING STC JOB : STC\n'); + printBatchName('slice timing correction'); % get metadata for STC % Note that slice ordering is assumed to be from foot to head. If it is not, enter @@ -45,16 +57,31 @@ else referenceSlice = opt.STC_referenceSlice; end - if referenceSlice > TA - error('%s (%f) %s (%f).\n%s', ... - 'The reference slice time', referenceSlice, ... - 'is greater than the acquisition time', TA, ... - ['Reference slice time must be in milliseconds ' ... - 'or leave it empty to use mid-acquisition time as reference.']); + if TA >= TR || referenceSlice > TA || any(sliceOrder > TA) + + pattern = repmat ('%.3f, ', 1, numel(sliceOrder)); + pattern(end) = []; + + msg = sprintf([ ... + 'Impossible values on slice timing input:\n\n', ... + ' repetition time > acquisition time > reference slice.\n\n', ... + 'All STC values in the opt structure must be in seconds.\n', ... + 'Current values:', ... + '\n- repetition time: %f', ... + '\n- acquisition time: %f', ... + '\n- reference slice: %f', ... + '\n- slice order: ' pattern], TR, TA, referenceSlice, sliceOrder); + + errorStruct.identifier = 'setBatchSTC:invalidInputTime'; + errorStruct.message = msg; + error(errorStruct); end - % prefix of the files to look for - prefix = getPrefix('STC', opt); + matlabbatch{end + 1}.spm.temporal.st.nslices = nbSlices; + matlabbatch{end}.spm.temporal.st.tr = TR; + matlabbatch{end}.spm.temporal.st.ta = TA; + matlabbatch{end}.spm.temporal.st.so = sliceOrder * 1000; + matlabbatch{end}.spm.temporal.st.refslice = referenceSlice * 1000; [sessions, nbSessions] = getInfo(BIDS, subID, opt, 'Sessions'); @@ -73,10 +100,10 @@ subID, sessions{iSes}, runs{iRun}, opt); % check that the file with the right prefix exist - file = validationInputFile(subFuncDataDir, prefix, fileName); + file = validationInputFile(subFuncDataDir, fileName); % add the file to the list - matlabbatch{1}.spm.temporal.st.scans{runCounter} = {file}; + matlabbatch{end}.spm.temporal.st.scans{runCounter} = {file}; runCounter = runCounter + 1; @@ -86,12 +113,6 @@ end - matlabbatch{1}.spm.temporal.st.nslices = nbSlices; - matlabbatch{1}.spm.temporal.st.tr = TR; - matlabbatch{1}.spm.temporal.st.ta = TA; - matlabbatch{1}.spm.temporal.st.so = sliceOrder; - matlabbatch{1}.spm.temporal.st.refslice = referenceSlice; - % The following lines are commented out because those parameters % can be set in the spm_my_defaults.m % matlabbatch{1}.spm.temporal.st.prefix = spm_get_defaults('slicetiming.prefix'); diff --git a/src/batches/setBatchSaveCoregistrationMatrix.m b/src/batches/setBatchSaveCoregistrationMatrix.m index ac5d5008..82212043 100644 --- a/src/batches/setBatchSaveCoregistrationMatrix.m +++ b/src/batches/setBatchSaveCoregistrationMatrix.m @@ -1,6 +1,26 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers -function matlabbatch = setBatchSaveCoregistrationMatrix(matlabbatch, BIDS, subID, opt) +function matlabbatch = setBatchSaveCoregistrationMatrix(matlabbatch, BIDS, opt, subID) + % + % Short description of what the function does goes here. + % + % USAGE:: + % + % matlabbatch = setBatchSaveCoregistrationMatrix(matlabbatch, BIDS, opt, subID) + % + % :param matlabbatch: + % :type matlabbatch: structure + % :param BIDS: BIDS layout returned by ``getData``. + % :type BIDS: structure + % :param opt: + % :type opt: Options chosen for the analysis. See ``checkOptions()``. + % :param subID: + % :type subID: + % + % :returns: - :matlabbatch: + % + + printBatchName('saving coregistration matrix'); % create name of the output file based on the name of the first image of the % first session diff --git a/src/batches/setBatchSegmentation.m b/src/batches/setBatchSegmentation.m index 78721948..395cc588 100644 --- a/src/batches/setBatchSegmentation.m +++ b/src/batches/setBatchSegmentation.m @@ -1,27 +1,59 @@ -% (C) Copyright 2019 CPP BIDS SPM-pipeline developers +% (C) Copyright 2020 CPP BIDS SPM-pipeline developers -function matlabbatch = setBatchSegmentation(matlabbatch, opt) +function matlabbatch = setBatchSegmentation(matlabbatch, opt, imageToSegment) + % + % Creates a batch to segment the anatomical image + % + % USAGE:: + % + % matlabbatch = setBatchSegmentation(matlabbatch, opt) + % + % :param matlabbatch: list of SPM batches + % :type matlabbatch: structure + % :param opt: structure or json filename containing the options. See + % ``checkOptions()`` and ``loadAndCheckOptions()``. + % :type opt: structure + % + % :returns: :matlabbatch: (structure) + % - fprintf(1, ' BUILDING SPATIAL JOB : SEGMENT ANATOMICAL\n'); + printBatchName('Segmentation anatomical image'); % define SPM folder spmLocation = spm('dir'); - % SAVE BIAS CORRECTED IMAGE - matlabbatch{end + 1}.spm.spatial.preproc.channel.vols(1) = ... - cfg_dep('Named File Selector: Anatomical(1) - Files', ... - substruct( ... - '.', 'val', '{}', {opt.orderBatches.selectAnat}, ... - '.', 'val', '{}', {1}, ... - '.', 'val', '{}', {1}, ... - '.', 'val', '{}', {1}), ... - substruct('.', 'files', '{}', {1})); + % save bias correction field = false + % save bias corrected image = true + matlabbatch{end + 1}.spm.spatial.preproc.channel.write = [false true]; + + if isfield(opt, 'orderBatches') && isfield(opt.orderBatches, 'selectAnat') + + % SAVE BIAS CORRECTED IMAGE + matlabbatch{end}.spm.spatial.preproc.channel.vols(1) = ... + cfg_dep('Named File Selector: Anatomical(1) - Files', ... + substruct( ... + '.', 'val', '{}', {opt.orderBatches.selectAnat}, ... + '.', 'val', '{}', {1}, ... + '.', 'val', '{}', {1}, ... + '.', 'val', '{}', {1}), ... + substruct('.', 'files', '{}', {1})); + else + + % in case a cell was given as input + if iscell(imageToSegment) + imageToSegment = char(imageToSegment); + end + + % add all the images to segment + for iImg = 1:size(imageToSegment, 1) + file = validationInputFile([], deblank(imageToSegment(iImg, :))); + matlabbatch{end}.spm.spatial.preproc.channel.vols{iImg, 1} = file; + end + + end matlabbatch{end}.spm.spatial.preproc.channel.biasreg = 0.001; matlabbatch{end}.spm.spatial.preproc.channel.biasfwhm = 60; - % save bias correction field = false - % save bias corrected image = true - matlabbatch{end}.spm.spatial.preproc.channel.write = [false true]; % CREATE SEGMENTS IN NATIVE SPACE OF GM,WM AND CSF matlabbatch{end}.spm.spatial.preproc.tissue(1).tpm = ... diff --git a/src/batches/setBatchSelectAnat.m b/src/batches/setBatchSelectAnat.m index d4476f08..cb60668f 100644 --- a/src/batches/setBatchSelectAnat.m +++ b/src/batches/setBatchSelectAnat.m @@ -1,15 +1,33 @@ -% (C) Copyright 2019 CPP BIDS SPM-pipeline developers +% (C) Copyright 2020 CPP BIDS SPM-pipeline developers function matlabbatch = setBatchSelectAnat(matlabbatch, BIDS, opt, subID) + % + % Creates a batch to set an anatomical image + % + % USAGE:: + % + % matlabbatch = setBatchSelectAnat(matlabbatch, BIDS, opt, subID) + % + % :param matlabbatch: list of SPM batches + % :type matlabbatch: structure + % :param BIDS: BIDS layout returned by ``getData``. + % :type BIDS: structure + % :param opt: structure or json filename containing the options. See + % ``checkOptions()`` and ``loadAndCheckOptions()``. + % :type opt: structure + % :param subID: subject ID + % :type subID: string + % + % :returns: :matlabbatch: (structure) + % % matlabbatch = setBatchSelectAnat(matlabbatch, BIDS, opt, subID) % - % Creates a batch to set an anat image % - image type = opt.anatReference.type (default = T1w) % - session to select the anat from = opt.anatReference.session (default = 1) % % We assume that the first anat of that type is the "correct" one - fprintf(1, ' BUILDING SPATIAL JOB : SELECTING ANATOMCAL\n'); + printBatchName('selecting anatomical image'); [anatImage, anatDataDir] = getAnatFilename(BIDS, subID, opt); diff --git a/src/batches/setBatchSkullStripping.m b/src/batches/setBatchSkullStripping.m index b69c1a00..286b65a6 100644 --- a/src/batches/setBatchSkullStripping.m +++ b/src/batches/setBatchSkullStripping.m @@ -1,24 +1,25 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers -function matlabbatch = setBatchSkullStripping(matlabbatch, BIDS, subID, opt) +function matlabbatch = setBatchSkullStripping(matlabbatch, BIDS, opt, subID) % - % Creates a batch to computes a brain mask based on the tissue probability maps - % from the segmentaion. + % Creates a batch to compute a brain mask based on the tissue probability maps + % from the segmentation. % % USAGE:: % - % matlabbatch = setBatchSkullStripping(matlabbatch, BIDS, subID, opt) + % matlabbatch = setBatchSkullStripping(matlabbatch, BIDS, opt, subID) % % :param matlabbatch: list of SPM batches % :type matlabbatch: structure - % :param BIDS: dataset layout returned by getData + % :param BIDS: BIDS layout returned by ``getData``. % :type BIDS: structure + % :param opt: structure or json filename containing the options. See + % ``checkOptions()`` and ``loadAndCheckOptions()``. + % :type opt: structure % :param subID: subject ID % :type subID: string - % :param opt: options - % :type opt: structure % - % :returns: - :matlabbatch: (structure) + % :returns: - :matlabbatch: (structure) The matlabbatch ready to run the spm job % % This function will get its inputs from the segmentation batch by reading % the dependency from ``opt.orderBatches.segment``. If this field is not specified it will @@ -27,24 +28,24 @@ % % The threshold for inclusion in the mask can be set by:: % - % opt.skullstrip.threshold - % - % The defaukt is ``0.75``. + % opt.skullstrip.threshold (default = 0.75) % % Any voxel with p(grayMatter) + p(whiteMatter) + p(CSF) > threshold % will be included in the skull stripping mask. % - fprintf(1, ' BUILDING SPATIAL JOB : SKULL STRIPPING\n'); + printBatchName('skull stripping'); [anatImage, anatDataDir] = getAnatFilename(BIDS, subID, opt); + output = ['m' strrep(anatImage, '.nii', '_skullstripped.nii')]; + expression = sprintf('i1.*((i2+i3+i4)>%f)', opt.skullstrip.threshold); % if this is part of a pipeline we get the segmentation dependency to get % the input from. % Otherwise the files to process are stored in a cell if isfield(opt, 'orderBatches') && isfield(opt.orderBatches, 'segment') - matlabbatch{end + 1}.spm.util.imcalc.input(1) = ... + input(1) = ... cfg_dep( ... 'Segment: Bias Corrected (1)', ... substruct( ... @@ -54,7 +55,8 @@ substruct( ... '.', 'channel', '()', {1}, ... '.', 'biascorr', '()', {':'})); - matlabbatch{end}.spm.util.imcalc.input(2) = ... + + input(2) = ... cfg_dep( ... 'Segment: c1 Images', ... substruct( ... @@ -64,7 +66,8 @@ substruct( ... '.', 'tiss', '()', {1}, ... '.', 'c', '()', {':'})); - matlabbatch{end}.spm.util.imcalc.input(3) = ... + + input(3) = ... cfg_dep( ... 'Segment: c2 Images', ... substruct( ... @@ -74,7 +77,8 @@ substruct( ... '.', 'tiss', '()', {2}, ... '.', 'c', '()', {':'})); - matlabbatch{end}.spm.util.imcalc.input(4) = ... + + input(4) = ... cfg_dep( ... 'Segment: c3 Images', ... substruct( ... @@ -84,32 +88,27 @@ substruct( ... '.', 'tiss', '()', {3}, ... '.', 'c', '()', {':'})); + else % bias corrected image biasCorrectedAnatImage = validationInputFile(anatDataDir, anatImage, 'm'); - matlabbatch{end + 1}.spm.util.imcalc.input(1) = biasCorrectedAnatImage; - % get the tissue probability maps in native space for that subject TPMs = validationInputFile(anatDataDir, anatImage, 'c[123]'); + input{1} = biasCorrectedAnatImage; % grey matter - matlabbatch{end}.spm.util.imcalc.input(2) = TPMs(1, :); + input{2} = TPMs(1, :); % white matter - matlabbatch{end}.spm.util.imcalc.input(3) = TPMs(2, :); + input{3} = TPMs(2, :); % csf - matlabbatch{end}.spm.util.imcalc.input(4) = TPMs(3, :); + input{4} = TPMs(3, :); end - matlabbatch{end}.spm.util.imcalc.output = ['m' strrep(anatImage, '.nii', '_skullstripped.nii')]; - matlabbatch{end}.spm.util.imcalc.outdir = {anatDataDir}; - - matlabbatch{end}.spm.util.imcalc.expression = sprintf( ... - 'i1.*((i2+i3+i4)>%f)', ... - opt.skullstrip.threshold); + matlabbatch = setBatchImageCalculation(matlabbatch, input, output, anatDataDir, expression); - % add a batch to output the mask + %% Add a batch to output the mask matlabbatch{end + 1} = matlabbatch{end}; matlabbatch{end}.spm.util.imcalc.expression = sprintf( ... '(i2+i3+i4)>%f', ... diff --git a/src/batches/setBatchSmoothConImages.m b/src/batches/setBatchSmoothConImages.m index a6d65773..8e19b61c 100644 --- a/src/batches/setBatchSmoothConImages.m +++ b/src/batches/setBatchSmoothConImages.m @@ -1,12 +1,28 @@ % (C) Copyright 2019 CPP BIDS SPM-pipeline developers -function matlabbatch = setBatchSmoothConImages(group, funcFWHM, conFWHM, opt) - - fprintf(1, 'SMOOTHING CON IMAGES...'); - - counter = 0; - - matlabbatch = {}; +function matlabbatch = setBatchSmoothConImages(matlabbatch, group, opt, funcFWHM, conFWHM) + % + % Creates a batch to smooth all the con images of all subjects + % + % USAGE:: + % + % matlabbatch = setBatchSmoothConImages(matlabbatch, group, opt, funcFWHM, conFWHM) + % + % :param matlabbatch: + % :type matlabbatch: + % :param group: + % :type group: + % :param opt: Options chosen for the analysis. See ``checkOptions()``. + % :type opt: + % :param funcFWHM: + % :type funcFWHM: + % :param conFWHM: + % :type conFWHM: + % + % :returns: - :matlabbatch: + % + + printBatchName('smoothing contrast images'); %% Loop through the groups, subjects, and sessions for iGroup = 1:length(group) @@ -15,8 +31,6 @@ for iSub = 1:group(iGroup).numSub - counter = counter + 1; - subNumber = group(iGroup).subNumber{iSub}; printProcessingSubject(groupName, iSub, subNumber); @@ -24,17 +38,16 @@ ffxDir = getFFXdir(subNumber, funcFWHM, opt); conImg = spm_select('FPlist', ffxDir, '^con*.*nii$'); - matlabbatch{counter}.spm.spatial.smooth.data = cellstr(conImg); %#ok<*AGROW> + data = cellstr(conImg); - % Define how much smoothing is required - matlabbatch{counter}.spm.spatial.smooth.fwhm = ... - [conFWHM conFWHM conFWHM]; - matlabbatch{counter}.spm.spatial.smooth.dtype = 0; - matlabbatch{counter}.spm.spatial.smooth.prefix = [ ... - spm_get_defaults('smooth.prefix'), ... - num2str(conFWHM)]; + matlabbatch = setBatchSmoothing( ... + matlabbatch, ... + data, ... + conFWHM, ... + [spm_get_defaults('smooth.prefix'), num2str(conFWHM)]); end + end end diff --git a/src/batches/setBatchSmoothing.m b/src/batches/setBatchSmoothing.m index 1ca9015c..f0e353de 100644 --- a/src/batches/setBatchSmoothing.m +++ b/src/batches/setBatchSmoothing.m @@ -1,56 +1,33 @@ % (C) Copyright 2019 CPP BIDS SPM-pipeline developers -function matlabbatch = setBatchSmoothing(BIDS, opt, subID, funcFWHM) - - fprintf(1, 'PREPARING: SMOOTHING JOB \n'); - - % creates prefix to look for - prefix = getPrefix('smoothing', opt); - if strcmp(opt.space, 'individual') - prefix = getPrefix('smoothing_space-individual', opt); - end - - % identify sessions for this subject - [sessions, nbSessions] = getInfo(BIDS, subID, opt, 'Sessions'); - - % clear previous matlabbatch and files - matlabbatch = []; - allFiles = []; - - sesCounter = 1; - - for iSes = 1:nbSessions % For each session - - % get all runs for that subject across all sessions - [runs, nbRuns] = getInfo(BIDS, subID, opt, 'Runs', sessions{iSes}); - - % numRuns = group(iGroup).numRuns(iSub); - for iRun = 1:nbRuns - - % get the filename for this bold run for this task - [fileName, subFuncDataDir] = getBoldFilename( ... - BIDS, ... - subID, sessions{iSes}, runs{iRun}, opt); - - % check that the file with the right prefix exist - files = validationInputFile(subFuncDataDir, fileName, prefix); - - % add the files to list - allFilesTemp = cellstr(files); - allFiles = [allFiles; allFilesTemp]; %#ok - sesCounter = sesCounter + 1; - - end - end - - matlabbatch{1}.spm.spatial.smooth.data = allFiles; - % Define the amount of smoothing required - matlabbatch{1}.spm.spatial.smooth.fwhm = [funcFWHM funcFWHM funcFWHM]; - matlabbatch{1}.spm.spatial.smooth.dtype = 0; - matlabbatch{1}.spm.spatial.smooth.im = 0; - - % Prefix = s+funcFWHM - matlabbatch{1}.spm.spatial.smooth.prefix = ... - [spm_get_defaults('smooth.prefix'), num2str(funcFWHM)]; +function matlabbatch = setBatchSmoothing(matlabbatch, images, FWHM, prefix) + % + % Small wrapper to create smoothing batch + % + % USAGE:: + % + % matlabbatch = setBatchSmoothing(matlabbatch, images, FWHM, prefix) + % + % :param matlabbatch: + % :type matlabbatch: structure + % :param images: + % :type images: + % :param funcFWHM: + % :type funcFWHM: + % :param prefix: + % :type prefix: + % + % :returns: - :matlabbatch: (structure) + % + % + + printBatchName('smoothing images'); + + matlabbatch{end + 1}.spm.spatial.smooth.data = images; + matlabbatch{end}.spm.spatial.smooth.prefix = prefix; + matlabbatch{end}.spm.spatial.smooth.fwhm = [FWHM FWHM FWHM]; + + matlabbatch{end}.spm.spatial.smooth.dtype = 0; + matlabbatch{end}.spm.spatial.smooth.im = 0; end diff --git a/src/batches/setBatchSmoothingFunc.m b/src/batches/setBatchSmoothingFunc.m new file mode 100644 index 00000000..3baf0487 --- /dev/null +++ b/src/batches/setBatchSmoothingFunc.m @@ -0,0 +1,65 @@ +% (C) Copyright 2019 CPP BIDS SPM-pipeline developers + +function matlabbatch = setBatchSmoothingFunc(matlabbatch, BIDS, opt, subID, funcFWHM) + % + % Short description of what the function does goes here. + % + % USAGE:: + % + % matlabbatch = setBatchSmoothingFunc(matlabbatch, BIDS, opt, subID, funcFWHM) + % + % :param matlabbatch: + % :type matlabbatch: structure + % :param BIDS: BIDS layout returned by ``getData``. + % :type BIDS: structure + % :param opt: Options chosen for the analysis. See ``checkOptions()``. + % :type opt: string + % :param subID: + % :type subID: + % :param funcFWHM: + % :type funcFWHM: + % + % :returns: - :matlabbatch: (structure) + % + % + + printBatchName('smoothing functional images'); + + prefix = getPrefix('smooth', opt); + + % identify sessions for this subject + [sessions, nbSessions] = getInfo(BIDS, subID, opt, 'Sessions'); + + % clear previous matlabbatch and files + allFiles = []; + + for iSes = 1:nbSessions % For each session + + % get all runs for that subject across all sessions + [runs, nbRuns] = getInfo(BIDS, subID, opt, 'Runs', sessions{iSes}); + + % numRuns = group(iGroup).numRuns(iSub); + for iRun = 1:nbRuns + + % get the filename for this bold run for this task + [fileName, subFuncDataDir] = getBoldFilename( ... + BIDS, ... + subID, sessions{iSes}, runs{iRun}, opt); + + % check that the file with the right prefix exist + files = validationInputFile(subFuncDataDir, fileName, prefix); + + % add the files to list + allFilesTemp = cellstr(files); + allFiles = [allFiles; allFilesTemp]; %#ok + + end + end + + % Prefix = s+funcFWHM + matlabbatch = setBatchSmoothing(matlabbatch, ... + allFiles, ... + funcFWHM, ... + [spm_get_defaults('smooth.prefix'), num2str(funcFWHM)]); + +end diff --git a/src/batches/setBatchSubjectLevelContrasts.m b/src/batches/setBatchSubjectLevelContrasts.m index 34bb15fb..ad971576 100644 --- a/src/batches/setBatchSubjectLevelContrasts.m +++ b/src/batches/setBatchSubjectLevelContrasts.m @@ -1,25 +1,39 @@ % (C) Copyright 2019 CPP BIDS SPM-pipeline developers -function matlabbatch = setBatchSubjectLevelContrasts(opt, subID, funcFWHM) - - fprintf(1, 'BUILDING JOB : FMRI contrasts\n'); +function matlabbatch = setBatchSubjectLevelContrasts(matlabbatch, opt, subID, funcFWHM) + % + % Short description of what the function does goes here. + % + % USAGE:: + % + % matlabbatch = setBatchSubjectLevelContrasts(matlabbatch, opt, subID, funcFWHM) + % + % :param matlabbatch: + % :type matlabbatch: structure + % :param opt: + % :type opt: structure + % :param subID: + % :type subID: string + % :param funcFWHM: + % :type funcFWHM: + % + % :returns: - :matlabbatch: + % + + printBatchName('subject level contrasts specification'); ffxDir = getFFXdir(subID, funcFWHM, opt); + spmMatFile = cellstr(fullfile(ffxDir, 'SPM.mat')); + % Create Contrasts contrasts = specifyContrasts(ffxDir, opt.taskName, opt); - - matlabbatch = []; - for icon = 1:size(contrasts, 2) - matlabbatch{1}.spm.stats.con.consess{icon}.tcon.name = ... - contrasts(icon).name; - matlabbatch{1}.spm.stats.con.consess{icon}.tcon.convec = ... - contrasts(icon).C; - matlabbatch{1}.spm.stats.con.consess{icon}.tcon.sessrep = 'none'; + consess{icon}.tcon.name = contrasts(icon).name; %#ok<*AGROW> + consess{icon}.tcon.convec = contrasts(icon).C; + consess{icon}.tcon.sessrep = 'none'; end - matlabbatch{1}.spm.stats.con.spmmat = cellstr(fullfile(ffxDir, 'SPM.mat')); - matlabbatch{1}.spm.stats.con.delete = 1; + matlabbatch = setBatchContrasts(matlabbatch, spmMatFile, consess); end diff --git a/src/batches/setBatchSubjectLevelGLMSpec.m b/src/batches/setBatchSubjectLevelGLMSpec.m index c07948da..2b353d61 100644 --- a/src/batches/setBatchSubjectLevelGLMSpec.m +++ b/src/batches/setBatchSubjectLevelGLMSpec.m @@ -1,10 +1,27 @@ % (C) Copyright 2019 CPP BIDS SPM-pipeline developers function matlabbatch = setBatchSubjectLevelGLMSpec(varargin) - - [BIDS, opt, subID, funcFWHM] = deal(varargin{:}); - - fprintf(1, 'BUILDING JOB : FMRI design\n'); + % + % Short description of what the function does goes here. + % + % USAGE:: + % + % matlabbatch = setBatchSubjectLevelGLMSpec(matlabbatch, BIDS, opt, subID, funcFWHM) + % + % :param argin1: (dimension) obligatory argument. Lorem ipsum dolor sit amet, + % consectetur adipiscing elit. Ut congue nec est ac lacinia. + % :type argin1: type + % :param argin2: Options chosen for the analysis. See ``checkOptions()``. + % :type argin2: string + % :param argin3: (dimension) optional argument + % + % :returns: - :argout1: (type) (dimension) + % - :argout2: (type) (dimension) + % + + [matlabbatch, BIDS, opt, subID, funcFWHM] = deal(varargin{:}); + + printBatchName('specify subject level fmri model'); % Check the slice timing information is not in the metadata and not added % manually in the opt variable. @@ -14,33 +31,36 @@ if isempty(sliceOrder) % no slice order defined here so we fall back on using the number of - % slice in the first bold imageBIDS, opt, subID, funcFWHM, iSes, iRun - % to set the number of time bins we will use to upsample our model - % during regression creation - fileName = spm_BIDS(BIDS, 'data', ... - 'sub', subID, ... - 'type', 'bold'); + % slice in the first bold image to set the number of time bins + % we will use to upsample our model during regression creation + fileName = bids.query(BIDS, 'data', ... + 'sub', subID, ... + 'type', 'bold'); fileName = strrep(fileName{1}, '.gz', ''); hdr = spm_vol(fileName); % we are assuming axial acquisition here sliceOrder = 1:hdr(1).dim(3); end - %% - matlabbatch = []; - - matlabbatch{1}.spm.stats.fmri_spec.timing.units = 'secs'; + matlabbatch{end + 1}.spm.stats.fmri_spec.timing.units = 'secs'; % get TR from metadata TR = opt.metadata.RepetitionTime; - matlabbatch{1}.spm.stats.fmri_spec.timing.RT = TR; + matlabbatch{end}.spm.stats.fmri_spec.timing.RT = TR; % number of times bins nbTimeBins = numel(unique(sliceOrder)); - matlabbatch{1}.spm.stats.fmri_spec.timing.fmri_t = nbTimeBins; - - refBin = floor(nbTimeBins / 2); - matlabbatch{1}.spm.stats.fmri_spec.timing.fmri_t0 = refBin; + matlabbatch{end}.spm.stats.fmri_spec.timing.fmri_t = nbTimeBins; + + % If no reference slice is given for STC, then STC took the mid-volume + % time point to do the correction. + % When no STC was done, this is usually a good way to do it too. + if isempty(opt.STC_referenceSlice) + refBin = floor(nbTimeBins / 2); + else + refBin = opt.STC_referenceSlice / opt.metadata.RepetitionTime; + end + matlabbatch{end}.spm.stats.fmri_spec.timing.fmri_t0 = refBin; % Create ffxDir if it doesnt exist % If it exists, issue a warning that it has been overwritten @@ -50,21 +70,21 @@ rmdir(ffxDir, 's'); mkdir(ffxDir); end - matlabbatch{1}.spm.stats.fmri_spec.dir = {ffxDir}; + matlabbatch{end}.spm.stats.fmri_spec.dir = {ffxDir}; - matlabbatch{1}.spm.stats.fmri_spec.fact = struct('name', {}, 'levels', {}); + matlabbatch{end}.spm.stats.fmri_spec.fact = struct('name', {}, 'levels', {}); - matlabbatch{1}.spm.stats.fmri_spec.bases.hrf.derivs = [0 0]; + matlabbatch{end}.spm.stats.fmri_spec.bases.hrf.derivs = opt.model.hrfDerivatives; - matlabbatch{1}.spm.stats.fmri_spec.volt = 1; + matlabbatch{end}.spm.stats.fmri_spec.volt = 1; - matlabbatch{1}.spm.stats.fmri_spec.global = 'None'; + matlabbatch{end}.spm.stats.fmri_spec.global = 'None'; - matlabbatch{1}.spm.stats.fmri_spec.mask = {''}; + matlabbatch{end}.spm.stats.fmri_spec.mask = {''}; % The following lines are commented out because those parameters % can be set in the spm_my_defaults.m - % matlabbatch{1}.spm.stats.fmri_spec.cvi = 'AR(1)'; + % matlabbatch{end}.spm.stats.fmri_spec.cvi = 'AR(1)'; % identify sessions for this subject [sessions, nbSessions] = getInfo(BIDS, subID, opt, 'Sessions'); @@ -84,7 +104,7 @@ disp(fullpathBoldFileName); - matlabbatch{1}.spm.stats.fmri_spec.sess(sesCounter).scans = ... + matlabbatch{end}.spm.stats.fmri_spec.sess(sesCounter).scans = ... {fullpathBoldFileName}; % get stimuli onset time file @@ -97,25 +117,25 @@ tsvFile, ... funcFWHM); - matlabbatch{1}.spm.stats.fmri_spec.sess(sesCounter).multi = ... + matlabbatch{end}.spm.stats.fmri_spec.sess(sesCounter).multi = ... cellstr(fullpathOnsetFileName); % get realignment parameters realignParamFile = getRealignParamFile(fullpathBoldFileName, prefix); - matlabbatch{1}.spm.stats.fmri_spec.sess(sesCounter).multi_reg = ... + matlabbatch{end}.spm.stats.fmri_spec.sess(sesCounter).multi_reg = ... cellstr(realignParamFile); % multiregressor selection - matlabbatch{1}.spm.stats.fmri_spec.sess(sesCounter).regress = ... + matlabbatch{end}.spm.stats.fmri_spec.sess(sesCounter).regress = ... struct('name', {}, 'val', {}); % multicondition selection - matlabbatch{1}.spm.stats.fmri_spec.sess(sesCounter).cond = ... + matlabbatch{end}.spm.stats.fmri_spec.sess(sesCounter).cond = ... struct('name', {}, 'onset', {}, 'duration', {}); % The following lines are commented out because those parameters % can be set in the spm_my_defaults.m - % matlabbatch{1}.spm.stats.fmri_spec.sess(ses_counter).hpf = 128; + % matlabbatch{end}.spm.stats.fmri_spec.sess(ses_counter).hpf = 128; sesCounter = sesCounter + 1; diff --git a/src/batches/setBatchSubjectLevelResults.m b/src/batches/setBatchSubjectLevelResults.m new file mode 100644 index 00000000..0b5cd0f9 --- /dev/null +++ b/src/batches/setBatchSubjectLevelResults.m @@ -0,0 +1,84 @@ +% (C) Copyright 2019 CPP BIDS SPM-pipeline developers + +function matlabbatch = setBatchSubjectLevelResults(varargin) + % + % Short description of what the function does goes here. + % + % USAGE:: + % + % matlabbatch = setBatchSubjectLevelResults(matlabbatch, opt, subID, funcFWHM, iStep, iCon) + % + % :param matlabbatch: + % :type matlabbatch: structure + % :param opt: + % :type opt: structure + % :param subID: + % :type subID: string + % :param funcFWHM: + % :type funcFWHM: float + % :param iStep: + % :type iStep: positive integer + % :param iCon: + % :type iCon: positive integer + % + % :returns: - :matlabbatch: (structure) + % + + [matlabbatch, opt, subID, funcFWHM, iStep, iCon] = deal(varargin{:}); + + result.Contrasts = opt.result.Steps(iStep).Contrasts(iCon); + + if isfield(opt.result.Steps(iStep), 'Output') + result.Output = opt.result.Steps(iStep).Output; + end + result.space = opt.space; + + result.dir = getFFXdir(subID, funcFWHM, opt); + result.label = subID; + result.nbSubj = 1; + + result.contrastNb = getContrastNb(result); + + matlabbatch = setBatchResults(matlabbatch, result); + +end + +function contrastNb = getContrastNb(result) + % + % identify which contrast nb actually has the name the user asked + % + + load(fullfile(result.dir, 'SPM.mat')); + + if isempty(result.Contrasts.Name) + + printAvailabileContrasts(SPM); + + errorStruct.identifier = 'setBatchSubjectLevelResults:missingContrastName'; + errorStruct.message = 'No contrast name specified'; + error(errorStruct); + + end + + contrastNb = find(strcmp({SPM.xCon.name}', result.Contrasts.Name)); + + if isempty(contrastNb) + + printAvailabileContrasts(SPM); + + errorStruct.identifier = 'setBatchSubjectLevelResults:NoMatchingContrastName'; + errorStruct.message = sprintf( ... + 'This SPM file %s does not contain a contrast named %s', ... + fullfile(result.dir, 'SPM.mat'), ... + result.Contrasts.Name); + + error(errorStruct); + + end + +end + +function printAvailabileContrasts(SPM) + sprintf('List of contrast in this SPM file'); + disp({SPM.xCon.name}'); +end diff --git a/src/defaults/checkOptions.m b/src/defaults/checkOptions.m index b87cd0bf..12f060e9 100644 --- a/src/defaults/checkOptions.m +++ b/src/defaults/checkOptions.m @@ -1,14 +1,117 @@ % (C) Copyright 2019 CPP BIDS SPM-pipeline developers function opt = checkOptions(opt) - % opt = checkOptions(opt) % - % we check the option inputs and add any missing field with some defaults + % Check the option inputs and add any missing field with some defaults + % + % Then it will search the derivatives directory for any zipped ``*.gz`` image + % and uncompress the files for the task of interest. + % + % USAGE:: + % + % opt = checkOptions(opt) + % + % :param opt: structure or json filename containing the options. + % :type opt: structure + % + % :returns: + % + % - :opt: the option structure with missing values filled in by the defaults. + % + % REQUIRED FIELDS: + % - ``opt.taskName`` + % - ``opt.dataDir`` + % + % IMPORTANT OPTIONS (with their defaults): + % - ``opt.groups = {''}`` - group of subjects to analyze + % - ``opt.subjects = {[]}`` - suject to run in each group + % space where we conduct the analysis + % - ``opt.derivativesDir = ''`` - directory where the raw and derivatives + % are located. See ``setDerivativesDir()`` for more information. + % - ``opt.space = 'MNI'`` - Space where we conduct the analysis + % - ``opt.realign.useUnwarp = true`` + % - ``opt.useFieldmaps = true`` - when set to ``true`` the + % preprocessing pipeline will look for the voxel displacement maps (created by + % ``bidsCreateVDM()``) and will use them for realign and unwarp. + % - ``opt.model.file = ''`` - path to the BIDS model file that contains the + % model to speficy and the contrasts to compute. + % + % OTHER OPTIONS (with their defaults): + % - ``opt.zeropad = 2`` - number of zeros used for padding subject numbers, in case + % subjects should be fetched by their number ``1`` and not their label ``O1'``. + % - ``opt.anatReference.type = 'T1w'`` - type of the anatomical reference + % - ``opt.anatReference.session = 1`` - session number of the anatomical reference + % - ``opt.skullstrip.threshold = 0.75`` - Threshold used for the skull stripping. + % Any voxel with ``p(grayMatter) + p(whiteMatter) + p(CSF) > threshold`` + % will be included in the mask. + % - ``opt.funcVoxelDims = []`` - Voxel dimensions to use for resampling of functional data + % at normalization. + % - ``opt.STC_referenceSlice = []`` - reference slice for the slice timing correction. + % If left emtpy the mid-volume acquisition time point will be selected at run time. + % - ``opt.sliceOrder = []`` - To be used if SPM can't extract slice info. NOT RECOMMENDED: + % if you know the order in which slices were acquired, you should be able to recompute + % slice timing and add it to the json files in your BIDS data set. + % fieldsToSet = setDefaultOption(); opt = setDefaultFields(opt, fieldsToSet); + checkFields(opt); + + if ~isempty(opt.dataDir) + opt.dataDir = spm_file(opt.dataDir, 'cpath'); + end + + opt = orderfields(opt); + +end + +function fieldsToSet = setDefaultOption() + % this defines the missing fields + + fieldsToSet.dataDir = ''; + fieldsToSet.derivativesDir = ''; + + fieldsToSet.groups = {''}; + fieldsToSet.subjects = {[]}; + fieldsToSet.zeropad = 2; + + fieldsToSet.anatReference.type = 'T1w'; + fieldsToSet.anatReference.session = []; + + %% Options for slice time correction + % all in seconds + fieldsToSet.STC_referenceSlice = []; + fieldsToSet.sliceOrder = []; + + %% Options for realign + fieldsToSet.realign.useUnwarp = true; + fieldsToSet.useFieldmaps = true; + + %% Options for segmentation + fieldsToSet.skullstrip.threshold = 0.75; + + %% Options for normalize + fieldsToSet.space = 'MNI'; + fieldsToSet.funcVoxelDims = []; + + %% Options for model specification and results + fieldsToSet.model.file = ''; + fieldsToSet.model.hrfDerivatives = [0 0]; + fieldsToSet.contrastList = {}; + + % specify the results to compute + fieldsToSet.result.Steps = returnDefaultResultsStructure(); + + fieldsToSet.parallelize.do = false; + fieldsToSet.parallelize.nbWorkers = 3; + fieldsToSet.parallelize.killOnExit = true; + +end + +function checkFields(opt) + if ~isfield(opt, 'taskName') || isempty(opt.taskName) errorStruct.identifier = 'checkOptions:noTask'; @@ -51,65 +154,4 @@ end - opt = orderfields(opt); - -end - -function fieldsToSet = setDefaultOption() - % this defines the missing fields - - % group of subjects to analyze - fieldsToSet.groups = {''}; - % suject to run in each group - fieldsToSet.subjects = {[]}; - fieldsToSet.zeropad = 2; - - % session number and type of the anatomical reference - fieldsToSet.anatReference.type = 'T1w'; - fieldsToSet.anatReference.session = 1; - - % any voxel with p(grayMatter) + p(whiteMatter) + p(CSF) > threshold - % will be included in the skull stripping mask - fieldsToSet.skullstrip.threshold = 0.75; - - % space where we conduct the analysis - fieldsToSet.space = 'MNI'; - - % The directory where the raw and derivatives are located - fieldsToSet.dataDir = ''; - fieldsToSet.derivativesDir = ''; - - % Options for slice time correction - % If left unspecified the slice timing will be done using the mid-volume acquisition - % time point as reference. - % Slice order must be entered in time unit (ms) (this is the BIDS way of doing things) - % instead of the slice index of the reference slice (the "SPM" way of doing things). - % More info here: https://en.wikibooks.org/wiki/SPM/Slice_Timing - fieldsToSet.STC_referenceSlice = []; % reference slice: middle acquired slice - fieldsToSet.sliceOrder = []; % To be used if SPM can't extract slice info - - % when opt.ignoreFieldmaps is set to false, the - % preprocessing pipeline will look for the voxel displacement maps (created by - % the corresponding workflow) and will use them for realign and unwarp - fieldsToSet.ignoreFieldmaps = false; - - % fieldsToSet for normalize - % Voxel dimensions for resampling at normalization of functional data or leave empty [ ]. - fieldsToSet.funcVoxelDims = []; - - % specify the model file that contains the contrasts to compute - fieldsToSet.contrastList = {}; - fieldsToSet.model.file = ''; - - % specify the results to compute - fieldsToSet.result.Steps = struct( ... - 'Level', '', ... % dataset, run, subject - 'Contrasts', struct( ... - 'Name', '', ... - 'Mask', false, ... - 'MC', 'FWE', ... % FWE, none, FDR - 'p', 0.05, ... - 'k', 0, ... - 'NIDM', true)); - end diff --git a/src/defaults/checkOptionsSource.m b/src/defaults/checkOptionsSource.m new file mode 100644 index 00000000..3320f740 --- /dev/null +++ b/src/defaults/checkOptionsSource.m @@ -0,0 +1,74 @@ +% (C) Copyright 2020 CPP BIDS SPM-pipeline developers + +function optSource = checkOptionsSource(optSource) + % + % Check the option inputs for source data and add any missing field with some defaults + % + % USAGE:: + % + % optSource = checkOptionsSource(optSource) + % + % :param optSource: Obligatory argument. The structure that contains the options set by the user + % to run the batch workflow for source processing + % + % :returns: - :optSource: (struc) The structure with any unset fields with the deaufalt values + % + % OPTIONS (with their defaults): + % - ``optSource.sourceDir = ''`` - The directory where the source data are located. + % - ``optSource.dataDir = ''`` - The directory where the raw data to apply changes are located. + % - ``optSource.sequenceToIgnore = {}`` - The list of sequence(s) to ignore. + % - ``optSource.dataType = 0`` - Data format conversion (0 is reccomended). + % - ``optSource.zip = 0`` - Boolean to enable gzip of the new 4D file in ``convert3Dto4D``. + % - ``optSource.nbDummies = 0`` - Number of volumes to discard ad dummies in ``convert3Dto4D``. + % - ``optSource.sequenceRmDummies = {}`` - The list of sequence(s) where to discarding the + % dummies. + + fieldsToSet = setDefaultOptionSource(); + + optSource = setDefaultFields(optSource, fieldsToSet); + + if isempty(optSource.sourceDir) || ~isdir(optSource.sourceDir) + + warning('The source folder is not provided or does not exist.'); + + end + + if isempty(optSource.dataDir) || ~isdir(optSource.dataDir) + + warning('The raw folder is not provided or does not exist.'); + + end + + if isempty(optSource.sequenceToIgnore) + + warning('No sequence-to-ignore provided, I will convert all the images that I can found'); + + end + +end + +function fieldsToSet = setDefaultOptionSource() + % This defines the missing fields + + % The directory where the source data are located + fieldsToSet.sourceDir = ''; + + % The directory where the raw data to apply changes are located + fieldsToSet.dataDir = ''; + + % The list of sequence(s) to ignore + fieldsToSet.sequenceToIgnore = {}; + + % Data format conversion (0 is reccomended) + fieldsToSet.dataType = 0; + + % Boolean to enable gzip of the new 4D file + fieldsToSet.zip = 0; + + % Number of volumes to discard ad dummies + fieldsToSet.nbDummies = 0; + + % The list of sequence(s) where to discarding the dummies + fieldsToSet.sequenceRmDummies = {}; + +end diff --git a/src/defaults/createDefaultModel.m b/src/defaults/createDefaultModel.m new file mode 100644 index 00000000..0de2252f --- /dev/null +++ b/src/defaults/createDefaultModel.m @@ -0,0 +1,132 @@ +% (C) Copyright 2020 CPP BIDS SPM-pipeline developers + +function opt = createDefaultModel(BIDS, opt) + % + % Creates a default model json file. + % This model has 3 "steps" in that order: + % + % - Subject level: + % - will create a GLM with a design matrix that includes all + % all the possible type of trial_types that exist across + % all subjects and runs for the task specified in ``opt``, + % as well as the realignment parameters. + % + % - use AutoContrasts to generate contrasts for all each trial_type + % across runs + % + % - Run level: + % - will create a GLM with a design matrix that includes all + % all the possible type of trial_types that exist across + % all subjects and runs for the task specified in ``opt``, + % as well as the realignment parameters. + % + % - use AutoContrasts to generate contrasts for each trial_type + % for each run. This can be useful to run MVPA analysis on the beta + % images of each run. + % + % - Dataset level: + % + % - use AutoContrasts to generate contrasts for each trial_type + % for at the group level. + % + % USAGE:: + % + % opt = createDefaultModel(BIDS, opt) + % + % :output: + % + % - a model file in the current directory:: + % + % fullfile(pwd, 'models', ['model-default' opt.taskName '_smdl.json']); + % + % EXAMPLE:: + % + % opt.taskName = 'myFascinatingTask'; + % opt.derivativesDir = fullfile(pwd, 'data', 'raw'); + % opt = checkOptions(opt); + % + % [~, opt, BIDS] = getData(opt); + % + % createDefaultModel(BIDS, opt); + % + + % TODO deal with the Transformations and Convolve fields + + jsonOptions.indent = ' '; + + trialTypeList = listAllTrialTypes(BIDS, opt); + + content = returnEmptyModel(); + + content = fillDefaultDesginMatrixAndContrasts(content, trialTypeList); + + content.Name = opt.taskName; + content.Description = ['default model for ' opt.taskName]; + content.Input.task = opt.taskName; + + % save the json file + [~, ~, ~] = mkdir(fullfile(pwd, 'models')); + filename = fullfile(pwd, 'models', ... + ['model-default' upper(opt.taskName(1)) opt.taskName(2:end) '_smdl.json']); + + spm_jsonwrite(filename, content, jsonOptions); + + opt.model.file = filename; + +end + +function trialTypeList = listAllTrialTypes(BIDS, opt) + % list all the *events.tsv files for that task and make a lis of all the + % trial_types + eventFiles = bids.query(BIDS, 'data', ... + 'type', 'events', ... + 'task', opt.taskName); + + trialTypeList = {}; + + for iFile = 1:size(eventFiles, 1) + tmp = spm_load(eventFiles{iFile, 1}); + for iTrialType = 1:numel(tmp.trial_type) + trialTypeList{end + 1, 1} = tmp.trial_type{iTrialType}; %#ok<*AGROW> + end + end + + trialTypeList = unique(trialTypeList); + idx = ismember(trialTypeList, 'trial_type'); + if any(idx) + trialTypeList{idx} = []; + end + +end + +function content = fillDefaultDesginMatrixAndContrasts(content, trialTypeList) + + REALIGN_PARAMETERS_NAME = { ... + 'trans_x', 'trans_y', 'trans_z', ... + 'rot_x', 'rot_y', 'rot_z'}; + + for iTrialType = 1:numel(trialTypeList) + + if ~isempty(trialTypeList{iTrialType}) + trialTypeName = ['trial_type.' trialTypeList{iTrialType}]; + + % subject + content.Steps{1}.Model.X{iTrialType} = trialTypeName; + + % run + content.Steps{2}.Model.X{iTrialType} = trialTypeName; + + for iStep = 1:numel(content.Steps) + content.Steps{iStep}.AutoContrasts{iTrialType} = trialTypeName; + end + end + + end + + % add realign parameters + for iRealignParam = 1:numel(REALIGN_PARAMETERS_NAME) + content.Steps{1}.Model.X{end + 1} = REALIGN_PARAMETERS_NAME{iRealignParam}; + content.Steps{2}.Model.X{end + 1} = REALIGN_PARAMETERS_NAME{iRealignParam}; + end + +end diff --git a/src/defaults/datasetDescriptionDefaults.m b/src/defaults/datasetDescriptionDefaults.m new file mode 100644 index 00000000..a9bc50bf --- /dev/null +++ b/src/defaults/datasetDescriptionDefaults.m @@ -0,0 +1,27 @@ +% (C) Copyright 2020 CPP BIDS SPM-pipeline developers + +function datasetDescription = datasetDescriptionDefaults() + + datasetDescription.Name = 'cpp_spm outputs'; + datasetDescription.BIDSVersion = '1.4.1'; + datasetDescription.DatasetType = 'derivative'; + + datasetDescription.GeneratedBy = {struct( ... + 'Name', 'cpp_spm', ... + 'Version', getVersion(), ... + 'Container', struct('Type', '', 'Tag', ''))}; + + % RECOMMENDED + datasetDescription.License = ''; + datasetDescription.Authors = {''}; + datasetDescription.Acknowledgements = ''; + datasetDescription.HowToAcknowledge = ''; + datasetDescription.Funding = {''}; + datasetDescription.ReferencesAndLinks = {''}; + datasetDescription.DatasetDOI = ''; + datasetDescription.SourceDatasets = {struct('DOI', '', 'URL', '', 'Version', '')}; + + % sort fields alphabetically + datasetDescription = orderfields(datasetDescription); + +end diff --git a/src/defaults/returnDefaultResultsStructure.m b/src/defaults/returnDefaultResultsStructure.m new file mode 100644 index 00000000..96cbeb08 --- /dev/null +++ b/src/defaults/returnDefaultResultsStructure.m @@ -0,0 +1,31 @@ +% (C) Copyright 2019 CPP BIDS SPM-pipeline developers + +function results = returnDefaultResultsStructure() + + Contrasts = struct( ... + 'Name', '', ... + 'useMask', false(), ... + 'MC', 'FWE', ... % FWE, none, FDR + 'p', 0.05, ... + 'k', 0); + + Output = struct( ... + 'png', false(), ... + 'csv', false(), ... + 'thresh_spm', false(), ... + 'binary', false(), ... + 'montage', struct( ... + 'do', false(), ... + 'slices', [], ... + 'orientation', 'axial', ... + 'background', fullfile(spm('dir'), ... + 'canonical', ... + 'avg152T1.nii,1')), ... + 'NIDM_results', false()); + + results = struct( ... + 'Level', '', ... + 'Contrasts', Contrasts, ... + 'Output', Output); + +end diff --git a/src/defaults/returnEmptyModel.m b/src/defaults/returnEmptyModel.m new file mode 100644 index 00000000..3148a97f --- /dev/null +++ b/src/defaults/returnEmptyModel.m @@ -0,0 +1,69 @@ +% (C) Copyright 2020 CPP BIDS SPM-pipeline developers + +function content = returnEmptyModel() + % + % Creates the content of a basic model.json file for GLM analysis with + % some default options like high pass filter cut-off + % and the type of autocorrelation correction. + % + % USAGE:: + % + % content = createEmptytModelBasic() + % + % :returns: + % + % - :content: structure containing the output that can be saved with + % ``spm_jsonwrite()``. See below. + % + % EXAMPLE:: + % + % jsonOptions.indent = ' '; + % content = createEmptytModelBasic() + % filename = fullfile(pwd, 'models', 'model-empty_smdl.json') + % spm_jsonwrite(filename, content, jsonOptions); + % + + content.Name = ' '; + content.Description = ' '; + content.Input = struct('task', ' '); + content.Steps = { ... + struct( ... + 'Level', 'subject', ... + 'Transformations', {returnTransformation()}, ... + 'Model', returnModel(), ... + 'AutoContrasts', {{' '}}); ... + struct( ... + 'Level', 'run', ... + 'Transformations', {returnTransformation()}, ... + 'Model', returnModel(), ... + 'AutoContrasts', {{' '}} ... + ); ... + struct( ... + 'Level', 'dataset', ... + 'AutoContrasts', {{' '}} ... + ) ... + }; + +end + +function transformation = returnTransformation() + + transformation = {struct( ... + 'Name', 'Factor', ... + 'Inputs', {{'trial_type'}} ... + ); ... + struct( ... + 'Name', 'Convolve', ... + 'Model', 'spm', ... + 'Inputs', {{' '}} ... + )}; + +end + +function model = returnModel() + model = struct( ... + 'X', {{' '}}, ... + 'Options', struct('high_pass_filter_cutoff_secs', 128), ... + 'Software', struct('SPM', struct('whitening', 'FAST')), ... + 'Mask', {' '}); +end diff --git a/src/defaults/spm_my_defaults.m b/src/defaults/spm_my_defaults.m index f2cf37d7..43b67496 100644 --- a/src/defaults/spm_my_defaults.m +++ b/src/defaults/spm_my_defaults.m @@ -1,6 +1,15 @@ % (C) Copyright 2019 CPP BIDS SPM-pipeline developers -function spm_my_defaults +function spm_my_defaults() + % + % Short description of what the function does goes here. + % + % USAGE:: + % + % [argout1, argout2] = templateFunction(argin1, [argin2 == default,] [argin3]) + % + % + % % This is where we set the defautls we want to use. % These will overide the spm defaults. % When "not enough" information is specified in the batch files, SPM falls diff --git a/src/fieldmaps/getMetadataFromIntendedForFunc.m b/src/fieldmaps/getMetadataFromIntendedForFunc.m index 5f5b1218..29d8ee57 100644 --- a/src/fieldmaps/getMetadataFromIntendedForFunc.m +++ b/src/fieldmaps/getMetadataFromIntendedForFunc.m @@ -1,28 +1,31 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers function [totalReadoutTime, blipDir] = getMetadataFromIntendedForFunc(BIDS, fmapMetadata) - % get metadata of the associated bold file - % find bold file this fmap is intended for, parse its filename and get its - % metadata + % + % Gets metadata of the associated bold file: + % - finds the bold file a fmap is intended for, + % - parse its filename, + % - get its metadata. % % USAGE:: % % [totalReadoutTime, blipDir] = getMetadataFromIntendedForFunc(BIDS, fmapMetadata) % - % :param BIDS: + % :param BIDS: BIDS layout returned by ``getData()``. % :type BIDS: structure % :param fmapMetadata: % :type fmapMetadata: structure % - % :returns: - :totalReadoutTime: (type) (dimension) - % - :blipDir: (type) (dimension) + % :returns: :totalReadoutTime: (type) (dimension) + % :blipDir: (type) (dimension) % % At the moment the VDM is created based on the characteristics of the last % func file in the IntendedFor field % - % TODO - % - if there are several func file for this fmap and they have different - % characteristic this may require creating a VDM for each + % .. TODO: + % + % - if there are several func file for this fmap and they have different + % characteristic this may require creating a VDM for each for iFile = 1:size(fmapMetadata.IntendedFor) @@ -35,13 +38,17 @@ fragments = bids.internal.parse_filename(filename); - funcMetadata = spm_BIDS(BIDS, 'metadata', ... - 'modality', 'func', ... - 'type', fragments.type, ... - 'sub', fragments.sub, ... - 'ses', fragments.ses, ... - 'run', fragments.run, ... - 'acq', fragments.acq); + if ~isfield(fragments, 'acq') + fragments.acq = ''; + end + + funcMetadata = bids.query(BIDS, 'metadata', ... + 'modality', 'func', ... + 'type', fragments.type, ... + 'sub', fragments.sub, ... + 'ses', fragments.ses, ... + 'run', fragments.run, ... + 'acq', fragments.acq); end diff --git a/src/fieldmaps/getVdmFile.m b/src/fieldmaps/getVdmFile.m index b83dfade..2c96416e 100644 --- a/src/fieldmaps/getVdmFile.m +++ b/src/fieldmaps/getVdmFile.m @@ -10,7 +10,7 @@ % % :param BIDS: % :type BIDS: structure - % :param opt: options + % :param opt: Options chosen for the analysis. See ``checkOptions()``. % :type opt: structure % :param boldFilename: % :type opt: string @@ -26,25 +26,25 @@ fragments.ses = ''; end - modalities = spm_BIDS(BIDS, 'modalities', ... - 'sub', fragments.sub, ... - 'ses', fragments.ses); + modalities = bids.query(BIDS, 'modalities', ... + 'sub', fragments.sub, ... + 'ses', fragments.ses); - if ~opt.ignoreFieldmaps && any(ismember('fmap', modalities)) + if opt.useFieldmaps && any(ismember('fmap', modalities)) % We loop through the field maps and find the one that is intended for this % bold file by reading from the metadata % % We break the loop when the file has been found - fmapFiles = spm_BIDS(BIDS, 'data', ... - 'modality', 'fmap', ... - 'sub', fragments.sub, ... - 'ses', fragments.ses); + fmapFiles = bids.query(BIDS, 'data', ... + 'modality', 'fmap', ... + 'sub', fragments.sub, ... + 'ses', fragments.ses); - fmapMetadata = spm_BIDS(BIDS, 'metadata', ... - 'modality', 'fmap', ... - 'sub', fragments.sub, ... - 'ses', fragments.ses); + fmapMetadata = bids.query(BIDS, 'metadata', ... + 'modality', 'fmap', ... + 'sub', fragments.sub, ... + 'ses', fragments.ses); for iFile = 1:size(fmapFiles, 1) diff --git a/src/getAnatFilename.m b/src/getAnatFilename.m index e1361e47..74b83485 100644 --- a/src/getAnatFilename.m +++ b/src/getAnatFilename.m @@ -1,6 +1,23 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers function [anatImage, anatDataDir] = getAnatFilename(BIDS, subID, opt) + % + % Short description of what the function does goes here. + % + % USAGE:: + % + % [argout1, argout2] = templateFunction(argin1, [argin2 == default,] [argin3]) + % + % :param argin1: (dimension) obligatory argument. Lorem ipsum dolor sit amet, + % consectetur adipiscing elit. Ut congue nec est ac lacinia. + % :type argin1: type + % :param opt: Options chosen for the analysis. See ``checkOptions()``. + % :type opt: structure + % :param argin3: (dimension) optional argument + % + % :returns: - :argout1: (type) (dimension) + % - :argout2: (type) (dimension) + % % [anatImage, anatDataDir] = getAnatFilename(BIDS, subID, opt) % % Get the filename and the directory of an anat file for a given session / @@ -9,15 +26,29 @@ anatType = opt.anatReference.type; - % TODO allow for the session to be referenced by a string e.g ses-retest - anatSession = opt.anatReference.session; + sessions = getInfo(BIDS, subID, opt, 'Sessions'); % get all anat images for that subject fo that type - sessions = getInfo(BIDS, subID, opt, 'Sessions'); - anat = spm_BIDS(BIDS, 'data', ... - 'sub', subID, ... - 'ses', sessions{anatSession}, ... - 'type', anatType); + % TODO allow for the session to be referenced by a string e.g ses-retest + anat = bids.query(BIDS, 'data', ... + 'sub', subID, ... + 'type', anatType); + if ~isempty(opt.anatReference.session) + anatSession = opt.anatReference.session; + anat = bids.query(BIDS, 'data', ... + 'sub', subID, ... + 'ses', sessions{anatSession}, ... + 'type', anatType); + end + + if isempty(anat) + anat = bids.query(BIDS, 'data', ... + 'sub', subID, ... + 'type', anatType); + error('No anat file for the subject %s. Here are all anat file:\n%s', ... + subID, ... + char(anat)); + end % TODO % We assume that the first anat of that type is the correct one diff --git a/src/getBoldFilename.m b/src/getBoldFilename.m index bd453c1e..6c5f5342 100644 --- a/src/getBoldFilename.m +++ b/src/getBoldFilename.m @@ -1,6 +1,26 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers function [boldFileName, subFuncDataDir] = getBoldFilename(varargin) + % + % Short description of what the function does goes here. + % + % USAGE:: + % + % [argout1, argout2] = templateFunction(argin1, [argin2 == default,] [argin3]) + % + % :param argin1: (dimension) obligatory argument. Lorem ipsum dolor sit amet, + % consectetur adipiscing elit. Ut congue nec est ac lacinia. + % :type argin1: type + % :param argin2: optional argument and its default value. And some of the + % options can be shown in litteral like ``this`` or ``that``. + % :type argin2: string + % :param argin3: (dimension) optional argument + % :param opt: Options chosen for the analysis. See ``checkOptions()``. + % :type opt: structure + % + % :returns: - :argout1: (type) (dimension) + % - :argout2: (type) (dimension) + % % [fileName, subFuncDataDir] = getBoldFilename(BIDS, opt, subID, sessionID, runID) % % Get the filename and the directory of a bold file for a given session / diff --git a/src/getData.m b/src/getData.m index 956dea12..d28da2b2 100644 --- a/src/getData.m +++ b/src/getData.m @@ -1,44 +1,63 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers function [group, opt, BIDS] = getData(opt, BIDSdir, type) - % [group, opt, BIDS] = getData(opt, BIDSdir, type) - % - % getData checks that all the options specified by the user in getOptions - % and fills the blank for any that might have been missed out. - % It then reads the specified BIDS data set and gets the groups and - % subjects to analyze. This can be specified in the opt structure in - % different ways: - % Set the group of subjects to analyze. - % opt.groups = {'control', 'blind'}; - % - % If there are no groups (i.e subjects names are of the form `sub-01` for - % example) or if you want to run all subjects of all groups then use: - % opt.groups = {''}; - % opt.subjects = {[]}; - % - % If you have 2 groups (`cont` and `cat` for example) the following will - % run cont01, cont02, cat03, cat04. - % opt.groups = {'cont', 'cat'}; - % opt.subjects = {[1 2], [3 4]}; + % + % Short description of what the function does goes here. + % + % USAGE:: + % + % [group, opt, BIDS] = getData(opt, [BIDSdir], [type = 'bold']) + % + % :param opt: Options chosen for the analysis. See ``checkOptions()``. + % :type opt: structure + % :param BIDSdir: the directory where the data is ; default is : + % ``fullfile(opt.dataDir, '..', 'derivatives', 'cpp_spm')`` + % :type BIDSdir: string + % :param type: the data type you want to get the metadata of; + % supported: ``'bold'`` (default) and ``T1w`` + % :type type: string + % + % :returns: - :group: (structure) + % - :opt: (structure) + % - :BIDS: (structure) + % + % ``getData()`` reads the specified BIDS data set and gets the groups and + % subjects to analyze. This can be specified in the opt structure in different ways. + % + % Set the group of subjects to analyze:: + % + % opt.groups = {'control', 'blind'}; + % + % If there are no groups (i.e subjects names are of the form ``sub-01`` for + % example) or if you want to run all subjects of all groups then use:: + % + % opt.groups = {''}; + % opt.subjects = {[]}; + % + % If you have 2 groups (``cont`` and ``cat`` for example) the following will + % run ``cont01``, ``cont02``, ``cat03``, ``cat04``:: + % + % opt.groups = {'cont', 'cat'}; + % opt.subjects = {[1 2], [3 4]}; % % If you have more than 2 groups but want to only run the subjects of 2 - % groups then you can use. - % opt.groups = {'cont', 'cat'}; - % opt.subjects = {[], []}; + % groups then you can use:: + % + % opt.groups = {'cont', 'cat'}; + % opt.subjects = {[], []}; + % + % You can also directly specify the subject label for the participants you + % want to run:: % - % You can also directly specify the subject label for the participants you want to run - % opt.groups = {''}; - % opt.subjects = {'01', 'cont01', 'cat02', 'ctrl02', 'blind01'}; + % opt.groups = {''}; + % opt.subjects = {'01', 'cont01', 'cat02', 'ctrl02', 'blind01'}; % - % You can also specify: - % - BIDSdir: the directory where the data is ; default is : - % fullfile(opt.dataDir, '..', 'derivatives', 'SPM12_CPPL') - % - type: the data type you want to get the metadata of ; - % supported: bold (default) and T1w + % .. todo + % Check if the following is true? Ideally write a test to make sure. % % IMPORTANT NOTE: if you specify the type variable for T1w then you must % make sure that the T1w.json is also present in the anat folder because - % of the way the spm_BIDS function works at the moment + % of the way the bids.query function works at the moment if nargin < 2 || (exist('BIDSdir', 'var') && isempty(BIDSdir)) % The directory where the derivatives are located @@ -54,12 +73,12 @@ fprintf(1, 'FOR TASK: %s\n', opt.taskName); % we let SPM figure out what is in this BIDS data set - BIDS = spm_BIDS(derivativesDir); + BIDS = bids.layout(derivativesDir); % make sure that the required tasks exist in the data set - if ~ismember(opt.taskName, spm_BIDS(BIDS, 'tasks')) + if ~ismember(opt.taskName, bids.query(BIDS, 'tasks')) fprintf('List of tasks present in this dataset:\n'); - spm_BIDS(BIDS, 'tasks'); + bids.query(BIDS, 'tasks'); errorStruct.identifier = 'getData:noMatchingTask'; errorStruct.message = sprintf( ... @@ -69,7 +88,7 @@ end % get IDs of all subjects - subjects = spm_BIDS(BIDS, 'subjects'); + subjects = bids.query(BIDS, 'subjects'); % get metadata for bold runs for that task % we take those from the first run of the first subject assuming it can @@ -111,19 +130,20 @@ function opt = getMetaData(BIDS, opt, subjects, type) + % TODO % THIS NEEDS FIXING AS WE MIGHT WANT THE METADATA OF THE SUBJECTS SELECTED % RATHER THAN THE FIRST SUBJECT OF THE DATASET switch type case 'bold' - metadata = spm_BIDS(BIDS, 'metadata', ... - 'task', opt.taskName, ... - 'sub', subjects{1}, ... - 'type', type); + metadata = bids.query(BIDS, 'metadata', ... + 'task', opt.taskName, ... + 'sub', subjects{1}, ... + 'type', type); case 'T1w' - metadata = spm_BIDS(BIDS, 'metadata', ... - 'sub', subjects{1}, ... - 'type', type); + metadata = bids.query(BIDS, 'metadata', ... + 'sub', subjects{1}, ... + 'type', type); end if iscell(metadata) diff --git a/src/getFuncVoxelDims.m b/src/getFuncVoxelDims.m index 8ac87338..99c825b2 100644 --- a/src/getFuncVoxelDims.m +++ b/src/getFuncVoxelDims.m @@ -1,6 +1,23 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers function [voxDim, opt] = getFuncVoxelDims(opt, subFuncDataDir, prefix, fileName) + % + % Short description of what the function does goes here. + % + % USAGE:: + % + % [argout1, argout2] = templateFunction(argin1, [argin2 == default,] [argin3]) + % + % :param opt: Options chosen for the analysis. See ``checkOptions()``. + % :type opt: structure + % :param argin2: optional argument and its default value. And some of the + % options can be shown in litteral like ``this`` or ``that``. + % :type argin2: string + % :param argin3: (dimension) optional argument + % + % :returns: - :argout1: (type) (dimension) + % - :argout2: (type) (dimension) + % % [voxDim, opt] = getFuncVoxelDims(opt, subFuncDataDir, prefix, fileName) % % diff --git a/src/getInfo.m b/src/getInfo.m index 8b137b9a..8e56ef5a 100644 --- a/src/getInfo.m +++ b/src/getInfo.m @@ -1,21 +1,15 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers function varargout = getInfo(BIDS, subID, opt, info, varargin) - % wrapper function to fetch specific info in a BIDS structure returned by - % spm_bids. :: % - % varargout = getInfo(BIDS, subID, opt, info, varargin) + % Wrapper function to fetch specific info in a BIDS structure returned by + % spm_bids. % - % for a given BIDS data set, subject identity, and info type, + % USAGE:: % - % if info = Sessions, this returns name of the sessions and their number - % - % if info = Runs, this returns name of the runs and their number for an specified session. - % - % if info = Filename, this returns the name of the file for an specified - % session and run. + % varargout = getInfo(BIDS, subID, opt, info, varargin) % - % :param BIDS: (structure) returned by spm_BIDS when exploring a BIDS data set. + % :param BIDS: (structure) returned by bids.query when exploring a BIDS data set. % :param subID: ID of the subject % :param opt: (structure) Mostly used to find the task name. % :param info: (strint) ``sessions``, ``runs``, ``filename``. @@ -30,6 +24,15 @@ % - type - string ; modality type to look for. For example: ``bold``, ``events``, % ``stim``, ``physio`` % + % for a given BIDS data set, subject identity, and info type, + % + % if info = Sessions, this returns name of the sessions and their number + % + % if info = Runs, this returns name of the runs and their number for an specified session. + % + % if info = Filename, this returns the name of the file for an specified + % session and run. + % varargout = {}; %#ok<*NASGU> @@ -37,9 +40,9 @@ case 'sessions' - sessions = spm_BIDS(BIDS, 'sessions', ... - 'sub', subID, ... - 'task', opt.taskName); + sessions = bids.query(BIDS, 'sessions', ... + 'sub', subID, ... + 'task', opt.taskName); nbSessions = size(sessions, 2); if nbSessions == 0 nbSessions = 1; @@ -52,11 +55,11 @@ session = varargin{1}; - runs = spm_BIDS(BIDS, 'runs', ... - 'sub', subID, ... - 'task', opt.taskName, ... - 'ses', session, ... - 'type', 'bold'); + runs = bids.query(BIDS, 'runs', ... + 'sub', subID, ... + 'task', opt.taskName, ... + 'ses', session, ... + 'type', 'bold'); nbRuns = size(runs, 2); % Get the number of runs if nbRuns == 0 @@ -70,12 +73,12 @@ [session, run, type] = deal(varargin{:}); - varargout = spm_BIDS(BIDS, 'data', ... - 'sub', subID, ... - 'run', run, ... - 'ses', session, ... - 'task', opt.taskName, ... - 'type', type); + varargout = bids.query(BIDS, 'data', ... + 'sub', subID, ... + 'run', run, ... + 'ses', session, ... + 'task', opt.taskName, ... + 'type', type); otherwise error('Not sure what info you want me to get.'); diff --git a/src/getMeanFuncFilename.m b/src/getMeanFuncFilename.m index 63933da4..98297502 100644 --- a/src/getMeanFuncFilename.m +++ b/src/getMeanFuncFilename.m @@ -1,6 +1,25 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers function [meanImage, meanFuncDir] = getMeanFuncFilename(BIDS, subID, opt) + % + % Short description of what the function does goes here. + % + % USAGE:: + % + % [argout1, argout2] = templateFunction(argin1, [argin2 == default,] [argin3]) + % + % :param argin1: (dimension) obligatory argument. Lorem ipsum dolor sit amet, + % consectetur adipiscing elit. Ut congue nec est ac lacinia. + % :type argin1: type + % :param argin2: optional argument and its default value. And some of the + % options can be shown in litteral like ``this`` or ``that``. + % :type argin2: string + % :param opt: Options chosen for the analysis. See ``checkOptions()``. + % :type opt: structure + % + % :returns: - :argout1: (type) (dimension) + % - :argout2: (type) (dimension) + % % [anatImage, anatDataDir] = getAnatFilename(BIDS, subID, opt) % % Get the filename and the directory of an anat file for a given session / @@ -13,12 +32,12 @@ BIDS, ... subID, sessions{1}, runs{1}, opt); - prefix = getPrefix('smoothing_space-individual', opt); + prefix = getPrefix('mean', opt); meanImage = validationInputFile( ... subFuncDataDir, ... boldFileName, ... - ['mean' prefix]); + prefix); [meanFuncDir, meanImage, ext] = spm_fileparts(meanImage); meanImage = [meanImage ext]; diff --git a/src/getPrefix.m b/src/getPrefix.m index 8e6dbe15..e99d41cc 100644 --- a/src/getPrefix.m +++ b/src/getPrefix.m @@ -1,6 +1,23 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers function [prefix, motionRegressorPrefix] = getPrefix(step, opt, funcFWHM) + % + % Short description of what the function does goes here. + % + % USAGE:: + % + % [argout1, argout2] = templateFunction(argin1, [argin2 == default,] [argin3]) + % + % :param argin1: (dimension) obligatory argument. Lorem ipsum dolor sit amet, + % consectetur adipiscing elit. Ut congue nec est ac lacinia. + % :type argin1: type + % :param opt: Options chosen for the analysis. See ``checkOptions()``. + % :type opt: structure + % :param argin3: (dimension) optional argument + % + % :returns: - :argout1: (type) (dimension) + % - :argout2: (type) (dimension) + % % [prefix, motionRegressorPrefix] = getPrefix(step, opt, funcFWHM) % % generates prefix to append to file name to look for @@ -13,47 +30,63 @@ motionRegressorPrefix = ''; allowedPrefixCases = { - 'STC'; ... - 'preprocess'; ... - 'smoothing_space-individual'; ... - 'smoothing'; ... - 'FFX_space-individual'; ... + 'realign'; ... + 'normalise'; ... + 'funcQA'; ... + 'smooth'; ... 'FFX'}; - switch step + switch lower(step) - case 'STC' - - case 'preprocess' + case 'realign' prefix = prefixForSTC(prefix, opt); - % when smoothing is done on non-normalized data (in the space of T1w) - case 'smoothing_space-individual' - prefix = prefixForSTC(prefix, opt); - prefix = [spm_get_defaults('unwarp.write.prefix') prefix]; + case 'normalise' + prefix = getPrefix('realign', opt); - case 'smoothing' - prefix = prefixForSTC(prefix, opt); - prefix = [spm_get_defaults('normalise.write.prefix') prefix]; + if ~opt.realign.useUnwarp && strcmp(opt.space, 'individual') + prefix = [spm_get_defaults('realign.write.prefix') prefix]; + elseif opt.realign.useUnwarp + prefix = [spm_get_defaults('unwarp.write.prefix') prefix]; + end - case 'FFX_space-individual' - prefix = prefixForSTC(prefix, opt); - prefix = [spm_get_defaults('unwarp.write.prefix') prefix]; + case 'mean' + prefix = getPrefix('realign', opt); - % Check which level of smoothing is applied - if funcFWHM > 0 % else, take the smoothed files - prefix = [spm_get_defaults('smooth.prefix') num2str(funcFWHM) prefix]; + if opt.realign.useUnwarp + prefix = [spm_get_defaults('unwarp.write.prefix') prefix]; end - case 'FFX' - prefix = prefixForSTC(prefix, opt); - prefix = [spm_get_defaults('normalise.write.prefix') prefix]; + prefix = ['mean' prefix]; + + case 'funcqa' + prefix = getPrefix('realign', opt); + + if ~opt.realign.useUnwarp + prefix = [spm_get_defaults('realign.write.prefix') prefix]; + elseif opt.realign.useUnwarp + prefix = [spm_get_defaults('unwarp.write.prefix') prefix]; + end + + motionRegressorPrefix = prefixForSTC(prefix, opt); + + case 'smooth' + prefix = getPrefix('normalise', opt); + + if strcmp(opt.space, 'MNI') + prefix = [spm_get_defaults('normalise.write.prefix') prefix]; + end + + case 'ffx' + motionRegressorPrefix = prefixForSTC(prefix, opt); + + prefix = getPrefix('smooth', opt); - % Check which level of smoothing is applied if funcFWHM > 0 prefix = [spm_get_defaults('smooth.prefix') num2str(funcFWHM) prefix]; end + %% otherwise fprintf(1, '\nAllowed prefix cases:\n'); @@ -62,10 +95,10 @@ end errorStruct.identifier = 'getPrefix:unknownPrefixCase'; - errorStruct.message = sprintf('%s\n%s', ... + errorStruct.message = sprintf('%s%s\n%s', ... ['This prefix case you have requested ' ... - 'does not exist: %s.'], ... - 'See allowed cases above', step); + 'does not exist: '], step, ... + 'See allowed cases above'); error(errorStruct); end diff --git a/src/getRealignParamFile.m b/src/getRealignParamFile.m index 85182a30..d5cc6111 100644 --- a/src/getRealignParamFile.m +++ b/src/getRealignParamFile.m @@ -1,6 +1,23 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers function realignParamFile = getRealignParamFile(fullpathBoldFileName, prefix) + % + % Short description of what the function does goes here. + % + % USAGE:: + % + % [argout1, argout2] = templateFunction(argin1, [argin2 == default,] [argin3]) + % + % :param argin1: (dimension) obligatory argument. Lorem ipsum dolor sit amet, + % consectetur adipiscing elit. Ut congue nec est ac lacinia. + % :type argin1: type + % :param argin2: optional argument and its default value. And some of the + % options can be shown in litteral like ``this`` or ``that``. + % :type argin2: string + % :param argin3: (dimension) optional argument + % + % :returns: - :argout1: (type) (dimension) + % - :argout2: (type) (dimension) [funcDataDir, boldFileName] = spm_fileparts(fullpathBoldFileName); diff --git a/src/getSliceOrder.m b/src/getSliceOrder.m index 7671a206..8cc2f553 100644 --- a/src/getSliceOrder.m +++ b/src/getSliceOrder.m @@ -1,26 +1,45 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers function sliceOrder = getSliceOrder(opt, verbose) - % get the slice order information from the BIDS data set or from getOption % - % In the case the slice timing information was not specified in the json FILES - % in the BIDS data set then it will try to read the opt structure for any relevant information. + % Get the slice order information from the BIDS metadata or from the ``opt`` + % structure. + % + % USAGE:: + % + % sliceOrder = getSliceOrder(opt, [verbose = false]) + % + % :param opt: Options chosen for the analysis. See ``checkOptions()``. + % :type opt: structure + % :param verbose: + % :type verbose: boolean + % + % :returns: + % - :sliceOrder: a vector of the time when each slice was acquired in + % in a volume or indicating the order of acquisition of the slices. + % + % In the case the slice timing information was not specified in the json files + % in the BIDS data set then ``getSliceOrder`` will try to read the ``opt`` + % structure for any relevant information. % If this comes out empty then slice timing correction will be skipped. + % + % See also: ``bidsSTC`` + % if nargin < 2 - verbose = 0; + verbose = false; end msg = {}; wng = {}; - % IF slice timing is not in the metadata + % If slice timing is not in the metadata if ~isfield(opt.metadata, 'SliceTiming') || isempty(opt.metadata.SliceTiming) msg{end + 1} = ' SLICE TIMING INFORMATION COULD NOT BE EXTRACTED FROM METADATA.\n'; - msg{end + 1} = ' CHECKING IF SPECIFIED IN opt IN THE "getOption" FUNCTION.\n\n'; + msg{end + 1} = ' CHECKING IF SPECIFIED IN opt IN THE "opt" STRUCTURE.\n\n'; - % IF SLICE TIME information is not in the metadata, you have the option + % If slice timing information is not in the metadata, you have the option % to add the slice order manually in the "opt" in the "getOptions" % function if ~isempty(opt.sliceOrder) @@ -35,6 +54,7 @@ sliceOrder = []; end + else % Otherwise get the slice order from the metadata sliceOrder = opt.metadata.SliceTiming; diff --git a/src/getSpecificSubjects.m b/src/getSpecificSubjects.m index 190a48bd..0314deaa 100644 --- a/src/getSpecificSubjects.m +++ b/src/getSpecificSubjects.m @@ -1,7 +1,23 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers function group = getSpecificSubjects(opt, group, iGroup, subjects) - + % + % Short description of what the function does goes here. + % + % USAGE:: + % + % [argout1, argout2] = templateFunction(argin1, [argin2 == default,] [argin3]) + % + % :param opt: Options chosen for the analysis. See ``checkOptions()``. + % :type opt: structure + % :param argin2: optional argument and its default value. And some of the + % options can be shown in litteral like ``this`` or ``that``. + % :type argin2: string + % :param argin3: (dimension) optional argument + % + % :returns: - :argout1: (type) (dimension) + % - :argout2: (type) (dimension) + % % add a test for ata set with subject only blind02 and we ask for this one % specifically diff --git a/src/group_level/getGrpLevelContrastToCompute.m b/src/group_level/getGrpLevelContrastToCompute.m index 1833e6fa..52ec91ad 100644 --- a/src/group_level/getGrpLevelContrastToCompute.m +++ b/src/group_level/getGrpLevelContrastToCompute.m @@ -1,6 +1,19 @@ % (C) Copyright 2019 CPP BIDS SPM-pipeline developers function [grpLvlCon, iStep] = getGrpLevelContrastToCompute(opt) + % + % Returns the autocontrast part of the dataset step of the BIDS model + % + % USAGE:: + % + % function [grpLvlCon, iStep] = getGrpLevelContrastToCompute(opt) + % + % :param opt: Options chosen for the analysis. See ``checkOptions()``. + % :type opt: structure + % + % :returns: - :grpLvlCon: + % - :iStep: + % model = spm_jsonread(opt.model.file); diff --git a/src/group_level/getRFXdir.m b/src/group_level/getRFXdir.m index 052fdbe1..69e0ff51 100644 --- a/src/group_level/getRFXdir.m +++ b/src/group_level/getRFXdir.m @@ -1,17 +1,32 @@ % (C) Copyright 2019 CPP BIDS SPM-pipeline developers -function rfxDir = getRFXdir(opt, funcFWHM, conFWHM, contrastName) - % rfxDir = getRFXdir(opt, funcFWHM, conFWHM, iStep, iCon) +function rfxDir = getRFXdir(opt, funcFWHM, conFWHM) % - % sets the name the RFX directory and creates it if it does not exist + % Sets the name the group level analysis directory and creates it if it does not exist + % + % USAGE:: + % + % rfxDir = getRFXdir(opt, funcFWHM, conFWHM, contrastName) + % + % :param opt: Options chosen for the analysis. See ``checkOptions()``. + % :type opt: structure + % :param funcFWHM: How much smoothing was applied to the functional + % data in the preprocessing. + % :type funcFWHM: scalar + % :param conFWHM: How much smoothing will be applied to the contrast + % images. + % :type conFWHM: scalar + % :param contrastName: + % :type contrastName: string + % + % :returns: :rfxDir: (string) Fullpath of the group level directory % rfxDir = fullfile( ... opt.derivativesDir, ... 'group', ... ['rfx_task-', opt.taskName], ... - ['rfx_funcFWHM-', num2str(funcFWHM), '_conFWHM-', num2str(conFWHM)], ... - contrastName); + ['rfx_funcFWHM-', num2str(funcFWHM), '_conFWHM-', num2str(conFWHM)]); if ~exist(rfxDir, 'dir') mkdir(rfxDir); diff --git a/src/reports/copyFigures.m b/src/reports/copyFigures.m new file mode 100644 index 00000000..607aecd1 --- /dev/null +++ b/src/reports/copyFigures.m @@ -0,0 +1,41 @@ +% (C) Copyright 2019 CPP BIDS SPM-pipeline developers + +function copyFigures(BIDS, opt, subID) + % + % Copy the figures from spatial preprocessing into a separate folder. + % + % USAGE:: + % + % copyFigures(BIDS, opt, subID) + % + % :param BIDS: BIDS layout returned by ``getData``. + % :type BIDS: structure + % :param opt: Options chosen for the analysis. See ``checkOptions()``. + % :type opt: structure + % :param subID: Subject label (for example `'01'`). + % :type subID: string + % + % + + imgNb = copyGraphWindownOutput(opt, subID, 'realign'); + + % loop through the figures outputed for unwarp: one per run + if opt.realign.useUnwarp + + runs = bids.query(BIDS, 'runs', ... + 'sub', subID, ... + 'task', opt.taskName, ... + 'type', 'bold'); + + nbRuns = size(runs, 2); + if nbRuns == 0 + nbRuns = 1; + end + + imgNb = copyGraphWindownOutput(opt, subID, 'unwarp', imgNb:(imgNb + nbRuns - 1)); + + end + + imgNb = copyGraphWindownOutput(opt, subID, 'func2anatCoreg', imgNb); %#ok + +end diff --git a/src/reports/copyGraphWindownOutput.m b/src/reports/copyGraphWindownOutput.m index 88e92598..a7821cd0 100644 --- a/src/reports/copyGraphWindownOutput.m +++ b/src/reports/copyGraphWindownOutput.m @@ -11,18 +11,18 @@ % % imgNb = copyGraphWindownOutput(opt, subID, [action = '',] [imgNb = 1]) % - % :param opt: options + % :param opt: Options chosen for the analysis. See ``checkOptions()``. % :type opt: structure - % :param subID: + % :param subID: Subject label (for example `'01'`). % :type subID: string - % :param action: + % :param action: Name to be given to the figure. % :type action: string - % :param imgNb: image number to look for. SPM increments them automatically. - % :type imgNb: integer + % :param imgNb: Image numbers to look for. SPM increments them automatically when + % adding a new figure a folder. + % :type imgNb: vector of integer % - % :returns: - :imgNb: (integer) number of the next image to get. + % :returns: :imgNb: (integer) number of the next image to get. % - % assumes that no file was generated if SPM is running in command line mode if nargin < 4 || isempty(imgNb) imgNb = 1; @@ -32,30 +32,57 @@ action = ''; end - if ~spm('CmdLine') && ~isOctave + figureDir = fullfile(opt.derivativesDir, strcat('sub-', subID), 'figures'); + if ~exist(figureDir, 'dir') + mkdir(figureDir); + end - figureDir = fullfile(opt.derivativesDir, ['sub-' subID], 'figures'); - if ~exist(figureDir, 'dir') - mkdir(figureDir); - end + for iFile = imgNb + + % Using validationInputFile might be too agressive as it throws an error if + % it can't find a file. Let's use a work around and stick to warnings for now + + % file = validationInputFile(pwd, sprintf('spm_.*%i.png', iFile)); + file = spm_select('FPList', pwd, sprintf('^spm_.*%i.png$', iFile)); - file = spm_select('FPList', pwd, sprintf('^spm_.*%i.png$', imgNb)); + if isempty(file) - if ~isempty(file) + warning( ... + 'copyGraphWindownOutput:noFile', ... + 'No figure file to copy'); - targetFile = [datestr(now, 'yyyymmddHHMM') ... - '_sub-', subID, ... - '_task-', opt.taskName, ... - '_' action '.png']; + elseif size(file, 1) > 1 + + warning( ... + 'copyGraphWindownOutput:tooManyFiles', ... + sprintf('\n %s\n %s\n %s', ... + 'Too many figure files to copy.', ... + 'Not sure what to do.', ... + 'Will skip this step.')); + disp(file); + + else + + targetFile = sprintf( ... + '%s_%i_sub-%s_task-%s_%s.png', ... + datestr(now, 'yyyymmddHHMM'), ... + iFile, ... + subID, ... + opt.taskName, ... + action); movefile( ... file, ... fullfile(figureDir, targetFile)); - imgNb = imgNb + 1; + fprintf(1, '\n%s\nwas moved to\n%s\n', ... + file, ... + fullfile(figureDir, targetFile)); end end + imgNb = imgNb(end) + 1; + end diff --git a/src/reports/reportBIDS.m b/src/reports/reportBIDS.m index 4fe2adce..4c617840 100644 --- a/src/reports/reportBIDS.m +++ b/src/reports/reportBIDS.m @@ -1,10 +1,22 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers function reportBIDS(opt) + % + % Prints out a human readable description of a BIDS data set. + % + % USAGE:: + % + % reportBIDS(opt) + % + % :param opt: Options chosen for the analysis. See ``checkOptions()``. + % :type opt: structure + % + % + % .. TODO: + % + % - save output in the derivatires folder + % derivativeDir = fullfile(rawDir, '..', 'derivatives', 'cpp_spm'); bids.report(opt.dataDir); - % TODO save output in the derivatires folder - % derivativeDir = fullfile(rawDir, '..', 'derivatives', 'SPM12_CPPL'); - end diff --git a/src/results/returnName.m b/src/results/returnName.m new file mode 100644 index 00000000..8fcc2ffb --- /dev/null +++ b/src/results/returnName.m @@ -0,0 +1,13 @@ +% (C) Copyright 2019 CPP BIDS SPM-pipeline developers + +function name = returnName(result) + + name = sprintf('%s_p-%0.3f_k-%i_MC-%s', ... + result.Contrasts.Name, ... + result.Contrasts.p, ... + result.Contrasts.k, ... + result.Contrasts.MC); + + name = strrep(name, '.', ''); + +end diff --git a/src/results/setMontage.m b/src/results/setMontage.m new file mode 100644 index 00000000..23e949d1 --- /dev/null +++ b/src/results/setMontage.m @@ -0,0 +1,14 @@ +% (C) Copyright 2019 CPP BIDS SPM-pipeline developers + +function montage = setMontage(result) + + % TO DO + % - adapt so that the background image is in MNI only if opt.space is MNI + % - add possibility to easily select mean functional or the anatomical: + % - at the group level or subject level + + montage.background = {result.Output.montage.background}; + montage.orientation = result.Output.montage.orientation; + montage.slices = result.Output.montage.slices; + +end diff --git a/src/setDerivativesDir.m b/src/setDerivativesDir.m index 15d51c37..04c9c37a 100644 --- a/src/setDerivativesDir.m +++ b/src/setDerivativesDir.m @@ -1,19 +1,53 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers function opt = setDerivativesDir(opt) - % derivativeDir sets the derivatives folder + % + % Sets the derivatives folder and the directory where to save the SPM jobs. + % The actual creation of the directory is done by + % ``createDerivativeDir(opt)``. + % + % USAGE:: % % opt = setDerivativesDir(opt) % - % Parameters: - % opt: option structure + % :param opt: Options chosen for the analysis. See ``checkOptions()``. + % :type opt: structure + % + % :returns: + % - :opt: structure or json filename containing the options. See + % ``checkOptions()`` and ``loadAndCheckOptions()``. + % + % Examples: + % % opt.dataDir = '/home/remi/data'; + % % opt.taskName = 'testTask'; + % % opt = setDerivativesDir(opt); + % % + % % disp(opt.derivativesDir) + % %|| '/home/remi/data/../derivatives/cpp_spm' + % % + % % disp(opt.opt.jobsDir) + % %|| '/home/remi/data/../derivatives/cpp_spm/JOBS/testTask + % + % % opt.dataDir = '/home/remi/data'; + % % opt.dataDir = '/home/remi/otherFolder'; + % % opt.taskName = 'testTask'; + % % opt = setDerivativesDir(opt); + % % + % % disp(opt.derivativesDir) + % %|| '/home/remi/otherFolder/derivatives/cpp_spm' + % + % % opt.dataDir = '/home/remi/data'; + % % opt.dataDir = '/home/remi/derivatives/preprocessing'; + % % opt.taskName = 'testTask'; + % % opt = setDerivativesDir(opt); + % % + % % disp(opt.derivativesDir) + % %|| '/home/remi/otherFolder/derivatives/preprocessing' + % % - % Returns: - % opt: with the additional field derivativesDir if ~isfield(opt, 'derivativesDir') || isempty(opt.derivativesDir) - opt.derivativesDir = fullfile(opt.dataDir, '..', 'derivatives', 'SPM12_CPPL'); - + opt.derivativesDir = fullfile(opt.dataDir, '..', 'derivatives', 'cpp_spm'); end try @@ -23,9 +57,13 @@ folders = strsplit(opt.derivativesDir, filesep); end - if ~strcmp(folders{end - 1}, 'derivatives') && ~strcmp(folders{end}, 'SPM12_CPPL') + if strcmp(folders{end}, 'derivatives') + folders{end + 1} = 'cpp_spm'; + end + + if ~strcmp(folders{end - 1}, 'derivatives') && ~strcmp(folders{end}, 'cpp_spm') folders{end + 1} = 'derivatives'; - folders{end + 1} = 'SPM12_CPPL'; + folders{end + 1} = 'cpp_spm'; end try @@ -36,6 +74,8 @@ opt.derivativesDir = strjoin(folders, filesep); end + opt.derivativesDir = spm_file(opt.derivativesDir, 'cpath'); + % Suffix output directory for the saved jobs opt.jobsDir = fullfile(opt.derivativesDir, 'JOBS', opt.taskName); diff --git a/src/subject_level/concatBetaImgTmaps.m b/src/subject_level/concatBetaImgTmaps.m deleted file mode 100644 index 982b82ba..00000000 --- a/src/subject_level/concatBetaImgTmaps.m +++ /dev/null @@ -1,122 +0,0 @@ -% (C) Copyright 2019 CPP BIDS SPM-pipeline developers - -function concatBetaImgTmaps(funcFWHM, opt, deleteIndBeta, deleteIndTmaps) - % Make 4D images of beta and t-maps for the MVPA. :: - % - % concatBetaImgTmaps(funcFWHM, opt, [deleteIndBeta = true,] [deleteIndTmaps = true]) - % - % :param funcFWHM: (scalar) smoothing (FWHM) applied to the the normalized EPI - % :param opt: (boolean) options structure - % :param deleteIndBeta: (boolean) - % :param deleteIndTmaps: (boolean) decide to delete t-maps, beta-maps - % - % If no ``opt`` input is given this function will attempt to load a json file. - % - - % delete individual Beta and tmaps - if nargin < 3 - deleteIndBeta = 1; - deleteIndTmaps = 1; - end - - % load the subjects/Groups information and the task name - [group, opt, ~] = getData(opt); - - %% Loop through the groups, subjects - for iGroup = 1:length(group) - - for iSub = 1:group(iGroup).numSub - - subID = group(iGroup).subNumber{iSub}; - - fprintf(1, 'PREPARING: 4D maps: %s \n', subID); - - ffxDir = getFFXdir(subID, funcFWHM, opt, isMVPA); - - load(fullfile(ffxDir, 'SPM.mat')); - - contrasts = specifyContrasts(ffxDir, opt.taskName, opt, isMVPA); - - beta_maps = cell(length(contrasts), 1); - t_maps = cell(length(contrasts), 1); - - % path to beta and t-map files. - for iContrast = 1:length(beta_maps) - % Note that the betas are created from the idx (Beta_idx(iBeta)) - fileName = sprintf('beta_%04d.nii', find(contrasts(iContrast).C)); - fileName = validationInputFile(ffxDir, fileName); - beta_maps{iContrast, 1} = [fileName, ',1']; - - % while the contrastes (t-maps) are not from the index. They were created - fileName = sprintf('spmT_%04d.nii', iContrast); - fileName = validationInputFile(ffxDir, fileName); - t_maps{iContrast, 1} = [fileName, ',1']; - end - - % clear previous matlabbatch and files - matlabbatch = []; - - % 4D beta maps - matlabbatch{1}.spm.util.cat.vols = beta_maps; - matlabbatch{1}.spm.util.cat.name = ['4D_beta_', num2str(funcFWHM), '.nii']; - matlabbatch{1}.spm.util.cat.dtype = 4; - - % 4D t-maps - matlabbatch{2}.spm.util.cat.vols = t_maps; - matlabbatch{2}.spm.util.cat.name = ['4D_t_maps_', num2str(funcFWHM), '.nii']; - matlabbatch{2}.spm.util.cat.dtype = 4; - - saveMatlabBatch(matlabbatch, 'concatBetaImgTmaps', opt, subID); - - spm_jobman('run', matlabbatch); - - removeBetaImgTmaps(beta_maps, t_maps, deleteIndBeta, deleteIndTmaps); - - end - end - -end - -function removeBetaImgTmaps(beta_maps, t_maps, deleteIndBeta, deleteIndTmaps) - - % delete maps - if deleteIndBeta - - % delete all individual beta maps - fprintf('Deleting individual beta-maps ... '); - for iBeta = 1:length(beta_maps) - delete(beta_maps{iBeta}(1:end - 2)); - end - fprintf('Done. \n\n\n '); - - end - - if deleteIndTmaps - - % delete all individual con maps - fprintf('Deleting individual con maps ... '); - for iCon = 1:length(t_maps) - delete(fullfile(ffxDir, ['con_', sprintf('%04d', iCon), '.nii'])); - end - fprintf('Done. \n\n\n '); - - % delete all individual t-maps - fprintf('Deleting individual t-maps ... '); - for iTmap = 1:length(t_maps) - delete(t_maps{iTmap}(1:end - 2)); - end - fprintf('Done. \n\n\n '); - end - - % delete mat files - - % This is refactorable - % ex: delete(fullfile(ffxDir, ['4D_*', num2str(funcFWHM), '.mat'])); - - if exist(fullfile(ffxDir, ['4D_beta_', num2str(funcFWHM), '.mat']), 'file') - delete(fullfile(ffxDir, ['4D_beta_', num2str(funcFWHM), '.mat'])); - end - if exist(fullfile(ffxDir, ['4D_t_maps_', num2str(funcFWHM), '.mat']), 'file') - delete(fullfile(ffxDir, ['4D_t_maps_', num2str(funcFWHM), '.mat'])); - end -end diff --git a/src/subject_level/convertOnsetTsvToMat.m b/src/subject_level/convertOnsetTsvToMat.m index 1a812360..c81cc80f 100644 --- a/src/subject_level/convertOnsetTsvToMat.m +++ b/src/subject_level/convertOnsetTsvToMat.m @@ -1,10 +1,24 @@ % (C) Copyright 2019 CPP BIDS SPM-pipeline developers function fullpathOnsetFileName = convertOnsetTsvToMat(opt, tsvFile) - %% Converts a tsv file to an onset file suitable for SPM ffx analysis - % The scripts extracts the conditions' names, onsets, and durations, and - % converts them to TRs (time unit) and saves the onset file to be used for - % SPM + % + % Converts an events.tsv file to an onset file suitable for SPM subject level + % analysis. + % The scripts extracts the trial type, onsets, and durations, and + % converts them and stores them in a mat file. + % + % USAGE:: + % + % fullpathOnsetFileName = convertOnsetTsvToMat(opt, tsvFile) + % + % :param opt: + % :type opt: structure + % :param tsvFile: + % :type tsvFile: string + % + % :returns: - :fullpathOnsetFileName: (string) name of the output `.mat` file. + % + % [pth, file, ext] = spm_fileparts(tsvFile); tsvFile = validationInputFile(pth, [file, ext]); @@ -23,10 +37,11 @@ end - conds = t.trial_type; % assign all the tsv information to a variable called conds. + % assign all the tsv information to a variable called conds. + conds = t.trial_type; - % identify where the conditions to include that are specificed in the 'un' step of the - % model file + % identify where the conditions to include that are specificed + % in the run step of the model file model = spm_jsonread(opt.model.file); for runIdx = 1:numel(model.Steps) @@ -41,6 +56,11 @@ isTrialType = strfind(step.Model.X, 'trial_type.'); + % create empty cell to be filled in according to the conditions present in each run + names = {}; + onsets = {}; + durations = {}; + % for each condition for iCond = 1:numel(isTrialType) @@ -54,10 +74,14 @@ % each line in the tsv files idx = find(strcmp(conditionName, conds)); - % Get the onset and duration of each condition - names{1, iCond} = conditionName; - onsets{1, iCond} = t.onset(idx)'; %#ok<*AGROW,*NASGU> - durations{1, iCond} = t.duration(idx)'; + if ~isempty(idx) + % Get the onset and duration of each condition + names{1, end + 1} = conditionName; + onsets{1, end + 1} = t.onset(idx)'; %#ok<*AGROW,*NASGU> + durations{1, end + 1} = t.duration(idx)'; + else + warning('No trial found for trial type %s in \n%s', conditionName, tsvFile); + end end end diff --git a/src/subject_level/createAndReturnOnsetFile.m b/src/subject_level/createAndReturnOnsetFile.m index 9e3f1599..73e50eed 100644 --- a/src/subject_level/createAndReturnOnsetFile.m +++ b/src/subject_level/createAndReturnOnsetFile.m @@ -1,11 +1,28 @@ % (C) Copyright 2019 CPP BIDS SPM-pipeline developers function onsetFileName = createAndReturnOnsetFile(opt, subID, tsvFile, funcFWHM) - % onsetFileName = createAndReturnOnsetFile(opt, boldFileName, prefix, isMVPA) % - % gets the tsv onset file based on the bold file name (removes any prefix) + % For a given events.tsv file it creates a .mat file that can directly be used + % for the GLM specification of a subject level model. The file is moved + % directly into the folder of the GLM. + % + % USAGE:: + % + % onsetFileName = createAndReturnOnsetFile(opt, subID, tsvFile, funcFWHM) + % + % :param opt: + % :type opt: structure + % :param subID: + % :type subID: string + % :param tsvFile: fullpath name of the tsv file. + % :type tsvFile: string + % :param funcFWHM: size of the FWHM gaussian kernel used to the subject level + % GLM. Necessary for the GLM directory. + % :type funcFWHM: float + % + % :returns: - :onsetFileName: (string) fullpath name of the file created. + % Removes any prefix. % - % convert the tsv files to a mat file to be used by SPM onsetFileName = convertOnsetTsvToMat(opt, tsvFile); diff --git a/src/subject_level/deleteResidualImages.m b/src/subject_level/deleteResidualImages.m index aa51b58a..a6b10bf4 100644 --- a/src/subject_level/deleteResidualImages.m +++ b/src/subject_level/deleteResidualImages.m @@ -1,5 +1,16 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers function deleteResidualImages(ffxDir) + % + % USAGE:: + % + % deleteResidualImages(ffxDir) + % + % :param ffxDir: + % :type ffxDir: string + % + delete(fullfile(ffxDir, 'Res_*.nii')); + delete(fullfile(ffxDir, 'res4d.nii*')); + end diff --git a/src/subject_level/getBoldFilenameForFFX.m b/src/subject_level/getBoldFilenameForFFX.m index eafd7d72..117ed609 100644 --- a/src/subject_level/getBoldFilenameForFFX.m +++ b/src/subject_level/getBoldFilenameForFFX.m @@ -1,10 +1,31 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers function [boldFileName, prefix] = getBoldFilenameForFFX(varargin) - % [boldFileName, prefix] = getBoldFilenameForFFX(BIDS, opt, subID, funcFWHM, iSes, iRun) % - % get the filename for this bold run for this task for the FFX setup + % Gets the filename for this bold run for this task for the FFX setup % and check that the file with the right prefix exist + % + % USAGE:: + % + % [boldFileName, prefix] = getBoldFilenameForFFX(BIDS, opt, subID, funcFWHM, iSes, iRun) + % + % :param BIDS: + % :type BIDS: structure + % :param opt: + % :type opt: structure + % :param subID: + % :type subID: string + % :param funcFWHM: + % :type funcFWHM: scalar + % :param iSes: + % :type iSes: integer + % :param iRun: + % :type iRun: integer + % + % :returns: - :boldFileName: (string) + % - :prefix: (srting) + % + % [BIDS, opt, subID, funcFWHM, iSes, iRun] = deal(varargin{:}); @@ -13,9 +34,6 @@ runs = getInfo(BIDS, subID, opt, 'Runs', sessions{iSes}); prefix = getPrefix('FFX', opt, funcFWHM); - if strcmp(opt.space, 'individual') - prefix = getPrefix('FFX_space-individual', opt, funcFWHM); - end [fileName, subFuncDataDir] = getBoldFilename( ... BIDS, ... diff --git a/src/subject_level/getFFXdir.m b/src/subject_level/getFFXdir.m index 01d96403..e2d4d4cc 100644 --- a/src/subject_level/getFFXdir.m +++ b/src/subject_level/getFFXdir.m @@ -1,10 +1,21 @@ % (C) Copyright 2019 CPP BIDS SPM-pipeline developers function ffxDir = getFFXdir(subID, funcFWFM, opt) - % ffxDir = getFFXdir(subID, funcFWFM, opt) % - % sets the name the FFX directory and creates it if it does not exist + % Sets the name the FFX directory and creates it if it does not exist % + % USAGE:: + % + % ffxDir = getFFXdir(subID, funcFWFM, opt) + % + % :param subID: + % :type subID: string + % :param funcFWFM: + % :type funcFWFM: scalar + % :param opt: + % :param opt: structure + % + % :returns: - :ffxDir: (string) % ffxDir = fullfile(opt.derivativesDir, ... @@ -16,4 +27,5 @@ if ~exist(ffxDir, 'dir') mkdir(ffxDir); end + end diff --git a/src/subject_level/specifyContrasts.m b/src/subject_level/specifyContrasts.m index b886f517..811c6e27 100644 --- a/src/subject_level/specifyContrasts.m +++ b/src/subject_level/specifyContrasts.m @@ -1,22 +1,35 @@ % (C) Copyright 2019 CPP BIDS SPM-pipeline developers function contrasts = specifyContrasts(ffxDir, taskName, opt) + % % Specifies the first level contrasts % + % USAGE:: + % + % contrasts = specifyContrasts(ffxDir, taskName, opt) + % + % :param ffxDir: + % :type ffxDir: + % :param taskName: + % :type taskName: + % :param opt: + % + % :returns: - :contrasts: (type) (dimension) + % % To know the names of the columns of the design matrix, type : - % strvcat(SPM.xX.name) + % ``strvcat(SPM.xX.name)`` % % EXAMPLE - % Sn(1) ins 1 - % Sn(1) ins 2 - % Sn(1) T1 - % Sn(1) T2 - % Sn(1) R1 - % Sn(1) R2 - % Sn(1) R3 - % Sn(1) R4 - % Sn(1) R5 - % Sn(1) R6 + % Sn(1) ins 1 + % Sn(1) ins 2 + % Sn(1) T1 + % Sn(1) T2 + % Sn(1) R1 + % Sn(1) R2 + % Sn(1) R3 + % Sn(1) R4 + % Sn(1) R5 + % Sn(1) R6 load(fullfile(ffxDir, 'SPM.mat')); diff --git a/src/templates/setBatchTemplate.m b/src/templates/setBatchTemplate.m new file mode 100644 index 00000000..7a7536b6 --- /dev/null +++ b/src/templates/setBatchTemplate.m @@ -0,0 +1,23 @@ +% (C) Copyright 2020 CPP BIDS SPM-pipeline developers + +function matlabbatch = setBatchTemplate(matlabbatch, BIDS, opt, subID, info, varargin) + % + % template to creae new setBatch functions + % + % USAGE:: + % + % matlabbatch = setBatchTemplate(matlabbatch, BIDS, opt, subID, info, varargin) + % + % :param matlabbatch: + % :type matlabbatch: + % + % :returns: - :matlabbatch: (structure) The matlabbatch ready to run the spm job + + printBatchName('name for this batch'); + + matlabbatch{end + 1}.spm.something = BIDS; + matlabbatch{end}.spm.else = opt; + matlabbatch{end}.spm.other = subID; + matlabbatch{end}.spm.thing = info; + +end diff --git a/src/templates/templateFunctionNumpy.m b/src/templates/templateFunctionNumpy.m deleted file mode 100644 index fed5063d..00000000 --- a/src/templates/templateFunctionNumpy.m +++ /dev/null @@ -1,21 +0,0 @@ -% (C) Copyright 2020 CPP BIDS SPM-pipeline developers - -function [argout] = templateFunctionNumpy(argin1, argin2, argin3) - % - % Short description of what the function does goes here. - % - % y = templateFunction(argin1, argin2, argin3) - % - % Parameters: - % argin1: The first input value - % - % argin2: The second input value - % - % argin3: The third input value - % - % Returns: - % The input value multiplied by two - - % The code goes below - -end diff --git a/src/unzipImgAndReturnsFullpathName.m b/src/unzipImgAndReturnsFullpathName.m index 865687e8..189b7b01 100644 --- a/src/unzipImgAndReturnsFullpathName.m +++ b/src/unzipImgAndReturnsFullpathName.m @@ -1,8 +1,18 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers function unzippedFullpathImgName = unzipImgAndReturnsFullpathName(fullpathImgName) - % unzippedFullpathImgName = unzipImgAndReturnsFullpathName(fullpathImgName) % + % Short description of what the function does goes here. + % + % USAGE:: + % + % unzippedFullpathImgName = unzipImgAndReturnsFullpathName(fullpathImgName) + % + % :param argin1: (dimension) obligatory argument. Lorem ipsum dolor sit amet, + % consectetur adipiscing elit. Ut congue nec est ac lacinia. + % :type argin1: type + % + % :returns: - :argout1: (type) (dimension) % [directory, filename, ext] = spm_fileparts(fullpathImgName); diff --git a/src/utils/checkDependencies.m b/src/utils/checkDependencies.m index 2cb74613..ac9c46d4 100644 --- a/src/utils/checkDependencies.m +++ b/src/utils/checkDependencies.m @@ -1,7 +1,20 @@ % (C) Copyright 2019 CPP BIDS SPM-pipeline developers function checkDependencies() - % Checks that that the right dependencies are installed. ALso loads the spm defaults. + % + % Checks that that the right dependencies are installed: + % - SPM + % - Nifti tools + % Also loads the spm defaults. + % + % USAGE:: + % + % checkDependencies() + % + % .. TODO: + % + % - need to check other dependencies (bids-matlab, spmup) + % printCredits(); diff --git a/src/utils/cleanCrash.m b/src/utils/cleanCrash.m new file mode 100644 index 00000000..c8cce61d --- /dev/null +++ b/src/utils/cleanCrash.m @@ -0,0 +1,26 @@ +% (C) Copyright 2020 CPP BIDS SPM-pipeline developers + +function cleanCrash() + % + % Removes any files left over from a previous unfinished run of the pipeline, + % like any *.png imgages + % + % USAGE:: + % + % cleanCrash() + % + % + + files = {'spm.*.png'}; + + for i = 1:numel(files) + + if ~isempty(spm_select('List', pwd, ['^' files{i} '$'])) + + delete(fullfile(pwd, strrep(files{i}, '.*', '*'))); + + end + + end + +end diff --git a/src/utils/convert3Dto4D.m b/src/utils/convert3Dto4D.m new file mode 100644 index 00000000..bafea7e0 --- /dev/null +++ b/src/utils/convert3Dto4D.m @@ -0,0 +1,154 @@ +% (C) Copyright 2020 CPP BIDS SPM-pipeline developers + +function convert3Dto4D(optSource) + % + % It converts single volumes of a sequence in a 4D file, remove the dummies (optional), zip the + % 4D file (optional) and delete the converted files. Recursevly loops through a folder in which a + % not-yet-BIDS dataset live and the nii files are sorted in each sequence folder. + % + % USAGE:: + % + % convert3Dto4D(optSource) + % + % :param optSource: Obligatory argument. The structure that contains the options set by the user + % to run the batch workflow for source processing + % + % .. todo: + % + % - expand to run through multiple subjs ans groups + % (https://stackoverflow.com/questions/8748976/ + % list-the-subfolders-in-a-folder-matlab-only-subfolders-not-files) + % - generalize how to retrieve RT from sidecar json file + % - saveMatlabBatch(matlabbatch, ... + % ['3Dto4D_dataType-' num2str(dataType) '_RT-' num2str(RT)], opt, subID); + % - Cover the MoCo use case: if the sequence is MoCo (motion corrected when the "scanner" + % reconstructs the images - an option on can tick on Siemens scanner and that output an + % additional MoCo file with the regular sequence) then each JSON file of each volume contains + % the motion correction information for that volume. So only taking the JSON of the first + % volume means we "lose" the realignment parameters that could be useful later. + + % Get source folder content + sourceDataStruc = dir(optSource.sourceDir); + + isDir = [sourceDataStruc(:).isdir]; + + optSource.sequenceList = {sourceDataStruc(isDir).name}'; + + % Loop through the sequence folders + + tic; + + for iSeq = 1:size(optSource.sequenceList, 1) + + % Skip 'non' folders + if length(optSource.sequenceList{iSeq}) > 2 + + % Check if sequence to ignore or not + if contains(optSource.sequenceList(iSeq), optSource.sequenceToIgnore) + + warning('\nIGNORING SEQUENCE: %s\n', string(optSource.sequenceList(iSeq))); + + else + + fprintf('\n\nCONVERTING SEQUENCE: %s \n', char(optSource.sequenceList(iSeq))); + + % Set whether to remove dummies or not + + nbDummies = 0; + + if contains(optSource.sequenceList(iSeq), optSource.sequenceRmDummies) + + nbDummies = optSource.nbDummies; + + fprintf('\n\nREMOVING %s DUMMIES\n\n', num2str(nbDummies)); + + end + + % Get sequence folder path + sequencePath = fullfile(optSource.sourceDir, optSource.sequenceList{iSeq}); + + % Retrieve volume files info + [volumesList, outputNameImage] = parseFiles('nii', sequencePath, nbDummies); + + % Set output name, it takes the file name of the 1st volume of the 4D file and add subfix + outputNameImage = strrep(outputNameImage, '.nii', '_4D.nii'); + + % Retrieve sidecar json files info + [jsonList, outputNameJson] = parseFiles('json', sequencePath, nbDummies); + + jsonFile = spm_jsonread(jsonList{1}); + + % % % % % % LIEGE SPECIFIC % % % % % % % + RT = jsonFile.acqpar.RepetitionTime / 1000; + % % % % % % % % % % % % % % % % % % % % % + + % Set and run spm batch, input all the volumes minus the dummies if > 0 + matlabbatch = []; + matlabbatch = setBatch3Dto4D(matlabbatch, ... + volumesList(nbDummies + 1:end, :), ... + RT, ... + outputNameImage, ... + optSource.dataType); + + spm_jobman('run', matlabbatch); + + if optSource.zip + + % Zip and delete the and the new 4D file + fprintf(1, 'ZIP AND DELETE THE NEW 4D BRAIN \n\n'); + + gzip([sequencePath filesep outputNameImage]); + + delete([sequencePath filesep outputNameImage]); + + end + + % Save one sidecar json file, it takes the file name of the 1st volume of the 4D file and + % add subfix + if ~isempty(jsonList) + + copyfile(jsonList{1}, [sequencePath filesep strrep(outputNameJson, '.json', '_4D.json')]); + + end + + % Delete all the single volumes .nii and .json files + fprintf(1, 'EXTERMINATE SINGLE VOLUMES FILES \n\n'); + + for iDel = 1:length(volumesList) + + delete(volumesList{iDel}); + delete(jsonList{iDel}); + + end + + end + + end + + end + + toc; + +end + +function [fileList, outputName] = parseFiles(fileExtention, sequencePath, nbDummies) + + fileList = spm_select('list', sequencePath, fileExtention); + + if size(fileList, 1) > 0 + + outputName = fileList(nbDummies + 1, :); + + fileList = strcat(sequencePath, filesep, cellstr(fileList)); + + else + + fileList = {}; + + outputName = []; + + warning('\nI have found 0 files with extension ''.%s'' \n', fileExtention); + + end + +end diff --git a/src/utils/createDataDictionary.m b/src/utils/createDataDictionary.m index 3d8c1a55..973b1c0d 100644 --- a/src/utils/createDataDictionary.m +++ b/src/utils/createDataDictionary.m @@ -1,6 +1,20 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers function createDataDictionary(subFuncDataDir, fileName, nbColums) + % + % Short description of what the function does goes here. + % + % USAGE:: + % + % [createDataDictionary(subFuncDataDir, fileName, nbColums) + % + % :param subFuncDataDir: Full path of the directory where the functional file is. + % :type subFuncDataDir: type + % :param fileName: Name of the boldfile. + % :type fileName: string + % :param nbColums: Number of extra columns to add as censoring regressors. + % :type nbColums: integer + % namecColumns = { ... 'trans_x', ... diff --git a/src/utils/createDerivativeDir.m b/src/utils/createDerivativeDir.m new file mode 100644 index 00000000..dd947ac0 --- /dev/null +++ b/src/utils/createDerivativeDir.m @@ -0,0 +1,22 @@ +% (C) Copyright 2019 CPP BIDS SPM-pipeline developers + +function createDerivativeDir(opt) + % + % Creates the derivative folder if it does not exist. + % + % USAGE:: + % + % opt = createDerivativeDir(opt) + % + % :param opt: Options chosen for the analysis. See ``checkOptions()``. + % :type opt: structure + % + + if ~exist(opt.derivativesDir, 'dir') + mkdir(opt.derivativesDir); + fprintf('derivatives directory created: %s \n', opt.derivativesDir); + else + fprintf('derivatives directory already exists. \n'); + end + +end diff --git a/src/utils/getEnvInfo.m b/src/utils/getEnvInfo.m index a7b16bd4..6d54cf54 100644 --- a/src/utils/getEnvInfo.m +++ b/src/utils/getEnvInfo.m @@ -1,8 +1,19 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers function [OS, GeneratedBy] = getEnvInfo() + % + % Gets information about the environement and operating system to help generate + % data descriptors for the derivatives. + % + % USAGE:: + % + % [OS, GeneratedBy] = getEnvInfo() + % + % :returns: :OS: (structure) (dimension) + % :GeneratedBy: (structure) (dimension) + % - GeneratedBy(1).name = 'CPP_BIDS_SPM_pipeline'; + GeneratedBy(1).name = 'cpp_spm'; GeneratedBy(1).Version = getVersion(); GeneratedBy(1).Description = ''; GeneratedBy(1).CodeURL = ''; diff --git a/src/utils/getVersion.m b/src/utils/getVersion.m index 66536823..a0ef2b7a 100644 --- a/src/utils/getVersion.m +++ b/src/utils/getVersion.m @@ -1,10 +1,21 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers function versionNumber = getVersion() + % + % Reads the version number of the pipeline from the txt file in the root of the + % repository. + % + % USAGE:: + % + % versionNumber = getVersion() + % + % :returns: :versionNumber: (string) Use semantic versioning format (like v0.1.0) + % + try versionNumber = fileread(fullfile(fileparts(mfilename('fullpath')), ... '..', '..', 'version.txt')); catch - versionNumber = 'v0.0.3'; + versionNumber = 'v0.1.0'; end end diff --git a/src/utils/isOctave.m b/src/utils/isOctave.m index 7f3cea40..5fe03b9f 100644 --- a/src/utils/isOctave.m +++ b/src/utils/isOctave.m @@ -1,8 +1,17 @@ % (C) Copyright 2020 Agah Karakuzu % (C) Copyright 2020 CPP BIDS SPM-pipeline developers -function retval = isOctave - % Return: true if the environment is Octave. +function retval = isOctave() + % + % Returns true if the environment is Octave. + % + % USAGE:: + % + % retval = isOctave() + % + % :returns: :retval: (boolean) + % + persistent cacheval % speeds up repeated calls if isempty (cacheval) diff --git a/src/utils/loadAndCheckOptions.m b/src/utils/loadAndCheckOptions.m index df30316d..1c10e0e0 100644 --- a/src/utils/loadAndCheckOptions.m +++ b/src/utils/loadAndCheckOptions.m @@ -1,37 +1,65 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers -function opt = loadAndCheckOptions(opt) - % opt = loadAndCheckOptions(opt) +function opt = loadAndCheckOptions(optionJsonFile) % - % if not argument is provived checks in the current directory for + % Loads the json file provided describing the options of an analysis. It then checks + % its content and fills any missing fields with the defaults. + % + % If no argument is provived, it checks in the current directory for any % ``opt_task-*.json`` files and loads the most recent one by name - % (using the date- key). + % (using the ``date-`` key). + % + % USAGE:: + % + % opt = loadAndCheckOptions(optionJsonFile) + % + % :param optionJsonFile: Fullpath to the json file describing the options of an + % analysis. It can also be an ``opt`` structure + % containing the options. + % :type optionJsonFile: string + % + % :returns: :opt: (structure) Options chosen for the analysis. See ``checkOptions()``. % - % then checks the content of the opt structure and adds missing information + % .. TODO + % + % - add test for when the input is a structure. - if nargin < 1 || isempty(opt) - opt = spm_select('FPList', pwd, '^options_task-.*.json$'); + if nargin < 1 || isempty(optionJsonFile) + optionJsonFile = spm_select('FPList', pwd, '^options_task-.*.json$'); end - if isstruct(opt) + if isstruct(optionJsonFile) + + opt = optionJsonFile; opt = checkOptions(opt); fprintf(1, '\nOptions are locked & loaded.\n\n'); + return end % finds most recent option file - if size(opt, 1) > 1 - containsDate = cellfun(@any, strfind(cellstr(opt), '_date-')); - opt = opt(containsDate, :); - opt = sortrows(opt); - opt = opt(end, :); + if size(optionJsonFile, 1) > 1 + containsDate = cellfun(@any, strfind(cellstr(optionJsonFile), '_date-')); + if any(containsDate) + optionJsonFile = optionJsonFile(containsDate, :); + optionJsonFile = sortrows(optionJsonFile); + optionJsonFile = optionJsonFile(end, :); + end + end + + if ischar(optionJsonFile) && size(optionJsonFile, 1) == 1 + if exist(optionJsonFile, 'file') + fprintf(1, '\nReading option from: %s.\n', optionJsonFile); + opt = spm_jsonread(optionJsonFile); + else + error('the requested file does not exist: %s', optionJsonFile); + end end - if ischar(opt) && exist(opt, 'file') - fprintf(1, '\nReading option from: %s.\n', opt); - opt = spm_jsonread(opt); - else - error('the requested file does not exist: %s', opt); + % temporary hack to fix the way spm_jsonread reads some empty fields + % REPORT IT TO SPM + if isfield(opt, 'subjects') && ~iscell(opt.subjects) && isnan(opt.subjects) + opt.subjects = {[]}; end opt = checkOptions(opt); diff --git a/src/utils/manageWorkersPool.m b/src/utils/manageWorkersPool.m new file mode 100644 index 00000000..b5a8c08b --- /dev/null +++ b/src/utils/manageWorkersPool.m @@ -0,0 +1,81 @@ +% (C) Copyright 2020 CPP BIDS SPM-pipeline developers + +function manageWorkersPool(action, opt) + % + % Check matlab version and opens pool of workers for parallel work. + % + % USAGE:: + % + % manageWorkersPool(action, opt) + % + % :param action: ``'open'`` or ``'close'`` + % :type action: string + % :param opt: + % :type opt: structure + % + % Requires to set some options:: + % + % opt.parallelize.do = true; + % opt.parallelize.nbWorkers = 3; + % opt.parallelize.killOnExit = true; + % + + if ~opt.parallelize.do + opt.parallelize.nbWorkers = 1; + opt.parallelize.killOnExit = true; + end + + if ~isOctave() + + matlabVer = version('-release'); + + nbWorkers = opt.parallelize.nbWorkers; + + switch lower(action) + + case 'open' + + if str2double(matlabVer(1:4)) > 2013 + + pool = gcp('nocreate'); + + if isempty(pool) + parpool(nbWorkers); %#ok<*DPOOL> + end + + else + + if matlabpool('size') == 0 %#ok<*DPOOL> + matlabpool(nbWorkers); + + elseif matlabpool('size') ~= nbWorkers + matlabpool close; + matlabpool(nbWorkers); + + end + + end + + case 'close' + + if opt.parallelize.killOnExit + + if str2double(matlabVer(1:4)) > 2013 + + pool = gcp('nocreate'); + if ~isempty(pool) + delete(gcp); + end + + else + matlabpool close; + + end + + end + + end + + end + +end diff --git a/src/utils/printBatchName.m b/src/utils/printBatchName.m new file mode 100644 index 00000000..32471cfe --- /dev/null +++ b/src/utils/printBatchName.m @@ -0,0 +1,7 @@ +% (C) Copyright 2019 CPP BIDS SPM-pipeline developers + +function printBatchName(batchName) + + fprintf(1, '\n BUILDING JOB: %s\n', lower(batchName)); + +end diff --git a/src/utils/printCredits.m b/src/utils/printCredits.m index 01ad0812..906fc1c5 100644 --- a/src/utils/printCredits.m +++ b/src/utils/printCredits.m @@ -6,7 +6,7 @@ function printCredits() contributors = { ... 'Mohamed Rezk', ... - 'Rémi Gau', ... + 'Remi Gau', ... 'Olivier Collignon', ... 'Ane Gurtubay', ... 'Marco Barilari', ... diff --git a/src/utils/printWorklowName.m b/src/utils/printWorklowName.m new file mode 100644 index 00000000..9b4155fd --- /dev/null +++ b/src/utils/printWorklowName.m @@ -0,0 +1,7 @@ +% (C) Copyright 2019 CPP BIDS SPM-pipeline developers + +function printWorklowName(workflowName) + + fprintf(1, '\n\n\nWORKFLOW: %s\n\n', upper(workflowName)); + +end diff --git a/src/utils/rmTrialTypeStr.m b/src/utils/rmTrialTypeStr.m new file mode 100644 index 00000000..82116dd6 --- /dev/null +++ b/src/utils/rmTrialTypeStr.m @@ -0,0 +1,6 @@ +% (C) Copyright 2019 CPP BIDS SPM-pipeline developers +function conName = rmTrialTypeStr(conName) + + conName = strrep(conName, 'trial_type.', ''); + +end diff --git a/src/utils/saveMatlabBatch.m b/src/utils/saveMatlabBatch.m index 79d1eb68..7d6c7ff0 100644 --- a/src/utils/saveMatlabBatch.m +++ b/src/utils/saveMatlabBatch.m @@ -1,12 +1,21 @@ % (C) Copyright 2019 CPP BIDS SPM-pipeline developers function saveMatlabBatch(matlabbatch, batchType, opt, subID) - % saveMatlabBatch(batch, batchType, opt, subID) % - % Also save some basic environnment info + % Also save some basic environnment info. % - % batch : matlabbatch - % batchType: (string) name to give to the batch file + % USAGE:: + % + % saveMatlabBatch(matlabbatch, batchType, opt, [subID]) + % + % :param matlabbatch: + % :type matlabbatch: structure + % :param batchType: + % :type batchType: string + % :param opt: Options chosen for the analysis. See ``checkOptions()``. + % :type opt: structure + % :param subID: + % :type subID: string % % diff --git a/src/utils/saveOptions.m b/src/utils/saveOptions.m index 78585d90..8f221439 100644 --- a/src/utils/saveOptions.m +++ b/src/utils/saveOptions.m @@ -1,6 +1,16 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers function saveOptions(opt) + % + % Short description of what the function does goes here. + % + % USAGE:: + % + % [argout1, argout2] = templateFunction(argin1, [argin2 == default,] [argin3]) + % + % :param opt: Options chosen for the analysis. See ``checkOptions()``. + % :type opt: structure + % filename = fullfile(pwd, ['options', ... '_task-', opt.taskName, ... @@ -10,6 +20,6 @@ function saveOptions(opt) jsonFormat.indent = ' '; spm_jsonwrite(filename, opt, jsonFormat); - fprintf('Options saved in: \n\n'); + fprintf('Options saved in: %s\n\n', filename); end diff --git a/src/utils/setDefaultFields.m b/src/utils/setDefaultFields.m index d79505ce..3f514ddb 100644 --- a/src/utils/setDefaultFields.m +++ b/src/utils/setDefaultFields.m @@ -1,6 +1,24 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers function structure = setDefaultFields(structure, fieldsToSet) + % + % Short description of what the function does goes here. + % + % USAGE:: + % + % [argout1, argout2] = templateFunction(argin1, [argin2 == default,] [argin3]) + % + % :param argin1: (dimension) obligatory argument. Lorem ipsum dolor sit amet, + % consectetur adipiscing elit. Ut congue nec est ac lacinia. + % :type argin1: type + % :param argin2: optional argument and its default value. And some of the + % options can be shown in litteral like ``this`` or ``that``. + % :type argin2: string + % :param argin3: (dimension) optional argument + % + % :returns: - :argout1: (type) (dimension) + % - :argout2: (type) (dimension) + % % structure = setDefaultFields(structure, fieldsToSet) % % recursively loop through the fields of a structure and sets a value if they don't exist diff --git a/src/utils/setGraphicWindow.m b/src/utils/setGraphicWindow.m index 180c3274..2d75a2c6 100644 --- a/src/utils/setGraphicWindow.m +++ b/src/utils/setGraphicWindow.m @@ -1,6 +1,24 @@ % (C) Copyright 2019 CPP BIDS SPM-pipeline developers function [interactiveWindow, graphWindow, cmdLine] = setGraphicWindow() + % + % Short description of what the function does goes here. + % + % USAGE:: + % + % [argout1, argout2] = templateFunction(argin1, [argin2 == default,] [argin3]) + % + % :param argin1: (dimension) obligatory argument. Lorem ipsum dolor sit amet, + % consectetur adipiscing elit. Ut congue nec est ac lacinia. + % :type argin1: type + % :param argin2: optional argument and its default value. And some of the + % options can be shown in litteral like ``this`` or ``that``. + % :type argin2: string + % :param argin3: (dimension) optional argument + % + % :returns: - :argout1: (type) (dimension) + % - :argout2: (type) (dimension) + % interactiveWindow = []; graphWindow = []; diff --git a/src/utils/validationInputFile.m b/src/utils/validationInputFile.m index 2a362cda..10cf7aec 100644 --- a/src/utils/validationInputFile.m +++ b/src/utils/validationInputFile.m @@ -1,27 +1,65 @@ % (C) Copyright 2019 CPP BIDS SPM-pipeline developers -function files = validationInputFile(dir, fileName, prefix) - % file = validationInputFile(dir, prefix, fileName) +function files = validationInputFile(dir, fileNamePattern, prefix) % - % Checks if files exist. A prefix can be added. The prefix allows for the - % use of regular expression. + % Looks for file name pattern in a given directory and returns all the files + % that match that pattern but throws an error if it cannot find any. % - % TPMs = validationInputFile(anatDataDir, anatImage, 'c[12]'); + % A prefix can be added to the filename. % - % If the filet(s) exist(s), it returns a char array containing list of fullpath. + % This function is mostly used that a file exists so + % that an error is thrown early when building a SPM job rather than at run + % time. + % + % USAGE:: + % + % files = validationInputFile(dir, fileName[, prefix]) + % + % :param dir: Directory where the search will be conducted. + % :type dir: string + % :param fileName: file name pattern. Can be a regular expression except for + % the starting ``^`` and ending ``$``. For example: + % ``'sub-.*_ses-.*_task-.*_bold.nii'``. + % :type fileName: string + % :param prefix: prefix to be added to the filename pattern. This can also be + % a regular expression (ish). For example ,f looking for the files that + % start with ``c1`` or ``c2`` or ``c3``, the prefix can be + % ``c[123]``. + % :type prefix: string + % + % :returns: + % + % :files: (string array) returns the fullpath file list of all the + % files matching the required pattern. + % + % + % See also: ``spm_select``. + % + % + % Example: + % % + % % tissueProbaMaps = validationInputFile(anatDataDir, anatImage, 'c[12]'); + % + % + + % try to guess directory in case a fullpath filename was given + if isempty(dir) + [dir, fileNamePattern, ext] = spm_fileparts(fileNamePattern); + fileNamePattern = [fileNamePattern, ext]; + end if nargin < 3 prefix = ''; end - files = spm_select('FPList', dir, ['^' prefix fileName '$']); + files = spm_select('FPList', dir, ['^' prefix fileNamePattern '$']); if isempty(files) errorStruct.identifier = 'validationInputFile:nonExistentFile'; errorStruct.message = sprintf( ... 'This file does not exist: %s', ... - fullfile(dir, [prefix fileName '[.gz]'])); + fullfile(dir, [prefix fileNamePattern '[.gz]'])); error(errorStruct); end diff --git a/src/utils/writeDatasetDescription.m b/src/utils/writeDatasetDescription.m new file mode 100644 index 00000000..c7641e28 --- /dev/null +++ b/src/utils/writeDatasetDescription.m @@ -0,0 +1,19 @@ +% (C) Copyright 2020 CPP BIDS SPM-pipeline developers + +function writeDatasetDescription(opt) + + oldDatasetDescription = spm_jsonread(fullfile(opt.derivativesDir, 'dataset_description.json')); + + newDatasetDescription = datasetDescriptionDefaults(); + + if isfield(oldDatasetDescription, 'DatasetDOI') + newDatasetDescription.SourceDatasets{1}.DOI = ... + oldDatasetDescription.DatasetDOI; + end + + spm_jsonwrite( ... + fullfile(opt.derivativesDir, 'dataset_description.json'), ... + newDatasetDescription, ... + struct('indent', ' ')); + +end diff --git a/src/workflows/bidsConcatBetaTmaps.m b/src/workflows/bidsConcatBetaTmaps.m new file mode 100644 index 00000000..b2a6ff46 --- /dev/null +++ b/src/workflows/bidsConcatBetaTmaps.m @@ -0,0 +1,107 @@ +% (C) Copyright 2019 CPP BIDS SPM-pipeline developers + +function bidsConcatBetaTmaps(opt, funcFWHM, deleteIndBeta, deleteIndTmaps) + % + % Make 4D images of beta and t-maps for the MVPA. :: + % + % concatBetaImgTmaps(funcFWHM, opt, [deleteIndBeta = true,] [deleteIndTmaps = true]) + % + % :param opt: options structure + % :type opt: structure + % :param funcFWHM: smoothing (FWHM) applied to the the normalized EPI + % :type funcFWHM: (scalar) + % :param deleteIndBeta: decide to delete beta-maps + % :type funcFWHM: (boolean) + % :param deleteIndTmaps: decide to delete t-maps + % :type funcFWHM: (boolean) + % + + % delete individual Beta and tmaps + if nargin < 3 + deleteIndBeta = 1; + deleteIndTmaps = 1; + end + + [~, opt, group] = setUpWorkflow(opt, 'merge beta images and t-maps'); + + % clear previous matlabbatch and files + matlabbatch = []; + RT = 0; + + %% Loop through the groups, subjects + for iGroup = 1:length(group) + + for iSub = 1:group(iGroup).numSub + + subID = group(iGroup).subNumber{iSub}; + + ffxDir = getFFXdir(subID, funcFWHM, opt); + + contrasts = specifyContrasts(ffxDir, opt.taskName, opt); + + beta_maps = cell(length(contrasts), 1); + t_maps = cell(length(contrasts), 1); + + % path to beta and t-map files. + for iContrast = 1:length(beta_maps) + % Note that the betas are created from the idx (Beta_idx(iBeta)) + fileName = sprintf('beta_%04d.nii', find(contrasts(iContrast).C)); + fileName = validationInputFile(ffxDir, fileName); + beta_maps{iContrast, 1} = [fileName, ',1']; + + % while the contrastes (t-maps) are not from the index. They were created + fileName = sprintf('spmT_%04d.nii', iContrast); + fileName = validationInputFile(ffxDir, fileName); + t_maps{iContrast, 1} = [fileName, ',1']; + end + + % beta maps + outputName = ['4D_beta_', num2str(funcFWHM), '.nii']; + + matlabbatch = []; + matlabbatch = setBatch3Dto4D(matlabbatch, beta_maps, RT, outputName); + + % t-maps + outputName = ['4D_t_maps_', num2str(funcFWHM), '.nii']; + + matlabbatch = setBatch3Dto4D(matlabbatch, t_maps, RT, outputName); + + saveAndRunWorkflow(matlabbatch, 'concat_betaImg_tMaps', opt, subID); + + removeBetaImgTmaps(t_maps, deleteIndBeta, deleteIndTmaps, ffxDir); + + end + end + +end + +function removeBetaImgTmaps(t_maps, deleteIndBeta, deleteIndTmaps, ffxDir) + + % delete maps + if deleteIndBeta + + % delete all individual beta maps + fprintf('Deleting individual beta-maps ... '); + delete(fullfile(ffxDir, ['beta_*', '.nii'])); + fprintf('Done. \n\n\n '); + + end + + if deleteIndTmaps + + % delete all individual con maps + fprintf('Deleting individual con maps ... '); + for iCon = 1:length(t_maps) + delete(fullfile(ffxDir, ['con_', sprintf('%04d', iCon), '.nii'])); + end + fprintf('Done. \n\n\n '); + + % delete all individual t-maps + fprintf('Deleting individual t-maps ... '); + for iTmap = 1:length(t_maps) + delete(t_maps{iTmap}(1:end - 2)); + end + fprintf('Done. \n\n\n '); + end + +end diff --git a/src/workflows/bidsCopyRawFolder.m b/src/workflows/bidsCopyRawFolder.m index 06d5ca42..f59a6391 100644 --- a/src/workflows/bidsCopyRawFolder.m +++ b/src/workflows/bidsCopyRawFolder.m @@ -1,12 +1,13 @@ % (C) Copyright 2019 CPP BIDS SPM-pipeline developers -function bidsCopyRawFolder(opt, deleteZippedNii, modalitiesToCopy) +function bidsCopyRawFolder(opt, deleteZippedNii, modalitiesToCopy, unZip) % - % This function will copy the subject's folders from the ``raw`` folder to the + % Copies the folders from the ``raw`` folder to the % ``derivatives`` folder, and will copy the dataset description and task json files % to the derivatives directory. - % Then it will search the derivatives directory for any zipped nii.gz image - % and uncompress it to .nii images. + % + % Then it will search the derivatives directory for any zipped ``*.gz`` image + % and uncompress the files for the task of interest. % % USAGE:: % @@ -14,16 +15,24 @@ function bidsCopyRawFolder(opt, deleteZippedNii, modalitiesToCopy) % [deleteZippedNii = true,] ... % [modalitiesToCopy = {'anat', 'func', 'fmap'}]) % - % :param opt: - % :type opt: type - % :param deleteZippedNii: + % :param opt: structure or json filename containing the options. See + % ``checkOptions()`` and ``loadAndCheckOptions()``. + % :type opt: structure + % :param deleteZippedNii: will delete the original zipped ``.gz`` if set to ``true`` % :type deleteZippedNii: boolean - % :param modalitiesToCopy: + % :param modalitiesToCopy: for example ``{'anat', 'func', 'fmap'}`` % :type modalitiesToCopy: cell + % :param unZip: + % :type unZip: boolean % %% input variables default values + if nargin < 4 || isempty(unZip) + % Will only copy those modalities if they exist + unZip = true(); + end + if nargin < 3 || isempty(modalitiesToCopy) % Will only copy those modalities if they exist modalitiesToCopy = {'anat', 'func', 'fmap'}; @@ -40,13 +49,19 @@ function bidsCopyRawFolder(opt, deleteZippedNii, modalitiesToCopy) end opt = loadAndCheckOptions(opt); + cleanCrash(); + + printWorklowName('copy data'); + + manageWorkersPool('open', opt); + %% All tasks in this experiment % raw directory and derivatives directory opt = setDerivativesDir(opt); - rawDir = opt.dataDir; - derivativesDir = opt.derivativesDir; - createDerivativeDir(derivativesDir); + [rawDir, derivativesDir] = returnRawAndDerivativeDir(opt); + + createDerivativeDir(opt); copyTsvJson(rawDir, derivativesDir); @@ -59,10 +74,16 @@ function bidsCopyRawFolder(opt, deleteZippedNii, modalitiesToCopy) subID = group(iGroup).subNumber{iSub}; - % the folder containing the subjects data - subDir = ['sub-', subID]; + subDir = returnSubjectDir(subID); + + fprintf('copying subject: %s \n', subDir); + + [~, ~, ~] = mkdir(fullfile(derivativesDir, subDir)); - mkdir(fullfile(derivativesDir, subDir)); + % copy scans.tsv files + copyTsvJson( ... + fullfile(rawDir, subDir), ... + fullfile(derivativesDir, subDir)); [sessions, nbSessions] = getInfo(BIDS, subID, opt, 'Sessions'); @@ -72,78 +93,134 @@ function bidsCopyRawFolder(opt, deleteZippedNii, modalitiesToCopy) for iSes = 1:nbSessions - sessionDir = []; - if ~isempty(sessions{iSes}) - sessionDir = ['ses-' sessions{iSes}]; - end + sessionDir = returnSessionDir(sessions{iSes}); + + fprintf(' copying session: %s \n', sessionDir); - mkdir(fullfile(derivativesDir, subDir, sessionDir)); + [~, ~, ~] = mkdir(fullfile(derivativesDir, subDir, sessionDir)); + + % copy scans.tsv files + copyTsvJson( ... + fullfile(rawDir, subDir, sessionDir), ... + fullfile(derivativesDir, subDir, sessionDir)); modalities = bids.query(BIDS, 'modalities', ... 'sub', subID, ... - 'ses', sessions{iSes}, ... - 'task', opt.taskName); + 'ses', sessions{iSes}); modalities = intersect(modalities, modalitiesToCopy); - for iModality = 1:numel(modalities) + copyModalities(BIDS, opt, modalities, subID, sessions{iSes}); - mkdir(fullfile(derivativesDir, subDir, sessionDir, modalities{iModality})); + end + end - srcFolder = fullfile(rawDir, ... - subDir, ... - sessionDir, ... - modalities{iModality}); - targetFolder = fullfile(derivativesDir, ... - subDir, ... - sessionDir); + end - copyModalityDir(srcFolder, targetFolder); + if unZip + unzipFiles(derivativesDir, deleteZippedNii, opt); + end - end + manageWorkersPool('close', opt); - fprintf('folder copied: %s \n', subDir); +end - end - end +function [rawDir, derivativesDir] = returnRawAndDerivativeDir(opt) - end + rawDir = opt.dataDir; + derivativesDir = opt.derivativesDir; + +end - unzipFiles(derivativesDir, deleteZippedNii); +function subDir = returnSubjectDir(subID) + + subDir = ['sub-', subID]; end -function createDerivativeDir(derivativesDir) - % make derivatives folder if it doesnt exist +function sessionDir = returnSessionDir(session) - if ~exist(derivativesDir, 'dir') - mkdir(derivativesDir); - fprintf('derivatives directory created: %s \n', derivativesDir); - else - fprintf('derivatives directory already exists. \n'); + sessionDir = []; + if ~isempty(session) + sessionDir = ['ses-' session]; end end -function copyTsvJson(rawDir, derivativesDir) +function copyTsvJson(srcDir, targetDir) % copy TSV and JSON file from raw folder - copyfile(fullfile(rawDir, '*.json'), derivativesDir); - fprintf(' json files copied to derivatives directory \n'); + ext = {'tsv', 'json'}; + + for i = 1:numel(ext) + + if ~isempty(spm_select('List', srcDir, ['^.*.' ext{i} '$'])) + + copyfile(fullfile(srcDir, ['*.' ext{i}]), targetDir); + fprintf(1, ' %s files copied\n', ext{i}); + + end - try - copyfile(fullfile(rawDir, '*.tsv'), derivativesDir); - fprintf(' tsv files copied to derivatives directory \n'); - catch end end -function copyModalityDir(srcFolder, targetFolder) +function copyModalities(BIDS, opt, modalities, subID, session) + + [rawDir, derivativesDir] = returnRawAndDerivativeDir(opt); + + subDir = returnSubjectDir(subID); + + sessionDir = returnSessionDir(session); + + for iModality = 1:numel(modalities) + + targetFolder = fullfile(derivativesDir, ... + subDir, ... + sessionDir); + + [~, ~, ~] = mkdir(fullfile(targetFolder, modalities{iModality})); + + srcFolder = fullfile(rawDir, ... + subDir, ... + sessionDir, ... + modalities{iModality}); + + % for func we only copy the files of the task of interest + if strcmp(modalities{iModality}, 'func') + + files = bids.query(BIDS, 'data', ... + 'sub', subID, ... + 'ses', session, ... + 'task', opt.taskName); + + for iFile = 1:size(files, 1) + copyToDerivative(files{iFile}, fullfile(targetFolder, 'func')); + p = bids.internal.parse_filename(files{iFile}); + sidecar = strrep(p.filename, p.ext, '.json'); + if exist(fullfile(fileparts(files{iFile}), sidecar), 'file') + copyToDerivative(fullfile(fileparts(files{iFile}), sidecar), ... + fullfile(targetFolder, 'func')); + end + end + + else + copyToDerivative(srcFolder, targetFolder); + + end + + end + +end + +function copyToDerivative(src, targetFolder) + + command = 'cp -R -L -f'; try status = system( ... - sprintf('cp -R -L -f %s %s', ... - srcFolder, ... + sprintf('%s %s %s', ... + command, ... + src, ... targetFolder)); if status > 0 @@ -158,25 +235,37 @@ function copyModalityDir(srcFolder, targetFolder) catch fprintf(1, 'Using octave/matlab to copy files.'); - copyfile(srcFolder, targetFolder); + copyfile(src, targetFolder); end end -function unzipFiles(derivativesDir, deleteZippedNii) +function unzipFiles(derivativesDir, deleteZippedNii, opt) %% search for nifti files in a compressed nii.gz format - zippedNiifiles = spm_select('FPListRec', derivativesDir, '^.*.nii.gz$'); - for iFile = 1:size(zippedNiifiles, 1) + zippedNiifiles = spm_select('FPListRec', derivativesDir, '^.*.gz$'); + + parfor iFile = 1:size(zippedNiifiles, 1) file = deblank(zippedNiifiles(iFile, :)); - n = load_untouch_nii(file); % load the nifti image - save_untouch_nii(n, file(1:end - 4)); % Save the functional data as unzipped nii - fprintf('unzipped: %s \n', file); + fragments = bids.internal.parse_filename(file); + + % for bold, physio and stim files, we only unzip the files of the task of + % interest + if any(strcmp(fragments.type, {'bold', 'stim', 'physio'})) && ... + isfield(fragments, 'task') && strcmp(fragments.task, opt.taskName) + + % load the nifti image and saves the functional data as unzipped nii + n = load_untouch_nii(file); + save_untouch_nii(n, file(1:end - 4)); + fprintf('unzipped: %s \n', file); + + % delete original zipped file + if deleteZippedNii + delete(file); + end - if deleteZippedNii - delete(file); % delete original zipped file end end diff --git a/src/workflows/bidsCreateVDM.m b/src/workflows/bidsCreateVDM.m index d6f98e9a..b06b1e7e 100644 --- a/src/workflows/bidsCreateVDM.m +++ b/src/workflows/bidsCreateVDM.m @@ -1,44 +1,62 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers function bidsCreateVDM(opt) - % bidsCreateVDM(opt) % - % inspired from spmup spmup_BIDS_preprocess (@ commit - % 198c980d6d7520b1a996f0e56269e2ceab72cc83) + % Creates the voxel displacement maps from the fieldmaps of a BIDS + % dataset. + % + % USAGE:: + % + % bidsCreateVDM([opt]) + % + % :param opt: structure or json filename containing the options. See + % ``checkOptions()`` and ``loadAndCheckOptions()``. + % :type opt: structure + % + % .. TODO: + % + % - take care of all types of fieldmaps + % + % Inspired from spmup ``spmup_BIDS_preprocess`` (@ commit 198c980d6d7520b1a99) + % (URL missing) + % if nargin < 1 opt = []; end - opt = loadAndCheckOptions(opt); - % load the subjects/Groups information and the task name - [group, opt, BIDS] = getData(opt); - - fprintf(1, ' FIELDMAP WORKFLOW\n'); + [BIDS, opt, group] = setUpWorkflow(opt, 'create voxel displacement map'); %% Loop through the groups, subjects, and sessions for iGroup = 1:length(group) groupName = group(iGroup).name; - for iSub = 1:group(iGroup).numSub + parfor iSub = 1:group(iGroup).numSub subID = group(iGroup).subNumber{iSub}; % TODO Move to getInfo - types = spm_BIDS(BIDS, 'types', 'sub', subID); + types = bids.query(BIDS, 'types', 'sub', subID); if any(ismember(types, {'phase12', 'phasediff', 'fieldmap', 'epi'})) printProcessingSubject(groupName, iSub, subID); - matlabbatch = setBatchCoregistrationFmap(BIDS, opt, subID); - saveMatlabBatch(matlabbatch, 'coregister_fmap', opt, subID); - spm_jobman('run', matlabbatch); + % Create rough mean of the 1rst run to improve SNR for coregistration + % TODO use the slice timed EPI if STC was used ? + sessions = getInfo(BIDS, subID, opt, 'Sessions'); + runs = getInfo(BIDS, subID, opt, 'Runs', sessions{1}); + [fileName, subFuncDataDir] = getBoldFilename(BIDS, subID, sessions{1}, runs{1}, opt); + spmup_basics(fullfile(subFuncDataDir, fileName), 'mean'); + + matlabbatch = []; + matlabbatch = setBatchCoregistrationFmap(matlabbatch, BIDS, opt, subID); + saveAndRunWorkflow(matlabbatch, 'coregister_fmap', opt, subID); - matlabbatch = setBatchCreateVDMs(BIDS, opt, subID); - saveMatlabBatch(matlabbatch, 'create_vdm', opt, subID); - spm_jobman('run', matlabbatch); + matlabbatch = []; + matlabbatch = setBatchCreateVDMs(matlabbatch, BIDS, opt, subID); + saveAndRunWorkflow(matlabbatch, 'create_vdm', opt, subID); % TODO % delete temporary mean images ?? diff --git a/src/workflows/bidsFFX.m b/src/workflows/bidsFFX.m index c06a36cc..843b0bf0 100644 --- a/src/workflows/bidsFFX.m +++ b/src/workflows/bidsFFX.m @@ -1,30 +1,42 @@ -% (C) Copyright 2019 CPP BIDS SPM-pipeline developers +% (C) Copyright 2020 CPP BIDS SPM-pipeline developers function bidsFFX(action, opt, funcFWHM) - % This scripts builds up the design matrix for each subject. - % It has to be run in 2 separate steps (action). :: % - % bidsFFX(action, funcFWHM, opt) + % - builds the subject level fMRI model and estimates it. % - % :param action: (string) ``specifyAndEstimate`` or ``contrasts``. - % :param opt: (scalar) options (see checkOptions()) - % :param funcFWHM: (scalar) Gaussian kernel size applied to the functional data. + % OR % - % ``specifyAndEstimate`` for fMRI design + estimate and - % ``contrasts`` to estimate contrasts. + % - compute the contrasts at the subject level. % - % For unsmoothed data ``funcFWHM = 0``, for smoothed data ``funcFWHM = ... mm``. - % In this way we can make multiple ffx for different smoothing degrees + % USAGE:: + % + % bidsFFX(action, funcFWHM, [opt]) + % + % :param action: Action to be conducted:``specifyAndEstimate`` or ``contrasts``. + % :type action: string + % :param opt: structure or json filename containing the options. See + % ``checkOptions()`` and ``loadAndCheckOptions()``. + % :type opt: structure + % :param funcFWHM: How much smoothing was applied to the functional + % data in the preprocessing (Gaussian kernel size). + % :type funcFWHM: scalar + % + % - ``specifyAndEstimate`` for fMRI design + estimate and + % - ``contrasts`` to estimate contrasts. + % + % For unsmoothed data ``funcFWHM = 0``, for smoothed data ``funcFWHM = ... mm``. + % In this way we can make multiple ffx for different smoothing degrees. % - % if input has no opt, load the opt.mat file if nargin < 3 opt = []; end - opt = loadAndCheckOptions(opt); - % load the subjects/Groups information and the task name - [group, opt, BIDS] = getData(opt); + [BIDS, opt, group] = setUpWorkflow(opt, 'subject level GLM'); + + if isempty(opt.model.file) + opt = createDefaultModel(BIDS, opt); + end %% Loop through the groups, subjects, and sessions for iGroup = 1:length(group) @@ -37,52 +49,54 @@ function bidsFFX(action, opt, funcFWHM) printProcessingSubject(groupName, iSub, subID); + matlabbatch = []; + switch action case 'specifyAndEstimate' - matlabbatch = setBatchSubjectLevelGLMSpec( ... - BIDS, opt, subID, funcFWHM); + matlabbatch = setBatchSubjectLevelGLMSpec(matlabbatch, BIDS, opt, subID, funcFWHM); + matlabbatch = setBatchPrintFigure(matlabbatch, [ ... + 'sub-', subID, ... + '_task-', opt.taskName, ... + '_design_before_estimation']); + matlabbatch = setBatchEstimateModel(matlabbatch); + matlabbatch = setBatchPrintFigure(matlabbatch, [ ... + 'sub-', subID, ... + '_task-', opt.taskName, ... + '_design_after_estimation']); - matlabbatch = setFmriEstimateBatch(matlabbatch); + batchName = ... + ['specify_estimate_ffx_task-', opt.taskName, ... + '_space-', opt.space, ... + '_FWHM-', num2str(funcFWHM)]; - saveMatlabBatch(matlabbatch, ... - ['specify_estimate_ffx_task-', opt.taskName, ... - '_space-', opt.space, ... - '_FWHM-', num2str(funcFWHM)], ... - opt, subID); + saveAndRunWorkflow(matlabbatch, batchName, opt, subID); - case 'contrasts' + plot_power_spectra_of_GLM_residuals( ... + getFFXdir(subID, funcFWHM, opt), ... + opt.metadata.RepetitionTime); - matlabbatch = setBatchSubjectLevelContrasts(opt, subID, funcFWHM); + deleteResidualImages(getFFXdir(subID, funcFWHM, opt)); - saveMatlabBatch(matlabbatch, ... - ['contrasts_ffx_task-', opt.taskName, ... - '_space-', opt.space, ... - '_FWHM-', num2str(funcFWHM)], ... - opt, subID); + movefile(['sub-', subID, '_task-', opt.taskName, '_design_*'], ... + getFFXdir(subID, funcFWHM, opt)); - end + case 'contrasts' - spm_jobman('run', matlabbatch); + matlabbatch = setBatchSubjectLevelContrasts(matlabbatch, opt, subID, funcFWHM); - end - end + batchName = ... + ['contrasts_ffx_task-', opt.taskName, ... + '_space-', opt.space, ... + '_FWHM-', num2str(funcFWHM)]; -end + saveAndRunWorkflow(matlabbatch, batchName, opt, subID); -function matlabbatch = setFmriEstimateBatch(matlabbatch) + end - fprintf(1, 'BUILDING JOB : FMRI estimate\n'); + end - matlabbatch{2}.spm.stats.fmri_est.spmmat(1) = cfg_dep( ... - 'fMRI model specification SPM file', ... - substruct( ... - '.', 'val', '{}', {1}, ... - '.', 'val', '{}', {1}, ... - '.', 'val', '{}', {1}), ... - substruct('.', 'spmmat')); + end - matlabbatch{2}.spm.stats.fmri_est.method.Classical = 1; - matlabbatch{2}.spm.stats.fmri_est.write_residuals = 1; end diff --git a/src/workflows/bidsGZipRawFolder.m b/src/workflows/bidsGZipRawFolder.m new file mode 100644 index 00000000..99003501 --- /dev/null +++ b/src/workflows/bidsGZipRawFolder.m @@ -0,0 +1,41 @@ +% (C) Copyright 2020 CPP BIDS SPM-pipeline developers + +function bidsGZipRawFolder(opt, keepUnzippedNii) + % + % + % GZip the nii files in a ``raw`` bids folders from the. It will do it independently of the task. + % + % USAGE:: + % + % bidsGZipRawFolder(optSource ... + % [, keepUnzippedNii = false]) + % + % :param opt: The structure that contains the options set by the user to run the batch + % workflow for source processing + % :type opt: structure + % :param keepUnzippedNii: will keep the original ``.nii`` if set to ``true``. Default is false + % :type keepUnzippedNii: boolean + + %% input variables default values + + if nargin < 2 || isempty(keepUnzippedNii) + % delete the original unzipped .nii + keepUnzippedNii = false; + end + + tic; + + printWorklowName('GZip data'); + + rawDir = opt.dataDir; + + unzippedNiifiles = cellstr(spm_select('FPListRec', rawDir, '^.*.nii$')); + + matlabbatch = []; + matlabbatch = setBatchGZip(matlabbatch, unzippedNiifiles, keepUnzippedNii); + + spm_jobman('run', matlabbatch); + + toc; + +end diff --git a/src/workflows/bidsRFX.m b/src/workflows/bidsRFX.m index a32a36b3..c4990bec 100644 --- a/src/workflows/bidsRFX.m +++ b/src/workflows/bidsRFX.m @@ -1,170 +1,98 @@ -% (C) Copyright 2019 CPP BIDS SPM-pipeline developers +% (C) Copyright 2020 CPP BIDS SPM-pipeline developers -function bidsRFX(action, funcFWHM, conFWHM, opt) +function bidsRFX(action, opt, funcFWHM, conFWHM) % - % This script smooth all con images created at the fisrt level in each - % subject, create a mean structural image and mean mask over the - % population, process the factorial design specification and estimation - % and estimate Contrats. :: + % - smooths all contrast images created at the subject level % - % bidsRFX(action, funcFWHM, conFWHM, opt) + % OR % - % :param action: (string) ``smoothContrasts`` or ``RFX`` - % :param funcFWHM: (scalar) - % :param conFWHM: (scalar) - % :param opt: (structure) (see checkOptions()) + % - creates a mean structural image and mean mask over the sample, + % - specifies and estimates the group level model, + % - computes the group level contrasts. % - % - case 'smoothContrasts': smooth con images - % - case 'RFX': Mean Struct, MeanMask, Factorial design specification and - % estimation, Contrast estimation + % USAGE:: % - % funcFWHM: How much smoothing was applied to the functional - % data in the preprocessing + % bidsRFX(action, [opt,] [funcFWHM = 0,] [conFWHM = 0]) + % + % :param action: Action to be conducted: ``smoothContrasts`` or ``RFX`` + % :type action: string + % :param opt: structure or json filename containing the options. See + % ``checkOptions()`` and ``loadAndCheckOptions()``. + % :type opt: structure + % :param funcFWHM: How much smoothing was applied to the functional + % data in the preprocessing (Gaussian kernel size). + % :type funcFWHM: scalar + % :param conFWHM: How much smoothing will be applied to the contrast + % images (Gaussian kernel size). + % :type conFWHM: scalar + % + % - case ``smoothContrasts``: smooth con images + % - case ``RFX``: Mean Struct, MeanMask, Factorial design specification and + % estimation, Contrast estimation % - % conFWHM: How much smoothing is required for the CON images for - % the second level analysis - if nargin < 2 || isempty(funcFWHM) - funcFWHM = 0; + if nargin < 2 + opt = []; end - if nargin < 3 || isempty(conFWHM) - conFWHM = 0; + if nargin < 4 || isempty(funcFWHM) + funcFWHM = 0; end - % if input has no opt, load the opt.mat file - if nargin < 4 - opt = []; + if nargin < 4 || isempty(conFWHM) + conFWHM = 0; end - opt = loadAndCheckOptions(opt); - % load the subjects/Groups information and the task name - [group, opt, ~] = getData(opt); + [~, opt, group] = setUpWorkflow(opt, 'group level GLM'); switch action case 'smoothContrasts' - matlabbatch = setBatchSmoothConImages(group, funcFWHM, conFWHM, opt); - - saveMatlabBatch( ... - ['smooth_con_FWHM-', num2str(conFWHM), '_task-', opt.taskName], ... - 'STC', ... - opt); + matlabbatch = []; + matlabbatch = setBatchSmoothConImages(matlabbatch, group, opt, funcFWHM, conFWHM); - spm_jobman('run', matlabbatch); + saveAndRunWorkflow(matlabbatch, ... + ['smooth_con_FWHM-', num2str(conFWHM), '_task-', opt.taskName], ... + opt); case 'RFX' - fprintf(1, 'Create Mean Struct and Mask IMAGES...'); - - rfxDir = getRFXdir(opt, funcFWHM, conFWHM, contrastName); + opt.rfxDir = getRFXdir(opt, funcFWHM, conFWHM); % ------ % TODO % - need to rethink where to save the anat and mask % - need to smooth the anat % - create a masked version of the anat too + % - needs to be improved (maybe??) as the structural and mask may vary for + % different analysis % ------ + matlabbatch = []; + matlabbatch = setBatchMeanAnatAndMask(matlabbatch, ... + opt, ... + funcFWHM, ... + fullfile(opt.derivativesDir, 'group')); + saveAndRunWorkflow(matlabbatch, 'create_mean_struc_mask', opt); - matlabbatch = ... - setBatchMeanAnatAndMask(opt, funcFWHM, rfxDir); - - % ------ % TODO - % needs to be improved (maybe??) as the structural and mask may vary for - % different analysis - % ------ - - saveMatlabBatch(matlabbatch, 'create_mean_struc_mask', opt); - - spm_jobman('run', matlabbatch); - - %% Factorial design specification + % saving needs to be improved (maybe??) as the name may vary with FXHM and contrast + matlabbatch = []; + matlabbatch = setBatchFactorialDesign(matlabbatch, opt, funcFWHM, conFWHM); % Load the list of contrasts of interest for the RFX grpLvlCon = getGrpLevelContrastToCompute(opt); + matlabbatch = setBatchEstimateModel(matlabbatch, grpLvlCon, opt); - % ------ - % TODO - % rfxDir should probably be set in setBatchFactorialDesign - % ------ - - rfxDir = getRFXdir(opt, funcFWHM, conFWHM, contrastName); + saveAndRunWorkflow(matlabbatch, 'group_level_model_specification_estimation', opt); - matlabbatch = setBatchFactorialDesign(grpLvlCon, group, conFWHM, rfxDir); - - % ------ % TODO - % needs to be improved (maybe??) as the name may vary with FXHM and - % contrast - % ------ - - saveMatlabBatch(matlabbatch, 'rfx_specification', opt); - - fprintf(1, 'Factorial Design Specification...'); - - spm_jobman('run', matlabbatch); - - %% Factorial design estimation - - fprintf(1, 'BUILDING JOB: Factorial Design Estimation'); - - matlabbatch = {}; - - for j = 1:size(grpLvlCon, 1) - conName = rmTrialTypeStr(grpLvlCon{j}); - matlabbatch{j}.spm.stats.fmri_est.spmmat = ... - { fullfile(rfxDir, conName, 'SPM.mat') }; %#ok<*AGROW> - matlabbatch{j}.spm.stats.fmri_est.method.Classical = 1; - end - - % ------ - % TODO - % needs to be improved (maybe??) as the name may vary with FXHM and - % contrast - % ------ - - saveMatlabBatch(matlabbatch, 'rfx_estimation', opt); - - fprintf(1, 'Factorial Design Estimation...'); - - spm_jobman('run', matlabbatch); - - %% Contrast estimation - - fprintf(1, 'BUILDING JOB: Contrast estimation'); - - matlabbatch = {}; - - % ADD/REMOVE CONTRASTS DEPENDING ON YOUR EXPERIMENT AND YOUR GROUPS - for j = 1:size(grpLvlCon, 1) - conName = rmTrialTypeStr(grpLvlCon{j}); - matlabbatch{j}.spm.stats.con.spmmat = ... - {fullfile(rfxDir, conName, 'SPM.mat')}; - matlabbatch{j}.spm.stats.con.consess{1}.tcon.name = 'GROUP'; - matlabbatch{j}.spm.stats.con.consess{1}.tcon.convec = 1; - matlabbatch{j}.spm.stats.con.consess{1}.tcon.sessrep = 'none'; - - matlabbatch{j}.spm.stats.con.delete = 0; - end - - % ------ - % TODO - % needs to be improved (maybe??) as the name may vary with FXHM and - % contrast - % ------ - - saveMatlabBatch(matlabbatch, 'rfx_contrasts', opt); - - fprintf(1, 'Contrast Estimation...'); - - spm_jobman('run', matlabbatch); + % saving needs to be improved (maybe??) as the name may vary with FXHM and contrast + rfxDir = getRFXdir(opt, funcFWHM, conFWHM); + matlabbatch = []; + matlabbatch = setBatchGroupLevelContrasts(matlabbatch, grpLvlCon, rfxDir); + saveAndRunWorkflow(matlabbatch, 'contrasts_rfx', opt); end end - -function conName = rmTrialTypeStr(conName) - conName = strrep(conName, 'trial_type.', ''); -end diff --git a/src/workflows/bidsRealignReslice.m b/src/workflows/bidsRealignReslice.m index 3b3c9b26..66f46cbf 100644 --- a/src/workflows/bidsRealignReslice.m +++ b/src/workflows/bidsRealignReslice.m @@ -1,29 +1,32 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers function bidsRealignReslice(opt) - % bidsRealignReslice(opt) % - % The scripts realigns the functional - % Assumes that bidsSTC has already been run - - %% TO DO - % find a way to paralelize this over subjects + % Realigns and reslices the functional data of a given task. + % + % USAGE:: + % + % bidsRealignReslice(opt) + % + % :param opt: structure or json filename containing the options. See + % ``checkOptions()`` and ``loadAndCheckOptions()``. + % :type opt: structure + % + % Assumes that ``bidsSTC()`` has already been run. + % - % if input has no opt, load the opt.mat file if nargin < 1 opt = []; end - opt = loadAndCheckOptions(opt); - % load the subjects/Groups information and the task name - [group, opt, BIDS] = getData(opt); + [BIDS, opt, group] = setUpWorkflow(opt, 'realign and reslice'); %% Loop through the groups, subjects, and sessions for iGroup = 1:length(group) groupName = group(iGroup).name; - for iSub = 1:group(iGroup).numSub + parfor iSub = 1:group(iGroup).numSub % Get the ID of the subject % (i.e SubNumber doesnt have to match the iSub if one subject @@ -40,9 +43,9 @@ function bidsRealignReslice(opt) opt, ... 'realignReslice'); - saveMatlabBatch(matlabbatch, 'realign_reslice', opt, subID); + saveAndRunWorkflow(matlabbatch, 'realign_reslice', opt, subID); - spm_jobman('run', matlabbatch); + copyFigures(BIDS, opt, subID); end end diff --git a/src/workflows/bidsRealignUnwarp.m b/src/workflows/bidsRealignUnwarp.m index 15da1897..e8db8d0b 100644 --- a/src/workflows/bidsRealignUnwarp.m +++ b/src/workflows/bidsRealignUnwarp.m @@ -1,29 +1,35 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers function bidsRealignUnwarp(opt) - % bidsRealignReslice(opt) % - % The scripts realigns the functional - % Assumes that bidsSTC has already been run - - %% TO DO - % find a way to paralelize this over subjects + % Realigns and unwarps the functional data of a given task. + % + % USAGE:: + % + % bidsRealignReslice(opt) + % + % :param opt: structure or json filename containing the options. See + % ``checkOptions()`` and ``loadAndCheckOptions()``. + % :type opt: structure + % + % Assumes that ``bidsSTC`` has already been run. + % + % If the ``bidsCreateVDM()`` workflow has been run before the voxel displacement + % maps will be used unless ``opt.useFieldmaps`` is set to ``false``. + % - % if input has no opt, load the opt.mat file if nargin < 1 opt = []; end - opt = loadAndCheckOptions(opt); - % load the subjects/Groups information and the task name - [group, opt, BIDS] = getData(opt); + [BIDS, opt, group] = setUpWorkflow(opt, 'realign and unwarp'); %% Loop through the groups, subjects, and sessions for iGroup = 1:length(group) groupName = group(iGroup).name; - for iSub = 1:group(iGroup).numSub + parfor iSub = 1:group(iGroup).numSub % Get the ID of the subject % (i.e SubNumber doesnt have to match the iSub if one subject @@ -40,9 +46,9 @@ function bidsRealignUnwarp(opt) opt, ... 'realignUnwarp'); - saveMatlabBatch(matlabbatch, 'realign_unwarp', opt, subID); + saveAndRunWorkflow(matlabbatch, 'realign_unwarp', opt, subID); - spm_jobman('run', matlabbatch); + copyFigures(BIDS, opt, subID); end end diff --git a/src/workflows/bidsResliceTpmToFunc.m b/src/workflows/bidsResliceTpmToFunc.m index 76dfe277..0421e856 100644 --- a/src/workflows/bidsResliceTpmToFunc.m +++ b/src/workflows/bidsResliceTpmToFunc.m @@ -1,20 +1,32 @@ % (C) Copyright 2020 CPP BIDS SPM-pipeline developers function bidsResliceTpmToFunc(opt) - % bidsResliceTpmToFunc(opt) % - % reslices the tissue probability map from the segmentation to the mean - % functional + % Reslices the tissue probability map (TPMs) from the segmentation to the mean + % functional. + % + % USAGE:: + % + % bidsResliceTpmToFunc([opt]) + % + % :param opt: structure or json filename containing the options. See + % ``checkOptions()`` and ``loadAndCheckOptions()``. + % :type opt: structure + % + % Assumes that the anatomical has already been segmented by ``bidsSpatialPrepro()`` + % or ``bidsSegmentSkullStrip()``. + % + % It is necessary to run this workflow before running the ``functionalQA`` pipeline + % as the computation of the tSNR by ``spmup`` requires the TPMs to have the same dimension + % as the functional. + % - % if input has no opt, load the opt.mat file if nargin < 1 opt = []; end - opt = loadAndCheckOptions(opt); - [group, opt, BIDS] = getData(opt); - - fprintf(1, 'RESLICING TPM TO MEAN FUNCTIONAL\n\n'); + [BIDS, opt, group] = setUpWorkflow(opt, ... + 'reslicing tissue probability maps to functional dimension'); %% Loop through the groups, subjects, and sessions for iGroup = 1:length(group) @@ -33,12 +45,30 @@ function bidsResliceTpmToFunc(opt) [anatImage, anatDataDir] = getAnatFilename(BIDS, subID, opt); TPMs = validationInputFile(anatDataDir, anatImage, 'c[123]'); - matlabbatch = setBatchReslice( ... + matlabbatch = []; + matlabbatch = setBatchReslice(matlabbatch, ... fullfile(meanFuncDir, meanImage), ... cellstr(TPMs)); - saveMatlabBatch(matlabbatch, 'reslice_tpm', opt, subID); - spm_jobman('run', matlabbatch); + saveAndRunWorkflow(matlabbatch, 'reslice_tpm', opt, subID); + + %% Compute brain mask of functional + TPMs = validationInputFile(anatDataDir, anatImage, 'rc[123]'); + % greay matter + input{1, 1} = TPMs(1, :); + % white matter + input{2, 1} = TPMs(2, :); + % csf + input{3, 1} = TPMs(3, :); + + output = strrep(meanImage, '.nii', '_mask.nii'); + + expression = sprintf('(i1+i2+i3)>%f', opt.skullstrip.threshold); + + matlabbatch = []; + matlabbatch = setBatchImageCalculation(matlabbatch, input, output, meanFuncDir, expression); + + saveAndRunWorkflow(matlabbatch, 'create_functional_brain_mask', opt, subID); end diff --git a/src/workflows/bidsResults.m b/src/workflows/bidsResults.m index 1673fd4b..05c00a88 100644 --- a/src/workflows/bidsResults.m +++ b/src/workflows/bidsResults.m @@ -1,173 +1,112 @@ -% (C) Copyright 2019 CPP BIDS SPM-pipeline developers +% (C) Copyright 2020 CPP BIDS SPM-pipeline developers function bidsResults(opt, funcFWHM, conFWHM) - % This scripts computes the results for a series of contrast that can be + % + % Computes the results for a series of contrast that can be % specified at the run, subject or dataset step level (see contrast specification - % following the BIDS stats model specification) + % following the BIDS stats model specification). + % + % USAGE:: + % + % bidsResults([opt], funcFWHM, conFWHM) + % + % :param opt: structure or json filename containing the options. See + % ``checkOptions()`` and ``loadAndCheckOptions()``. + % :type opt: structure + % :param funcFWHM: How much smoothing was applied to the functional + % data in the preprocessing (Gaussian kernel size). + % :type funcFWHM: scalar + % :param conFWHM: How much smoothing will be applied to the contrast + % images (Gaussian kernel size). + % :type conFWHM: scalar % - % funcFWHM is the number of the mm smoothing used on the normalized functional files. - % for unsmoothied data FFXSmoothing = 0 - % if input has no opt, load the opt.mat file if nargin < 1 opt = []; end - opt = loadAndCheckOptions(opt); - % load the subjects/Groups information and the task name - [group, opt] = getData(opt); + [~, opt, group] = setUpWorkflow(opt, 'computing GLM results'); matlabbatch = []; + % TOD0 + % if it does not exist create the default "result" field from the BIDS model file + % loop trough the steps and more results to compute for each contrast % mentioned for each step for iStep = 1:length(opt.result.Steps) - for iCon = 1:length(opt.result.Steps(iStep).Contrasts) - - % Depending on the level step we migh have to define a matlabbatch - % for each subject or just on for the whole group - switch opt.result.Steps(iStep).Level - - case 'run' - warning('run level not implemented yet'); - - % saveMatlabBatch(matlabbatch, 'computeFfxResults', opt, subID); - - case 'subject' + % Depending on the level step we migh have to define a matlabbatch + % for each subject or just on for the whole group + switch opt.result.Steps(iStep).Level - matlabbatch = ... - setBatchSubjectLevelResults( ... - matlabbatch, ... - group, ... - funcFWHM, ... - opt, ... - iStep, ... - iCon); + case 'run' + warning('run level not implemented yet'); - % TODO - % Save this batch in for each subject and not once for all + % saveMatlabBatch(matlabbatch, 'computeFfxResults', opt, subID); - saveMatlabBatch(matlabbatch, 'compute_ffx_results', opt); + case 'subject' - case 'dataset' + for iGroup = 1:length(group) - rfxDir = getRFXdir(opt, funcFWHM, conFWHM, contrastName); + % For each subject + for iSub = 1:group(iGroup).numSub - load(fullfile(rfxDir, 'SPM.mat')); + for iCon = 1:length(opt.result.Steps(iStep).Contrasts) - results.dir = rfxDir; - results.contrastNb = 1; - results.label = 'group level'; - results.nbSubj = SPM.nscan; + % Get the Subject ID + subID = group(iGroup).subNumber{iSub}; - matlabbatch = resultsMatlabbatch( ... - matlabbatch, opt, iStep, iCon, results); + matlabbatch = ... + setBatchSubjectLevelResults( ... + matlabbatch, ... + opt, ... + subID, ... + funcFWHM, ... + iStep, ... + iCon); - saveMatlabBatch(matlabbatch, 'compute_rfx_results', opt); + end - end - end - - end - - if ~isempty(matlabbatch) - - spm_jobman('run', matlabbatch); + end - % move ps file - % TODO - - % rename NIDM file - % TODO - end - -end + batchName = sprintf('compute_sub-%s_results', subID); -function batch = resultsMatlabbatch(batch, opt, iStep, iCon, results) - % outputs the typical matlabbatch to compute the results for a given - % contrast + saveAndRunWorkflow(matlabbatch, batchName, opt, subID); - batch{end + 1}.spm.stats.results.spmmat = {fullfile(results.dir, 'SPM.mat')}; + end - batch{end}.spm.stats.results.conspec.titlestr = ... - opt.result.Steps(iStep).Contrasts(iCon).Name; + case 'dataset' - batch{end}.spm.stats.results.conspec.contrasts = results.contrastNb; + results.dir = getRFXdir(opt, funcFWHM, conFWHM); + results.contrastNb = 1; + results.label = 'group level'; - batch{end}.spm.stats.results.conspec.threshdesc = ... - opt.result.Steps(iStep).Contrasts(iCon).MC; + load(fullfile(results.dir, 'SPM.mat')); + results.nbSubj = SPM.nscan; - batch{end}.spm.stats.results.conspec.thresh = opt.result.Steps(iStep).Contrasts(iCon).p; + for iCon = 1:length(opt.result.Steps(iStep).Contrasts) - batch{end}.spm.stats.results.conspec.extent = opt.result.Steps(iStep).Contrasts(iCon).k; + matlabbatch = setBatchResults(matlabbatch, opt, iStep, iCon, results); - batch{end}.spm.stats.results.conspec.conjunction = 1; + end - batch{end}.spm.stats.results.conspec.mask.none = ... - ~opt.result.Steps(iStep).Contrasts(iCon).Mask; + batchName = 'compute_group_level_results'; - batch{end}.spm.stats.results.units = 1; + saveAndRunWorkflow(matlabbatch, batchName, opt); - batch{end}.spm.stats.results.export{1}.ps = true; + otherwise - if opt.result.Steps(1).Contrasts(iCon).NIDM + error('This BIDS model does not contain an analysis step I understand.'); - batch{end}.spm.stats.results.export{2}.nidm.modality = 'FMRI'; - - batch{end}.spm.stats.results.export{2}.nidm.refspace = 'ixi'; - if strcmp(opt.space, 'T1w') - batch{end}.spm.stats.results.export{2}.nidm.refspace = 'subject'; end - batch{end}.spm.stats.results.export{2}.nidm.group.nsubj = results.nbSubj; - - batch{end}.spm.stats.results.export{2}.nidm.group.label = results.label; - end -end - -function batch = setBatchSubjectLevelResults(varargin) - - [batch, grp, funcFWHM, opt, iStep, iCon] = deal(varargin{:}); - - for iGroup = 1:length(grp) - - % For each subject - for iSub = 1:grp(iGroup).numSub - - % Get the Subject ID - subID = grp(iGroup).subNumber{iSub}; + % move ps file + % TODO - % FFX Directory - ffxDir = getFFXdir(subID, funcFWHM, opt); - - load(fullfile(ffxDir, 'SPM.mat')); - - % identify which contrast nb actually has the name the user asked - conNb = find( ... - strcmp({SPM.xCon.name}', ... - opt.result.Steps(iStep).Contrasts(iCon).Name)); - - if isempty(conNb) - sprintf('List of contrast in this SPM file'); - disp({SPM.xCon.name}'); - error( ... - 'This SPM file %s does not contain a contrast named %s', ... - fullfile(ffxDir, 'SPM.mat'), ... - opt.result.Steps(1).Contrasts(iCon).Name); - end - - results.dir = ffxDir; - results.contrastNb = conNb; - results.label = subID; - results.nbSubj = 1; - - batch = resultsMatlabbatch( ... - batch, opt, iStep, iCon, results); - - end - end + % rename NIDM file + % TODO end diff --git a/src/workflows/bidsSTC.m b/src/workflows/bidsSTC.m index 49e2334c..ade9c400 100644 --- a/src/workflows/bidsSTC.m +++ b/src/workflows/bidsSTC.m @@ -1,60 +1,56 @@ % (C) Copyright 2019 CPP BIDS SPM-pipeline developers function bidsSTC(opt) - % Performs SLICE TIMING CORRECTION of the functional data. The - % script for each subject and can handle multiple sessions and multiple - % runs. % - % Slice timing units is in milliseconds to be BIDS compliant and not in slice number - % as is more traditionally the case with SPM. + % Performs the slie timing correction of the functional data. % - % In the case the slice timing information was not specified in the json FILES - % in the BIDS data set (e.g it couldnt be extracted from the trento old scanner), - % then add this information manually in opt.sliceOrder field. + % USAGE:: % - % If this is empty the slice timing correction will not be performed + % bidsSTC([opt]) % - % If not specified this function will take the mid-volume time point as reference - % to do the slice timing correction + % :param opt: structure or json filename containing the options. See + % ``checkOptions()`` and ``loadAndCheckOptions()``. + % :type opt: structure % - % See README.md for more information about slice timing correction + % STC will be performed using the information provided in the BIDS data set. It + % will use the mid-volume acquisition time point as as reference. + % + % The fields of the ``opt`` structure related to STC can still be used to do some slice + % timing correction even if no information can be found in the BIDS data set. + % + % In general slice order and reference slice is entered in time unit (ms) (this is + % the BIDS way of doing things) instead of the slice index of the reference slice + % (the "SPM" way of doing things). + % + % If no slice timing information is available from the file metadata or from + % the ``opt`` strcuture this step will be skipped. + % + % See also ``getSliceOrder()``. + % + % See the documentation for more information about slice timing correction. % - % INPUT: - % opt - options structure defined by the getOption function. If no inout is given - % this function will attempt to load a opt.mat file in the same directory - % to try to get some options - % if input has no opt, load the opt.mat file if nargin < 1 opt = []; end - opt = loadAndCheckOptions(opt); - - % load the subjects/Groups information and the task name - [group, opt, BIDS] = getData(opt); - fprintf(1, 'DOING SLICE TIME CORRECTION\n'); + [BIDS, opt, group] = setUpWorkflow(opt, 'slice timing correction'); %% Loop through the groups, subjects, and sessions for iGroup = 1:length(group) groupName = group(iGroup).name; - for iSub = 1:group(iGroup).numSub + parfor iSub = 1:group(iGroup).numSub - % Get the ID of the subject - % (i.e SubNumber doesnt have to match the iSub if one subject is exluded) subID = group(iGroup).subNumber{iSub}; printProcessingSubject(groupName, iSub, subID); - matlabbatch = setBatchSTC(BIDS, opt, subID); - - if ~isempty(matlabbatch) - saveMatlabBatch(matlabbatch, 'STC', opt, subID); + matlabbatch = []; + matlabbatch = setBatchSTC(matlabbatch, BIDS, opt, subID); - spm_jobman('run', matlabbatch); - end + saveAndRunWorkflow(matlabbatch, 'STC', opt, subID); end end diff --git a/src/workflows/bidsSegmentSkullStrip.m b/src/workflows/bidsSegmentSkullStrip.m index d232174f..1e2da98a 100644 --- a/src/workflows/bidsSegmentSkullStrip.m +++ b/src/workflows/bidsSegmentSkullStrip.m @@ -2,25 +2,22 @@ function bidsSegmentSkullStrip(opt) % - % Segments and skullstrips the anat image. + % Segments and skullstrips the anatomical image. % % USAGE:: % % bidsSegmentSkullStrip([opt]) % - % :param opt: options + % :param opt: structure or json filename containing the options. See + % ``checkOptions()`` and ``loadAndCheckOptions()``. % :type opt: structure + % - % if input has no opt, load the opt.mat file if nargin < 1 opt = []; end - opt = loadAndCheckOptions(opt); - - % load the subjects/Groups information and the task name - [group, opt, BIDS] = getData(opt); - fprintf(1, 'SEGMENTING AND SKULL STRIPPING ANAT\n'); + [BIDS, opt, group] = setUpWorkflow(opt, 'segmentation and skulltripping'); %% Loop through the groups, subjects, and sessions for iGroup = 1:length(group) @@ -29,14 +26,11 @@ function bidsSegmentSkullStrip(opt) for iSub = 1:group(iGroup).numSub - matlabbatch = []; - % Get the ID of the subject - % (i.e SubNumber doesnt have to match the iSub if one subject - % is exluded for any reason) subID = group(iGroup).subNumber{iSub}; printProcessingSubject(groupName, iSub, subID); + matlabbatch = []; matlabbatch = setBatchSelectAnat(matlabbatch, BIDS, opt, subID); opt.orderBatches.selectAnat = 1; @@ -46,9 +40,7 @@ function bidsSegmentSkullStrip(opt) matlabbatch = setBatchSkullStripping(matlabbatch, BIDS, subID, opt); - saveMatlabBatch(matlabbatch, 'segment_skullstrip', opt, subID); - - spm_jobman('run', matlabbatch); + saveAndRunWorkflow(matlabbatch, 'segment_skullstrip', opt, subID); end end diff --git a/src/workflows/bidsSmoothing.m b/src/workflows/bidsSmoothing.m index c98c0c24..2dd19d46 100644 --- a/src/workflows/bidsSmoothing.m +++ b/src/workflows/bidsSmoothing.m @@ -1,34 +1,44 @@ -% (C) Copyright 2019 CPP BIDS SPM-pipeline developers +% (C) Copyright 2020 CPP BIDS SPM-pipeline developers function bidsSmoothing(funcFWHM, opt) - % This scripts performs smoothing to the functional data using a full width + % + % This performs smoothing to the functional data using a full width % half maximum smoothing kernel of size "mm_smoothing". + % + % USAGE:: + % + % bidsSmoothing(funcFWHM, [opt]) + % + % :param funcFWHM: How much smoothing was applied to the functional + % data in the preprocessing (Gaussian kernel size). + % :type funcFWHM: scalar + % :param opt: structure or json filename containing the options. See + % ``checkOptions()`` and ``loadAndCheckOptions()``. + % :type opt: structure + + % - % if input has no opt, load the opt.mat file if nargin < 2 opt = []; end - opt = loadAndCheckOptions(opt); - % load the subjects/Groups information and the task name - [group, opt, BIDS] = getData(opt); + [BIDS, opt, group] = setUpWorkflow(opt, 'smoothing functional data'); %% Loop through the groups, subjects, and sessions for iGroup = 1:length(group) groupName = group(iGroup).name; - for iSub = 1:group(iGroup).numSub + parfor iSub = 1:group(iGroup).numSub subID = group(iGroup).subNumber{iSub}; printProcessingSubject(groupName, iSub, subID); - matlabbatch = setBatchSmoothing(BIDS, opt, subID, funcFWHM); + matlabbatch = []; + matlabbatch = setBatchSmoothingFunc(matlabbatch, BIDS, opt, subID, funcFWHM); - saveMatlabBatch(matlabbatch, ['smoothing_FWHM-' num2str(funcFWHM)], opt, subID); - - spm_jobman('run', matlabbatch); + saveAndRunWorkflow(matlabbatch, ['smoothing_FWHM-' num2str(funcFWHM)], opt, subID); end end diff --git a/src/workflows/bidsSpatialPrepro.m b/src/workflows/bidsSpatialPrepro.m index 0616e0f9..91e73a34 100644 --- a/src/workflows/bidsSpatialPrepro.m +++ b/src/workflows/bidsSpatialPrepro.m @@ -1,41 +1,63 @@ % (C) Copyright 2019 CPP BIDS SPM-pipeline developers function bidsSpatialPrepro(opt) - % bidsSpatialPrepro(opt) % % Performs spatial preprocessing of the functional and structural data. % - % TODO update description - % The structural data are segmented and normalized to MNI space. - % The functional data are re-aligned, coregistered with the structural and - % normalized to MNI space. + % USAGE:: + % + % bidsSpatialPrepro([opt]) + % + % :param opt: structure or json filename containing the options. See + % ``checkOptions()`` and ``loadAndCheckOptions()``. + % :type opt: structure + % + % The anatomical data are segmented, skulls-stripped [and normalized to MNI space]. + % + % The functional data are re-aligned (unwarped), coregistered with the structural, + % the anatomical data is skull-stripped [and normalized to MNI space]. + % + % If you do not want to: + % + % - to perform realign AND unwarp, make sure you set + % ``opt.realign.useUnwarp`` to ``true``. + % - normalize the data to MNI space, make sure you set + % ``opt.space`` to ``MNI``. + % + % If you want to: + % + % - use another type of anatomical data than ``T1w`` as a reference or want to specify + % which anatomical session is to be used as a reference, you can set this in + % ``opt.anatReference``:: + % + % opt.anatReference.type = 'T1w'; + % opt.anatReference.session = 1; + % + % .. TODO: + % + % - average T1s across sessions if necessarry % - % Assumptions: - % - the batch is build using dependencies across the different batch modules - - % TO DO - % - find a way to paralelize this over subjects - % - average T1s across sessions if necessarry - % if input has no opt, load the opt.mat file if nargin < 1 opt = []; end - opt = loadAndCheckOptions(opt); - setGraphicWindow(); + [BIDS, opt, group] = setUpWorkflow(opt, 'spatial preprocessing'); - % load the subjects/Groups information and the task name - [group, opt, BIDS] = getData(opt); - - fprintf(1, 'DOING SPATIAL PREPROCESSING\n'); + opt.orderBatches.selectAnat = 1; + opt.orderBatches.realign = 2; + opt.orderBatches.coregister = 3; + opt.orderBatches.saveCoregistrationMatrix = 4; + opt.orderBatches.segment = 5; + opt.orderBatches.skullStripping = 6; + opt.orderBatches.skullStrippingMask = 7; %% Loop through the groups, subjects, and sessions for iGroup = 1:length(group) groupName = group(iGroup).name; - for iSub = 1:group(iGroup).numSub + parfor iSub = 1:group(iGroup).numSub matlabbatch = []; % Get the ID of the subject @@ -46,43 +68,41 @@ function bidsSpatialPrepro(opt) printProcessingSubject(groupName, iSub, subID); matlabbatch = setBatchSelectAnat(matlabbatch, BIDS, opt, subID); - opt.orderBatches.selectAnat = 1; + % if action is emtpy then only realign will be done action = []; - if strcmp(opt.space, 'individual') - action = 'realignUnwarp'; + if ~opt.realign.useUnwarp + action = 'realign'; end - [matlabbatch, voxDim] = setBatchRealign(matlabbatch, BIDS, subID, opt, action); - opt.orderBatches.realign = 2; + [matlabbatch, voxDim] = setBatchRealign(matlabbatch, action, BIDS, opt, subID); % dependency from file selector ('Anatomical') - matlabbatch = setBatchCoregistrationFuncToAnat(matlabbatch, BIDS, subID, opt); - opt.orderBatches.coregister = 3; + matlabbatch = setBatchCoregistrationFuncToAnat(matlabbatch, BIDS, opt, subID); - matlabbatch = setBatchSaveCoregistrationMatrix(matlabbatch, BIDS, subID, opt); + matlabbatch = setBatchSaveCoregistrationMatrix(matlabbatch, BIDS, opt, subID); % dependency from file selector ('Anatomical') matlabbatch = setBatchSegmentation(matlabbatch, opt); - opt.orderBatches.segment = 5; - matlabbatch = setBatchSkullStripping(matlabbatch, BIDS, subID, opt); + matlabbatch = setBatchSkullStripping(matlabbatch, BIDS, opt, subID); if strcmp(opt.space, 'MNI') % dependency from segmentation % dependency from coregistration - matlabbatch = setBatchNormalizationSpatialPrepro(matlabbatch, voxDim, opt); + matlabbatch = setBatchNormalizationSpatialPrepro(matlabbatch, opt, voxDim); + end + + % if no unwarping was done on func, we reslice the func, so we can use + % them for the functionalQA + if ~opt.realign.useUnwarp + matlabbatch = setBatchRealign(matlabbatch, 'reslice', BIDS, opt, subID); end batchName = ['spatial_preprocessing-' upper(opt.space(1)) opt.space(2:end)]; - saveMatlabBatch(matlabbatch, batchName, opt, subID); - spm_jobman('run', matlabbatch); + saveAndRunWorkflow(matlabbatch, batchName, opt, subID); - imgNb = copyGraphWindownOutput(opt, subID, 'realign'); - if strcmp(opt.space, 'individual') - imgNb = copyGraphWindownOutput(opt, subID, 'unwarp', imgNb); - end - imgNb = copyGraphWindownOutput(opt, subID, 'func2anatCoreg', imgNb); + copyFigures(BIDS, opt, subID); end end diff --git a/src/workflows/saveAndRunWorkflow.m b/src/workflows/saveAndRunWorkflow.m new file mode 100644 index 00000000..363b305b --- /dev/null +++ b/src/workflows/saveAndRunWorkflow.m @@ -0,0 +1,38 @@ +% (C) Copyright 2019 CPP BIDS SPM-pipeline developers + +function saveAndRunWorkflow(matlabbatch, batchName, opt, subID) + % + % Saves the SPM matlabbatch and runs it + % + % USAGE:: + % + % saveAndRunWorkflow(matlabbatch, batchName, opt, [subID]) + % + % :param matlabbatch: list of SPM batches + % :type matlabbatch: structure + % :param batchName: name of the batch + % :type batchName: string + % :param opt: structure or json filename containing the options. See + % ``checkOptions`` and ``loadAndCheckOptions``. + % :type opt: structure + % :param subID: subject ID + % :type subID: string + + if nargin < 4 + subID = []; + end + + if ~isempty(matlabbatch) + + saveMatlabBatch(matlabbatch, batchName, opt, subID); + + spm_jobman('run', matlabbatch); + + else + warning('This batch is empty and will not be run.'); + + end + + manageWorkersPool('close', opt); + +end diff --git a/src/workflows/setUpWorkflow.m b/src/workflows/setUpWorkflow.m new file mode 100644 index 00000000..60e36f47 --- /dev/null +++ b/src/workflows/setUpWorkflow.m @@ -0,0 +1,41 @@ +% (C) Copyright 2019 CPP BIDS SPM-pipeline developers + +function [BIDS, opt, group] = setUpWorkflow(opt, workflowName) + % + % Calls some common functions to: + % - check the configuraton, + % - remove some old files from an eventual previous crash + % - loads the layout of the BIDS dataset + % - tries to open a graphic window + % + % USAGE:: + % + % [BIDS, opt, group] =setUpWorkflow(opt, workflowName) + % + % :param opt: structure or json filename containing the options. See + % ``checkOptions`` and ``loadAndCheckOptions``. + % :type opt: structure + % :param workflowName: name that will be printed on screen + % :type workflowName: string + % + % :returns: + % + % - :BIDS: (structure) returned by ``getData`` + % - :opt: options checked + % - :group: + % + + opt = loadAndCheckOptions(opt); + + % load the subjects/Groups information and the task name + [group, opt, BIDS] = getData(opt); + + cleanCrash(); + + printWorklowName(workflowName); + + setGraphicWindow(); + + manageWorkersPool('open', opt); + +end diff --git a/tests/createDummyDataSet.sh b/tests/createDummyDataSet.sh index 7f72ae92..89d39117 100755 --- a/tests/createDummyDataSet.sh +++ b/tests/createDummyDataSet.sh @@ -4,7 +4,7 @@ # defines where the BIDS data set will be created StartDir=`pwd` # relative to starting directory -StartDir=$StartDir/dummyData/derivatives/SPM12_CPPL +StartDir=$StartDir/dummyData/derivatives/cpp_spm mkdir $StartDir SubList='ctrl01 ctrl02 blind01 blind02 01 02' # subject list @@ -19,42 +19,98 @@ do do # create folder for each session and functional and fmap - ThisDir=$StartDir/sub-$Subject/ses-$Ses - mkdir $ThisDir - - mkdir $ThisDir/func - - rm $ThisDir/func/* - - touch $ThisDir/func/sub-$Subject\_ses-$Ses\_task-vismotion_run-1_bold.nii - touch $ThisDir/func/sub-$Subject\_ses-$Ses\_task-vismotion_run-2_bold.nii - touch $ThisDir/func/sub-$Subject\_ses-$Ses\_task-vislocalizer_bold.nii + mkdir $StartDir/sub-$Subject/ses-$Ses - touch $ThisDir/func/asub-$Subject\_ses-$Ses\_task-vismotion_run-1_bold.nii - touch $ThisDir/func/asub-$Subject\_ses-$Ses\_task-vismotion_run-2_bold.nii - - touch $ThisDir/func/meanusub-$Subject\_ses-$Ses\_task-vislocalizer_bold.nii - touch $ThisDir/func/s6wsub-$Subject\_ses-$Ses\_task-vislocalizer_bold.nii - touch $ThisDir/func/s6rsub-$Subject\_ses-$Ses\_task-vislocalizer_bold.nii - touch $ThisDir/func/s6usub-$Subject\_ses-$Ses\_task-vislocalizer_bold.nii - touch $ThisDir/func/rp_sub-$Subject\_ses-$Ses\_task-vislocalizer_bold.txt + # FUNC + ThisDir=$StartDir/sub-$Subject/ses-$Ses/func + mkdir $ThisDir - echo "onset\tduration\ttrial_type" >> $ThisDir/func/sub-$Subject\_ses-$Ses\_task-vislocalizer_events.tsv - echo "2\t15\tVisMot" >> $ThisDir/func/sub-$Subject\_ses-$Ses\_task-vislocalizer_events.tsv - echo "25\t15\tVisStat" >> $ThisDir/func/sub-$Subject\_ses-$Ses\_task-vislocalizer_events.tsv + touch $ThisDir/sub-$Subject\_ses-$Ses\_task-vismotion_run-1_bold.nii + echo "{\"TaskName\": \"vislocalizer\"}" > $ThisDir/sub-$Subject\_ses-$Ses\_task-vismotion_run-1_bold.json + touch $ThisDir/sub-$Subject\_ses-$Ses\_task-vismotion_run-2_bold.nii + echo "{\"TaskName\": \"vislocalizer\"}" > $ThisDir/sub-$Subject\_ses-$Ses\_task-vismotion_run-2_bold.json + touch $ThisDir/asub-$Subject\_ses-$Ses\_task-vismotion_run-1_bold.nii + touch $ThisDir/asub-$Subject\_ses-$Ses\_task-vismotion_run-2_bold.nii + + touch $ThisDir/mean_sub-$Subject\_ses-$Ses\_task-vismotion_run-1_bold.nii + + touch $ThisDir/sub-$Subject\_ses-$Ses\_task-vislocalizer_bold.nii + touch $ThisDir/meanusub-$Subject\_ses-$Ses\_task-vislocalizer_bold.nii + touch $ThisDir/s6wsub-$Subject\_ses-$Ses\_task-vislocalizer_bold.nii + touch $ThisDir/s6rsub-$Subject\_ses-$Ses\_task-vislocalizer_bold.nii + touch $ThisDir/s6usub-$Subject\_ses-$Ses\_task-vislocalizer_bold.nii + touch $ThisDir/s6wusub-$Subject\_ses-$Ses\_task-vislocalizer_bold.nii + touch $ThisDir/rp_sub-$Subject\_ses-$Ses\_task-vislocalizer_bold.txt + + echo "onset\tduration\ttrial_type" > $ThisDir/sub-$Subject\_ses-$Ses\_task-vislocalizer_events.tsv + echo "2\t15\tVisMot" >> $ThisDir/sub-$Subject\_ses-$Ses\_task-vislocalizer_events.tsv + echo "25\t15\tVisStat" >> $ThisDir/sub-$Subject\_ses-$Ses\_task-vislocalizer_events.tsv + + echo "onset\tduration\ttrial_type" > $ThisDir/sub-$Subject\_ses-$Ses\_task-vismotion_run-1_events.tsv + echo "2\t2\tVisMotUp" >> $ThisDir/sub-$Subject\_ses-$Ses\_task-vismotion_run-1_events.tsv + echo "4\t2\tVisMotDown" >> $ThisDir/sub-$Subject\_ses-$Ses\_task-vismotion_run-1_events.tsv + + echo "onset\tduration\ttrial_type" > $ThisDir/sub-$Subject\_ses-$Ses\_task-vismotion_run-2_events.tsv + echo "3\t2\tVisMotDown" >> $ThisDir/sub-$Subject\_ses-$Ses\_task-vismotion_run-2_events.tsv + echo "6\t2\tVisMotUp" >> $ThisDir/sub-$Subject\_ses-$Ses\_task-vismotion_run-2_events.tsv + + # FMAP + ThisDir=$StartDir/sub-$Subject/ses-$Ses/fmap + mkdir $ThisDir - echo "onset\tduration\ttrial_type" >> $ThisDir/func/sub-$Subject\_ses-$Ses\_task-vismotion_run-1_events.tsv - echo "2\t2\tVisMotUp" >> $ThisDir/func/sub-$Subject\_ses-$Ses\_task-vismotion_run-1_events.tsv - echo "4\t2\tVisMotDown" >> $ThisDir/func/sub-$Subject\_ses-$Ses\_task-vismotion_run-1_events.tsv + touch $ThisDir/sub-$Subject\_ses-$Ses\_run-1_phasediff.nii + touch $ThisDir/sub-$Subject\_ses-$Ses\_run-1_magnitude1.nii + touch $ThisDir/sub-$Subject\_ses-$Ses\_run-1_magnitude2.nii + touch $ThisDir/sub-$Subject\_ses-$Ses\_run-2_phasediff.nii + touch $ThisDir/sub-$Subject\_ses-$Ses\_run-2_magnitude1.nii + touch $ThisDir/sub-$Subject\_ses-$Ses\_run-2_magnitude2.nii - echo "onset\tduration\ttrial_type" >> $ThisDir/func/sub-$Subject\_ses-$Ses\_task-vismotion_run-2_events.tsv - echo "3\t2\tVisMotDown" >> $ThisDir/func/sub-$Subject\_ses-$Ses\_task-vismotion_run-2_events.tsv - echo "6\t2\tVisMotUp" >> $ThisDir/func/sub-$Subject\_ses-$Ses\_task-vismotion_run-2_events.tsv + EchoTime1=0.006 + EchoTime2=0.00746 + template='{"EchoTime1":%f, "EchoTime2":%f, "IntendedFor":"%s"}' - mkdir $ThisDir/anat + IntendedFor=`echo func/sub-$Subject\_ses-$Ses\_task-vismotion_run-1_bold.nii` + json_string=$(printf "$template" "$EchoTime1" "$EchoTime2" "$IntendedFor") + echo "$json_string" > $ThisDir/sub-$Subject\_ses-$Ses\_run-2_phasediff.json - touch $ThisDir/anat/sub-$Subject\_ses-$Ses\_T1w.nii + IntendedFor=`echo func/sub-$Subject\_ses-$Ses\_task-vislocalizer_bold.nii` + json_string=$(printf "$template" "$EchoTime1" "$EchoTime2" "$IntendedFor") + echo "$json_string" > $ThisDir/sub-$Subject\_ses-$Ses\_run-1_phasediff.json done + # ANAT + ThisDir=$StartDir/sub-$Subject/ses-01/anat + mkdir $ThisDir + + touch $ThisDir/sub-$Subject\_ses-01_T1w.nii + touch $ThisDir/msub-$Subject\_ses-01_T1w.nii + touch $ThisDir/wmsub-$Subject\_ses-01_T1w.nii + touch $ThisDir/c1sub-$Subject\_ses-01_T1w.nii + touch $ThisDir/c2sub-$Subject\_ses-01_T1w.nii + touch $ThisDir/c3sub-$Subject\_ses-01_T1w.nii + + # STATS + mkdir $StartDir/sub-$Subject/stats + mkdir $StartDir/sub-$Subject/stats/ffx_task-vismotion/ + ThisDir=$StartDir/sub-$Subject/stats/ffx_task-vismotion/ffx_space-MNI_FWHM-6 + mkdir $ThisDir + + cp $StartDir/sub-01/stats/ffx_task-vismotion/ffx_space-MNI_FWHM-6/SPM.mat $ThisDir + + touch $ThisDir/mask.nii + + touch $ThisDir/spmT_0001.nii + touch $ThisDir/spmT_0002.nii + + touch $ThisDir/con_0001.nii + touch $ThisDir/con_0002.nii + touch $ThisDir/con_0003.nii + touch $ThisDir/con_0004.nii + + touch $ThisDir/s6con_0001.nii + touch $ThisDir/s6con_0002.nii + touch $ThisDir/s6con_0003.nii + touch $ThisDir/s6con_0004.nii + done; diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/anat/sub-01_ses-01_T1w.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/anat/sub-01_ses-01_T1w.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/func/asub-01_ses-01_task-vismotion_run-1_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/func/asub-01_ses-01_task-vismotion_run-1_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/func/asub-01_ses-01_task-vismotion_run-2_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/func/asub-01_ses-01_task-vismotion_run-2_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/func/meanusub-01_ses-01_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/func/meanusub-01_ses-01_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/func/rp_sub-01_ses-01_task-vislocalizer_bold.txt b/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/func/rp_sub-01_ses-01_task-vislocalizer_bold.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/func/s6rsub-01_ses-01_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/func/s6rsub-01_ses-01_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/func/s6usub-01_ses-01_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/func/s6usub-01_ses-01_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/func/s6wsub-01_ses-01_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/func/s6wsub-01_ses-01_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/func/sub-01_ses-01_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/func/sub-01_ses-01_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/func/sub-01_ses-01_task-vislocalizer_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/func/sub-01_ses-01_task-vislocalizer_events.tsv deleted file mode 100644 index eef890c1..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/func/sub-01_ses-01_task-vislocalizer_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -2 15 VisMot -25 15 VisStat diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/func/sub-01_ses-01_task-vismotion_run-1_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/func/sub-01_ses-01_task-vismotion_run-1_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/func/sub-01_ses-01_task-vismotion_run-1_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/func/sub-01_ses-01_task-vismotion_run-1_events.tsv deleted file mode 100644 index 899cce33..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/func/sub-01_ses-01_task-vismotion_run-1_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -2 2 VisMotUp -4 2 VisMotDown diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/func/sub-01_ses-01_task-vismotion_run-2_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/func/sub-01_ses-01_task-vismotion_run-2_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/func/sub-01_ses-01_task-vismotion_run-2_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/func/sub-01_ses-01_task-vismotion_run-2_events.tsv deleted file mode 100644 index 4b01c47d..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/func/sub-01_ses-01_task-vismotion_run-2_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -3 2 VisMotDown -6 2 VisMotUp diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-02/anat/sub-01_ses-02_T1w.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-02/anat/sub-01_ses-02_T1w.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-02/func/asub-01_ses-02_task-vismotion_run-1_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-02/func/asub-01_ses-02_task-vismotion_run-1_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-02/func/asub-01_ses-02_task-vismotion_run-2_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-02/func/asub-01_ses-02_task-vismotion_run-2_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-02/func/meanusub-01_ses-02_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-02/func/meanusub-01_ses-02_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-02/func/rp_sub-01_ses-02_task-vislocalizer_bold.txt b/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-02/func/rp_sub-01_ses-02_task-vislocalizer_bold.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-02/func/s6rsub-01_ses-02_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-02/func/s6rsub-01_ses-02_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-02/func/s6usub-01_ses-02_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-02/func/s6usub-01_ses-02_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-02/func/s6wsub-01_ses-02_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-02/func/s6wsub-01_ses-02_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-02/func/sub-01_ses-02_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-02/func/sub-01_ses-02_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-02/func/sub-01_ses-02_task-vislocalizer_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-02/func/sub-01_ses-02_task-vislocalizer_events.tsv deleted file mode 100644 index eef890c1..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-02/func/sub-01_ses-02_task-vislocalizer_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -2 15 VisMot -25 15 VisStat diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-02/func/sub-01_ses-02_task-vismotion_run-1_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-02/func/sub-01_ses-02_task-vismotion_run-1_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-02/func/sub-01_ses-02_task-vismotion_run-1_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-02/func/sub-01_ses-02_task-vismotion_run-1_events.tsv deleted file mode 100644 index 899cce33..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-02/func/sub-01_ses-02_task-vismotion_run-1_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -2 2 VisMotUp -4 2 VisMotDown diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-02/func/sub-01_ses-02_task-vismotion_run-2_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-02/func/sub-01_ses-02_task-vismotion_run-2_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-02/func/sub-01_ses-02_task-vismotion_run-2_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-02/func/sub-01_ses-02_task-vismotion_run-2_events.tsv deleted file mode 100644 index 4b01c47d..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-02/func/sub-01_ses-02_task-vismotion_run-2_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -3 2 VisMotDown -6 2 VisMotUp diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/stats/ffx_visMotion/ffx_space-MNI_FWHM-6/SPM.mat b/tests/dummyData/derivatives/SPM12_CPPL/sub-01/stats/ffx_visMotion/ffx_space-MNI_FWHM-6/SPM.mat deleted file mode 100644 index 19e65bcc..00000000 Binary files a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/stats/ffx_visMotion/ffx_space-MNI_FWHM-6/SPM.mat and /dev/null differ diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-01/anat/sub-02_ses-01_T1w.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-01/anat/sub-02_ses-01_T1w.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-01/func/asub-02_ses-01_task-vismotion_run-1_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-01/func/asub-02_ses-01_task-vismotion_run-1_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-01/func/asub-02_ses-01_task-vismotion_run-2_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-01/func/asub-02_ses-01_task-vismotion_run-2_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-01/func/meanusub-02_ses-01_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-01/func/meanusub-02_ses-01_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-01/func/rp_sub-02_ses-01_task-vislocalizer_bold.txt b/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-01/func/rp_sub-02_ses-01_task-vislocalizer_bold.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-01/func/s6rsub-02_ses-01_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-01/func/s6rsub-02_ses-01_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-01/func/s6usub-02_ses-01_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-01/func/s6usub-02_ses-01_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-01/func/s6wsub-02_ses-01_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-01/func/s6wsub-02_ses-01_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-01/func/sub-02_ses-01_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-01/func/sub-02_ses-01_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-01/func/sub-02_ses-01_task-vislocalizer_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-01/func/sub-02_ses-01_task-vislocalizer_events.tsv deleted file mode 100644 index eef890c1..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-01/func/sub-02_ses-01_task-vislocalizer_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -2 15 VisMot -25 15 VisStat diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-01/func/sub-02_ses-01_task-vismotion_run-1_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-01/func/sub-02_ses-01_task-vismotion_run-1_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-01/func/sub-02_ses-01_task-vismotion_run-1_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-01/func/sub-02_ses-01_task-vismotion_run-1_events.tsv deleted file mode 100644 index 899cce33..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-01/func/sub-02_ses-01_task-vismotion_run-1_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -2 2 VisMotUp -4 2 VisMotDown diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-01/func/sub-02_ses-01_task-vismotion_run-2_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-01/func/sub-02_ses-01_task-vismotion_run-2_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-01/func/sub-02_ses-01_task-vismotion_run-2_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-01/func/sub-02_ses-01_task-vismotion_run-2_events.tsv deleted file mode 100644 index 4b01c47d..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-01/func/sub-02_ses-01_task-vismotion_run-2_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -3 2 VisMotDown -6 2 VisMotUp diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-02/anat/sub-02_ses-02_T1w.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-02/anat/sub-02_ses-02_T1w.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-02/func/asub-02_ses-02_task-vismotion_run-1_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-02/func/asub-02_ses-02_task-vismotion_run-1_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-02/func/asub-02_ses-02_task-vismotion_run-2_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-02/func/asub-02_ses-02_task-vismotion_run-2_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-02/func/meanusub-02_ses-02_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-02/func/meanusub-02_ses-02_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-02/func/rp_sub-02_ses-02_task-vislocalizer_bold.txt b/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-02/func/rp_sub-02_ses-02_task-vislocalizer_bold.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-02/func/s6rsub-02_ses-02_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-02/func/s6rsub-02_ses-02_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-02/func/s6usub-02_ses-02_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-02/func/s6usub-02_ses-02_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-02/func/s6wsub-02_ses-02_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-02/func/s6wsub-02_ses-02_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-02/func/sub-02_ses-02_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-02/func/sub-02_ses-02_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-02/func/sub-02_ses-02_task-vislocalizer_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-02/func/sub-02_ses-02_task-vislocalizer_events.tsv deleted file mode 100644 index eef890c1..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-02/func/sub-02_ses-02_task-vislocalizer_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -2 15 VisMot -25 15 VisStat diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-02/func/sub-02_ses-02_task-vismotion_run-1_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-02/func/sub-02_ses-02_task-vismotion_run-1_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-02/func/sub-02_ses-02_task-vismotion_run-1_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-02/func/sub-02_ses-02_task-vismotion_run-1_events.tsv deleted file mode 100644 index 899cce33..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-02/func/sub-02_ses-02_task-vismotion_run-1_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -2 2 VisMotUp -4 2 VisMotDown diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-02/func/sub-02_ses-02_task-vismotion_run-2_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-02/func/sub-02_ses-02_task-vismotion_run-2_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-02/func/sub-02_ses-02_task-vismotion_run-2_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-02/func/sub-02_ses-02_task-vismotion_run-2_events.tsv deleted file mode 100644 index 4b01c47d..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-02/ses-02/func/sub-02_ses-02_task-vismotion_run-2_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -3 2 VisMotDown -6 2 VisMotUp diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-01/anat/sub-blind01_ses-01_T1w.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-01/anat/sub-blind01_ses-01_T1w.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-01/func/asub-blind01_ses-01_task-vismotion_run-1_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-01/func/asub-blind01_ses-01_task-vismotion_run-1_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-01/func/asub-blind01_ses-01_task-vismotion_run-2_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-01/func/asub-blind01_ses-01_task-vismotion_run-2_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-01/func/meanusub-blind01_ses-01_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-01/func/meanusub-blind01_ses-01_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-01/func/rp_sub-blind01_ses-01_task-vislocalizer_bold.txt b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-01/func/rp_sub-blind01_ses-01_task-vislocalizer_bold.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-01/func/s6rsub-blind01_ses-01_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-01/func/s6rsub-blind01_ses-01_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-01/func/s6usub-blind01_ses-01_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-01/func/s6usub-blind01_ses-01_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-01/func/s6wsub-blind01_ses-01_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-01/func/s6wsub-blind01_ses-01_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-01/func/sub-blind01_ses-01_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-01/func/sub-blind01_ses-01_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-01/func/sub-blind01_ses-01_task-vislocalizer_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-01/func/sub-blind01_ses-01_task-vislocalizer_events.tsv deleted file mode 100644 index eef890c1..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-01/func/sub-blind01_ses-01_task-vislocalizer_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -2 15 VisMot -25 15 VisStat diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-01/func/sub-blind01_ses-01_task-vismotion_run-1_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-01/func/sub-blind01_ses-01_task-vismotion_run-1_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-01/func/sub-blind01_ses-01_task-vismotion_run-1_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-01/func/sub-blind01_ses-01_task-vismotion_run-1_events.tsv deleted file mode 100644 index 899cce33..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-01/func/sub-blind01_ses-01_task-vismotion_run-1_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -2 2 VisMotUp -4 2 VisMotDown diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-01/func/sub-blind01_ses-01_task-vismotion_run-2_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-01/func/sub-blind01_ses-01_task-vismotion_run-2_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-01/func/sub-blind01_ses-01_task-vismotion_run-2_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-01/func/sub-blind01_ses-01_task-vismotion_run-2_events.tsv deleted file mode 100644 index 4b01c47d..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-01/func/sub-blind01_ses-01_task-vismotion_run-2_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -3 2 VisMotDown -6 2 VisMotUp diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-02/anat/sub-blind01_ses-02_T1w.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-02/anat/sub-blind01_ses-02_T1w.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-02/func/asub-blind01_ses-02_task-vismotion_run-1_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-02/func/asub-blind01_ses-02_task-vismotion_run-1_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-02/func/asub-blind01_ses-02_task-vismotion_run-2_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-02/func/asub-blind01_ses-02_task-vismotion_run-2_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-02/func/meanusub-blind01_ses-02_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-02/func/meanusub-blind01_ses-02_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-02/func/rp_sub-blind01_ses-02_task-vislocalizer_bold.txt b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-02/func/rp_sub-blind01_ses-02_task-vislocalizer_bold.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-02/func/s6rsub-blind01_ses-02_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-02/func/s6rsub-blind01_ses-02_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-02/func/s6usub-blind01_ses-02_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-02/func/s6usub-blind01_ses-02_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-02/func/s6wsub-blind01_ses-02_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-02/func/s6wsub-blind01_ses-02_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-02/func/sub-blind01_ses-02_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-02/func/sub-blind01_ses-02_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-02/func/sub-blind01_ses-02_task-vislocalizer_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-02/func/sub-blind01_ses-02_task-vislocalizer_events.tsv deleted file mode 100644 index eef890c1..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-02/func/sub-blind01_ses-02_task-vislocalizer_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -2 15 VisMot -25 15 VisStat diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-02/func/sub-blind01_ses-02_task-vismotion_run-1_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-02/func/sub-blind01_ses-02_task-vismotion_run-1_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-02/func/sub-blind01_ses-02_task-vismotion_run-1_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-02/func/sub-blind01_ses-02_task-vismotion_run-1_events.tsv deleted file mode 100644 index 899cce33..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-02/func/sub-blind01_ses-02_task-vismotion_run-1_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -2 2 VisMotUp -4 2 VisMotDown diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-02/func/sub-blind01_ses-02_task-vismotion_run-2_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-02/func/sub-blind01_ses-02_task-vismotion_run-2_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-02/func/sub-blind01_ses-02_task-vismotion_run-2_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-02/func/sub-blind01_ses-02_task-vismotion_run-2_events.tsv deleted file mode 100644 index 4b01c47d..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind01/ses-02/func/sub-blind01_ses-02_task-vismotion_run-2_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -3 2 VisMotDown -6 2 VisMotUp diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-01/anat/sub-blind02_ses-01_T1w.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-01/anat/sub-blind02_ses-01_T1w.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-01/func/asub-blind02_ses-01_task-vismotion_run-1_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-01/func/asub-blind02_ses-01_task-vismotion_run-1_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-01/func/asub-blind02_ses-01_task-vismotion_run-2_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-01/func/asub-blind02_ses-01_task-vismotion_run-2_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-01/func/meanusub-blind02_ses-01_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-01/func/meanusub-blind02_ses-01_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-01/func/rp_sub-blind02_ses-01_task-vislocalizer_bold.txt b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-01/func/rp_sub-blind02_ses-01_task-vislocalizer_bold.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-01/func/s6rsub-blind02_ses-01_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-01/func/s6rsub-blind02_ses-01_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-01/func/s6usub-blind02_ses-01_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-01/func/s6usub-blind02_ses-01_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-01/func/s6wsub-blind02_ses-01_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-01/func/s6wsub-blind02_ses-01_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-01/func/sub-blind02_ses-01_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-01/func/sub-blind02_ses-01_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-01/func/sub-blind02_ses-01_task-vislocalizer_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-01/func/sub-blind02_ses-01_task-vislocalizer_events.tsv deleted file mode 100644 index eef890c1..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-01/func/sub-blind02_ses-01_task-vislocalizer_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -2 15 VisMot -25 15 VisStat diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-01/func/sub-blind02_ses-01_task-vismotion_run-1_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-01/func/sub-blind02_ses-01_task-vismotion_run-1_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-01/func/sub-blind02_ses-01_task-vismotion_run-1_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-01/func/sub-blind02_ses-01_task-vismotion_run-1_events.tsv deleted file mode 100644 index 899cce33..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-01/func/sub-blind02_ses-01_task-vismotion_run-1_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -2 2 VisMotUp -4 2 VisMotDown diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-01/func/sub-blind02_ses-01_task-vismotion_run-2_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-01/func/sub-blind02_ses-01_task-vismotion_run-2_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-01/func/sub-blind02_ses-01_task-vismotion_run-2_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-01/func/sub-blind02_ses-01_task-vismotion_run-2_events.tsv deleted file mode 100644 index 4b01c47d..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-01/func/sub-blind02_ses-01_task-vismotion_run-2_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -3 2 VisMotDown -6 2 VisMotUp diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-02/anat/sub-blind02_ses-02_T1w.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-02/anat/sub-blind02_ses-02_T1w.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-02/func/asub-blind02_ses-02_task-vismotion_run-1_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-02/func/asub-blind02_ses-02_task-vismotion_run-1_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-02/func/asub-blind02_ses-02_task-vismotion_run-2_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-02/func/asub-blind02_ses-02_task-vismotion_run-2_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-02/func/meanusub-blind02_ses-02_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-02/func/meanusub-blind02_ses-02_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-02/func/rp_sub-blind02_ses-02_task-vislocalizer_bold.txt b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-02/func/rp_sub-blind02_ses-02_task-vislocalizer_bold.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-02/func/s6rsub-blind02_ses-02_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-02/func/s6rsub-blind02_ses-02_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-02/func/s6usub-blind02_ses-02_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-02/func/s6usub-blind02_ses-02_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-02/func/s6wsub-blind02_ses-02_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-02/func/s6wsub-blind02_ses-02_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-02/func/sub-blind02_ses-02_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-02/func/sub-blind02_ses-02_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-02/func/sub-blind02_ses-02_task-vislocalizer_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-02/func/sub-blind02_ses-02_task-vislocalizer_events.tsv deleted file mode 100644 index eef890c1..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-02/func/sub-blind02_ses-02_task-vislocalizer_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -2 15 VisMot -25 15 VisStat diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-02/func/sub-blind02_ses-02_task-vismotion_run-1_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-02/func/sub-blind02_ses-02_task-vismotion_run-1_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-02/func/sub-blind02_ses-02_task-vismotion_run-1_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-02/func/sub-blind02_ses-02_task-vismotion_run-1_events.tsv deleted file mode 100644 index 899cce33..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-02/func/sub-blind02_ses-02_task-vismotion_run-1_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -2 2 VisMotUp -4 2 VisMotDown diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-02/func/sub-blind02_ses-02_task-vismotion_run-2_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-02/func/sub-blind02_ses-02_task-vismotion_run-2_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-02/func/sub-blind02_ses-02_task-vismotion_run-2_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-02/func/sub-blind02_ses-02_task-vismotion_run-2_events.tsv deleted file mode 100644 index 4b01c47d..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-blind02/ses-02/func/sub-blind02_ses-02_task-vismotion_run-2_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -3 2 VisMotDown -6 2 VisMotUp diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-01/anat/sub-ctrl01_ses-01_T1w.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-01/anat/sub-ctrl01_ses-01_T1w.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-01/func/asub-ctrl01_ses-01_task-vismotion_run-1_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-01/func/asub-ctrl01_ses-01_task-vismotion_run-1_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-01/func/asub-ctrl01_ses-01_task-vismotion_run-2_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-01/func/asub-ctrl01_ses-01_task-vismotion_run-2_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-01/func/meanusub-ctrl01_ses-01_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-01/func/meanusub-ctrl01_ses-01_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-01/func/rp_sub-ctrl01_ses-01_task-vislocalizer_bold.txt b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-01/func/rp_sub-ctrl01_ses-01_task-vislocalizer_bold.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-01/func/s6rsub-ctrl01_ses-01_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-01/func/s6rsub-ctrl01_ses-01_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-01/func/s6usub-ctrl01_ses-01_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-01/func/s6usub-ctrl01_ses-01_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-01/func/s6wsub-ctrl01_ses-01_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-01/func/s6wsub-ctrl01_ses-01_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-01/func/sub-ctrl01_ses-01_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-01/func/sub-ctrl01_ses-01_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-01/func/sub-ctrl01_ses-01_task-vislocalizer_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-01/func/sub-ctrl01_ses-01_task-vislocalizer_events.tsv deleted file mode 100644 index eef890c1..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-01/func/sub-ctrl01_ses-01_task-vislocalizer_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -2 15 VisMot -25 15 VisStat diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-01/func/sub-ctrl01_ses-01_task-vismotion_run-1_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-01/func/sub-ctrl01_ses-01_task-vismotion_run-1_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-01/func/sub-ctrl01_ses-01_task-vismotion_run-1_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-01/func/sub-ctrl01_ses-01_task-vismotion_run-1_events.tsv deleted file mode 100644 index 899cce33..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-01/func/sub-ctrl01_ses-01_task-vismotion_run-1_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -2 2 VisMotUp -4 2 VisMotDown diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-01/func/sub-ctrl01_ses-01_task-vismotion_run-2_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-01/func/sub-ctrl01_ses-01_task-vismotion_run-2_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-01/func/sub-ctrl01_ses-01_task-vismotion_run-2_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-01/func/sub-ctrl01_ses-01_task-vismotion_run-2_events.tsv deleted file mode 100644 index 4b01c47d..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-01/func/sub-ctrl01_ses-01_task-vismotion_run-2_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -3 2 VisMotDown -6 2 VisMotUp diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-02/anat/sub-ctrl01_ses-02_T1w.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-02/anat/sub-ctrl01_ses-02_T1w.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-02/func/asub-ctrl01_ses-02_task-vismotion_run-1_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-02/func/asub-ctrl01_ses-02_task-vismotion_run-1_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-02/func/asub-ctrl01_ses-02_task-vismotion_run-2_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-02/func/asub-ctrl01_ses-02_task-vismotion_run-2_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-02/func/meanusub-ctrl01_ses-02_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-02/func/meanusub-ctrl01_ses-02_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-02/func/rp_sub-ctrl01_ses-02_task-vislocalizer_bold.txt b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-02/func/rp_sub-ctrl01_ses-02_task-vislocalizer_bold.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-02/func/s6rsub-ctrl01_ses-02_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-02/func/s6rsub-ctrl01_ses-02_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-02/func/s6usub-ctrl01_ses-02_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-02/func/s6usub-ctrl01_ses-02_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-02/func/s6wsub-ctrl01_ses-02_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-02/func/s6wsub-ctrl01_ses-02_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-02/func/sub-ctrl01_ses-02_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-02/func/sub-ctrl01_ses-02_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-02/func/sub-ctrl01_ses-02_task-vislocalizer_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-02/func/sub-ctrl01_ses-02_task-vislocalizer_events.tsv deleted file mode 100644 index eef890c1..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-02/func/sub-ctrl01_ses-02_task-vislocalizer_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -2 15 VisMot -25 15 VisStat diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-02/func/sub-ctrl01_ses-02_task-vismotion_run-1_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-02/func/sub-ctrl01_ses-02_task-vismotion_run-1_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-02/func/sub-ctrl01_ses-02_task-vismotion_run-1_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-02/func/sub-ctrl01_ses-02_task-vismotion_run-1_events.tsv deleted file mode 100644 index 899cce33..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-02/func/sub-ctrl01_ses-02_task-vismotion_run-1_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -2 2 VisMotUp -4 2 VisMotDown diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-02/func/sub-ctrl01_ses-02_task-vismotion_run-2_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-02/func/sub-ctrl01_ses-02_task-vismotion_run-2_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-02/func/sub-ctrl01_ses-02_task-vismotion_run-2_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-02/func/sub-ctrl01_ses-02_task-vismotion_run-2_events.tsv deleted file mode 100644 index 4b01c47d..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl01/ses-02/func/sub-ctrl01_ses-02_task-vismotion_run-2_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -3 2 VisMotDown -6 2 VisMotUp diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-01/anat/sub-ctrl02_ses-01_T1w.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-01/anat/sub-ctrl02_ses-01_T1w.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-01/func/asub-ctrl02_ses-01_task-vismotion_run-1_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-01/func/asub-ctrl02_ses-01_task-vismotion_run-1_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-01/func/asub-ctrl02_ses-01_task-vismotion_run-2_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-01/func/asub-ctrl02_ses-01_task-vismotion_run-2_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-01/func/meanusub-ctrl02_ses-01_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-01/func/meanusub-ctrl02_ses-01_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-01/func/rp_sub-ctrl02_ses-01_task-vislocalizer_bold.txt b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-01/func/rp_sub-ctrl02_ses-01_task-vislocalizer_bold.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-01/func/s6rsub-ctrl02_ses-01_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-01/func/s6rsub-ctrl02_ses-01_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-01/func/s6usub-ctrl02_ses-01_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-01/func/s6usub-ctrl02_ses-01_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-01/func/s6wsub-ctrl02_ses-01_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-01/func/s6wsub-ctrl02_ses-01_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-01/func/sub-ctrl02_ses-01_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-01/func/sub-ctrl02_ses-01_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-01/func/sub-ctrl02_ses-01_task-vislocalizer_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-01/func/sub-ctrl02_ses-01_task-vislocalizer_events.tsv deleted file mode 100644 index eef890c1..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-01/func/sub-ctrl02_ses-01_task-vislocalizer_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -2 15 VisMot -25 15 VisStat diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-01/func/sub-ctrl02_ses-01_task-vismotion_run-1_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-01/func/sub-ctrl02_ses-01_task-vismotion_run-1_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-01/func/sub-ctrl02_ses-01_task-vismotion_run-1_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-01/func/sub-ctrl02_ses-01_task-vismotion_run-1_events.tsv deleted file mode 100644 index 899cce33..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-01/func/sub-ctrl02_ses-01_task-vismotion_run-1_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -2 2 VisMotUp -4 2 VisMotDown diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-01/func/sub-ctrl02_ses-01_task-vismotion_run-2_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-01/func/sub-ctrl02_ses-01_task-vismotion_run-2_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-01/func/sub-ctrl02_ses-01_task-vismotion_run-2_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-01/func/sub-ctrl02_ses-01_task-vismotion_run-2_events.tsv deleted file mode 100644 index 4b01c47d..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-01/func/sub-ctrl02_ses-01_task-vismotion_run-2_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -3 2 VisMotDown -6 2 VisMotUp diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-02/anat/sub-ctrl02_ses-02_T1w.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-02/anat/sub-ctrl02_ses-02_T1w.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-02/func/asub-ctrl02_ses-02_task-vismotion_run-1_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-02/func/asub-ctrl02_ses-02_task-vismotion_run-1_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-02/func/asub-ctrl02_ses-02_task-vismotion_run-2_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-02/func/asub-ctrl02_ses-02_task-vismotion_run-2_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-02/func/meanusub-ctrl02_ses-02_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-02/func/meanusub-ctrl02_ses-02_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-02/func/rp_sub-ctrl02_ses-02_task-vislocalizer_bold.txt b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-02/func/rp_sub-ctrl02_ses-02_task-vislocalizer_bold.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-02/func/s6rsub-ctrl02_ses-02_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-02/func/s6rsub-ctrl02_ses-02_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-02/func/s6usub-ctrl02_ses-02_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-02/func/s6usub-ctrl02_ses-02_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-02/func/s6wsub-ctrl02_ses-02_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-02/func/s6wsub-ctrl02_ses-02_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-02/func/sub-ctrl02_ses-02_task-vislocalizer_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-02/func/sub-ctrl02_ses-02_task-vislocalizer_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-02/func/sub-ctrl02_ses-02_task-vislocalizer_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-02/func/sub-ctrl02_ses-02_task-vislocalizer_events.tsv deleted file mode 100644 index eef890c1..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-02/func/sub-ctrl02_ses-02_task-vislocalizer_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -2 15 VisMot -25 15 VisStat diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-02/func/sub-ctrl02_ses-02_task-vismotion_run-1_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-02/func/sub-ctrl02_ses-02_task-vismotion_run-1_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-02/func/sub-ctrl02_ses-02_task-vismotion_run-1_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-02/func/sub-ctrl02_ses-02_task-vismotion_run-1_events.tsv deleted file mode 100644 index 899cce33..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-02/func/sub-ctrl02_ses-02_task-vismotion_run-1_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -2 2 VisMotUp -4 2 VisMotDown diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-02/func/sub-ctrl02_ses-02_task-vismotion_run-2_bold.nii b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-02/func/sub-ctrl02_ses-02_task-vismotion_run-2_bold.nii deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-02/func/sub-ctrl02_ses-02_task-vismotion_run-2_events.tsv b/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-02/func/sub-ctrl02_ses-02_task-vismotion_run-2_events.tsv deleted file mode 100644 index 4b01c47d..00000000 --- a/tests/dummyData/derivatives/SPM12_CPPL/sub-ctrl02/ses-02/func/sub-ctrl02_ses-02_task-vismotion_run-2_events.tsv +++ /dev/null @@ -1,3 +0,0 @@ -onset duration trial_type -3 2 VisMotDown -6 2 VisMotUp diff --git a/tests/dummyData/derivatives/SPM12_CPPL/CHANGE b/tests/dummyData/derivatives/cpp_spm/CHANGE similarity index 100% rename from tests/dummyData/derivatives/SPM12_CPPL/CHANGE rename to tests/dummyData/derivatives/cpp_spm/CHANGE diff --git a/tests/dummyData/derivatives/SPM12_CPPL/README b/tests/dummyData/derivatives/cpp_spm/README similarity index 100% rename from tests/dummyData/derivatives/SPM12_CPPL/README rename to tests/dummyData/derivatives/cpp_spm/README diff --git a/tests/dummyData/derivatives/SPM12_CPPL/T1w.json b/tests/dummyData/derivatives/cpp_spm/T1w.json similarity index 100% rename from tests/dummyData/derivatives/SPM12_CPPL/T1w.json rename to tests/dummyData/derivatives/cpp_spm/T1w.json diff --git a/tests/dummyData/derivatives/SPM12_CPPL/dataset_description.json b/tests/dummyData/derivatives/cpp_spm/dataset_description.json similarity index 87% rename from tests/dummyData/derivatives/SPM12_CPPL/dataset_description.json rename to tests/dummyData/derivatives/cpp_spm/dataset_description.json index b0dbff42..c7375505 100755 --- a/tests/dummyData/derivatives/SPM12_CPPL/dataset_description.json +++ b/tests/dummyData/derivatives/cpp_spm/dataset_description.json @@ -17,7 +17,7 @@ "", "" ], - "DatasetDOI": "", + "DatasetDOI": "doi:10.18112/openneuro.ds000114.v1.0.1", "Name": "dummyData", "BIDSVersion": "1.1.0" } diff --git a/tests/dummyData/derivatives/cpp_spm/dataset_description_old.json b/tests/dummyData/derivatives/cpp_spm/dataset_description_old.json new file mode 100755 index 00000000..c7375505 --- /dev/null +++ b/tests/dummyData/derivatives/cpp_spm/dataset_description_old.json @@ -0,0 +1,23 @@ +{ + "License": "", + "Authors": [ + "John Doe", + "Charles Darwin", + "Freddy Krueger" + ], + "Acknowledgements": "Thanks to all to tests that failed courageously.", + "HowToAcknowledge": "", + "Funding": [ + "", + "", + "" + ], + "ReferencesAndLinks": [ + "", + "", + "" + ], + "DatasetDOI": "doi:10.18112/openneuro.ds000114.v1.0.1", + "Name": "dummyData", + "BIDSVersion": "1.1.0" +} diff --git a/tests/dummyData/derivatives/SPM12_CPPL/participants.json b/tests/dummyData/derivatives/cpp_spm/participants.json similarity index 100% rename from tests/dummyData/derivatives/SPM12_CPPL/participants.json rename to tests/dummyData/derivatives/cpp_spm/participants.json diff --git a/tests/dummyData/derivatives/SPM12_CPPL/participants.tsv b/tests/dummyData/derivatives/cpp_spm/participants.tsv similarity index 100% rename from tests/dummyData/derivatives/SPM12_CPPL/participants.tsv rename to tests/dummyData/derivatives/cpp_spm/participants.tsv diff --git a/tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/anat/sub-01_ses-01_T1w.json b/tests/dummyData/derivatives/cpp_spm/sub-01/ses-01/anat/sub-01_ses-01_T1w.json similarity index 100% rename from tests/dummyData/derivatives/SPM12_CPPL/sub-01/ses-01/anat/sub-01_ses-01_T1w.json rename to tests/dummyData/derivatives/cpp_spm/sub-01/ses-01/anat/sub-01_ses-01_T1w.json diff --git a/tests/dummyData/derivatives/cpp_spm/sub-01/stats/ffx_task-vismotion/ffx_space-MNI_FWHM-6/SPM.mat b/tests/dummyData/derivatives/cpp_spm/sub-01/stats/ffx_task-vismotion/ffx_space-MNI_FWHM-6/SPM.mat new file mode 100644 index 00000000..7d25a82b Binary files /dev/null and b/tests/dummyData/derivatives/cpp_spm/sub-01/stats/ffx_task-vismotion/ffx_space-MNI_FWHM-6/SPM.mat differ diff --git a/tests/dummyData/derivatives/SPM12_CPPL/task-vislocalizer_bold.json b/tests/dummyData/derivatives/cpp_spm/task-vislocalizer_bold.json similarity index 100% rename from tests/dummyData/derivatives/SPM12_CPPL/task-vislocalizer_bold.json rename to tests/dummyData/derivatives/cpp_spm/task-vislocalizer_bold.json diff --git a/tests/dummyData/derivatives/SPM12_CPPL/task-vismotion_bold.json b/tests/dummyData/derivatives/cpp_spm/task-vismotion_bold.json similarity index 100% rename from tests/dummyData/derivatives/SPM12_CPPL/task-vismotion_bold.json rename to tests/dummyData/derivatives/cpp_spm/task-vismotion_bold.json diff --git a/tests/dummyData/models/model-default_smdl.json b/tests/dummyData/models/model-default_smdl.json new file mode 100644 index 00000000..a62df506 --- /dev/null +++ b/tests/dummyData/models/model-default_smdl.json @@ -0,0 +1,71 @@ +{ + "Name": "vislocalizer", + "Description": "default model for vislocalizer", + "Input": { + "task": "vislocalizer" + }, + "Steps": [ + { + "Level": "subject", + "Transformations": [ + { + "Name": "Factor", + "Inputs": ["trial_type"] + }, + { + "Name": "Convolve", + "Model": "spm", + "Inputs": [" "] + } + ], + "Model": { + "X": ["trial_type.VisMot", "trial_type.VisStat", + "trans_x", "trans_y", "trans_z", "rot_x", "rot_y", "rot_z" + ], + "Options": { + "high_pass_filter_cutoff_secs": 128 + }, + "Software": { + "SPM": { + "whitening": "FAST" + } + }, + "Mask": " " + }, + "AutoContrasts": ["trial_type.VisMot", "trial_type.VisStat"] + }, + { + "Level": "run", + "Transformations": [ + { + "Name": "Factor", + "Inputs": ["trial_type"] + }, + { + "Name": "Convolve", + "Model": "spm", + "Inputs": [" "] + } + ], + "Model": { + "X": ["trial_type.VisMot", "trial_type.VisStat", + "trans_x", "trans_y", "trans_z", "rot_x", "rot_y", "rot_z" + ], + "Options": { + "high_pass_filter_cutoff_secs": 128 + }, + "Software": { + "SPM": { + "whitening": "FAST" + } + }, + "Mask": " " + }, + "AutoContrasts": ["trial_type.VisMot", "trial_type.VisStat"] + }, + { + "Level": "dataset", + "AutoContrasts": ["trial_type.VisMot", "trial_type.VisStat"] + } + ] +} diff --git a/tests/dummyData/models/model-empty_smdl.json b/tests/dummyData/models/model-empty_smdl.json new file mode 100644 index 00000000..30f8518b --- /dev/null +++ b/tests/dummyData/models/model-empty_smdl.json @@ -0,0 +1,67 @@ +{ + "Name": " ", + "Description": " ", + "Input": { + "task": " " + }, + "Steps": [ + { + "Level": "subject", + "Transformations": [ + { + "Name": "Factor", + "Inputs": ["trial_type"] + }, + { + "Name": "Convolve", + "Model": "spm", + "Inputs": [" "] + } + ], + "Model": { + "X": [" "], + "Options": { + "high_pass_filter_cutoff_secs": 128 + }, + "Software": { + "SPM": { + "whitening": "FAST" + } + }, + "Mask": " " + }, + "AutoContrasts": [" "] + }, + { + "Level": "run", + "Transformations": [ + { + "Name": "Factor", + "Inputs": ["trial_type"] + }, + { + "Name": "Convolve", + "Model": "spm", + "Inputs": [" "] + } + ], + "Model": { + "X": [" "], + "Options": { + "high_pass_filter_cutoff_secs": 128 + }, + "Software": { + "SPM": { + "whitening": "FAST" + } + }, + "Mask": " " + }, + "AutoContrasts": [" "] + }, + { + "Level": "dataset", + "AutoContrasts": [" "] + } + ] +} diff --git a/tests/dummyData/models/model-visMotionLoc_smdl.json b/tests/dummyData/models/model-visMotionLoc_smdl.json index a1c21046..aca51a49 100644 --- a/tests/dummyData/models/model-visMotionLoc_smdl.json +++ b/tests/dummyData/models/model-visMotionLoc_smdl.json @@ -2,7 +2,7 @@ "Name": "Motion localizer", "Description": "contrasts for the motion localizer dataset", "Input": { - "task": "visMotion" + "task": "vismotion" }, "Steps": [ { diff --git a/tests/dummyData/models/model-vislocalizer_smdl.json b/tests/dummyData/models/model-vislocalizer_smdl.json index ad6f985f..96fc20de 100644 --- a/tests/dummyData/models/model-vislocalizer_smdl.json +++ b/tests/dummyData/models/model-vislocalizer_smdl.json @@ -9,7 +9,7 @@ "Level": "run", "Model": { "X": [ - "trial_type.VisMot", "trial_type.VisStat", + "trial_type.VisMot", "trial_type.VisStat", "trial_type.missing_condition", "trans_x", "trans_y", "trans_z", "rot_x", "rot_y", "rot_z" ] }, diff --git a/tests/dummyData/raw/dataset_description.json b/tests/dummyData/raw/dataset_description.json new file mode 100755 index 00000000..c7375505 --- /dev/null +++ b/tests/dummyData/raw/dataset_description.json @@ -0,0 +1,23 @@ +{ + "License": "", + "Authors": [ + "John Doe", + "Charles Darwin", + "Freddy Krueger" + ], + "Acknowledgements": "Thanks to all to tests that failed courageously.", + "HowToAcknowledge": "", + "Funding": [ + "", + "", + "" + ], + "ReferencesAndLinks": [ + "", + "", + "" + ], + "DatasetDOI": "doi:10.18112/openneuro.ds000114.v1.0.1", + "Name": "dummyData", + "BIDSVersion": "1.1.0" +} diff --git a/tests/test_bidsCopyRawFolder.m b/tests/test_bidsCopyRawFolder.m index fa559eea..ad8adadc 100644 --- a/tests/test_bidsCopyRawFolder.m +++ b/tests/test_bidsCopyRawFolder.m @@ -8,37 +8,77 @@ function test_bidsCopyRawFolderBasic() - % TODO add test to only copy some modalities - - % directory with this script becomes the current directory opt.dataDir = fullfile( ... fileparts(mfilename('fullpath')), ... '..', 'demos', 'MoAE', 'output', 'MoAEpilot'); - % task to analyze opt.taskName = 'auditory'; opt = checkOptions(opt); - checkDependencies(); - bidsCopyRawFolder(opt, 1); + layoutRaw = bids.layout(opt.dataDir); + layoutDerivatives = bids.layout(fullfile(opt.dataDir, '..', 'derivatives', 'cpp_spm')); + assertEqual(exist( ... fullfile(opt.dataDir, '..', ... - 'derivatives', 'SPM12_CPPL', ... + 'derivatives', 'cpp_spm', ... 'dataset_description.json'), 'file'), ... 2); assertEqual(exist( ... fullfile(opt.dataDir, '..', ... - 'derivatives', 'SPM12_CPPL', 'sub-01', 'func', ... + 'derivatives', 'cpp_spm', 'sub-01', 'func', ... 'sub-01_task-auditory_bold.nii'), 'file'), ... 2); assertEqual(exist( ... fullfile(opt.dataDir, '..', ... - 'derivatives', 'SPM12_CPPL', 'sub-01', 'anat', ... + 'derivatives', 'cpp_spm', 'sub-01', 'anat', ... 'sub-01_T1w.nii'), 'file'), ... 2); end + +function test_bidsCopyRawFolder2tasks() + + system('rm -Rf dummyData/copy'); + + opt.dataDir = fullfile( ... + fileparts(mfilename('fullpath')), ... + 'dummyData', 'derivatives', 'cpp_spm'); + + opt.derivativesDir = fullfile( ... + fileparts(mfilename('fullpath')), ... + 'dummyData', 'copy'); + + opt.taskName = 'vismotion'; + + opt = checkOptions(opt); + + unZip = false; + deleteZippedNii = false; + bidsCopyRawFolder(opt, deleteZippedNii, {'func'}, unZip); + + assertEqual(exist( ... + fullfile(opt.derivativesDir, 'derivatives', 'cpp_spm', ... + 'sub-01', ... + 'ses-01', ... + 'func', ... + 'sub-01_ses-01_task-vismotion_run-1_bold.json'), 'file'), ... + 2); + + opt.taskName = 'vislocalizer'; + bidsCopyRawFolder(opt, deleteZippedNii, {'func'}, unZip); + + BIDS = bids.layout(fullfile(opt.derivativesDir, 'derivatives', 'cpp_spm')); + + modalities = bids.query(BIDS, 'modalities'); + assertEqual(numel(modalities), 1); + + tasks = bids.query(BIDS, 'tasks'); + assertEqual(numel(tasks), 2); + + system('rm -Rf dummyData/copy'); + +end diff --git a/tests/test_checkOptions.m b/tests/test_checkOptions.m index 232f1684..eab892fe 100644 --- a/tests/test_checkOptions.m +++ b/tests/test_checkOptions.m @@ -90,11 +90,12 @@ function test_checkOptionsErrorVoxDim() expectedOptions.space = 'MNI'; expectedOptions.anatReference.type = 'T1w'; - expectedOptions.anatReference.session = 1; + expectedOptions.anatReference.session = []; expectedOptions.skullstrip.threshold = 0.75; - expectedOptions.ignoreFieldmaps = false; + expectedOptions.realign.useUnwarp = true; + expectedOptions.useFieldmaps = true; expectedOptions.taskName = ''; @@ -102,16 +103,13 @@ function test_checkOptionsErrorVoxDim() expectedOptions.contrastList = {}; expectedOptions.model.file = ''; + expectedOptions.model.hrfDerivatives = [0 0]; - expectedOptions.result.Steps = struct( ... - 'Level', '', ... - 'Contrasts', struct( ... - 'Name', '', ... - 'Mask', false, ... - 'MC', 'FWE', ... - 'p', 0.05, ... - 'k', 0, ... - 'NIDM', true)); + expectedOptions.result.Steps = returnDefaultResultsStructure(); + + expectedOptions.parallelize.do = false; + expectedOptions.parallelize.nbWorkers = 3; + expectedOptions.parallelize.killOnExit = true; expectedOptions = orderfields(expectedOptions); diff --git a/tests/test_checkOptionsSource.m b/tests/test_checkOptionsSource.m new file mode 100644 index 00000000..1d0f69a9 --- /dev/null +++ b/tests/test_checkOptionsSource.m @@ -0,0 +1,51 @@ +function test_suite = test_checkOptionsSource %#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_checkOptionsSourceBasic() + + optSource.nbDummies = 0; + optSource = checkOptionsSource(optSource); + + expectedOptionsSource = defaultOptionsSource(); + expectedOptionsSource.nbDummies = 0; + + assertEqual(optSource, expectedOptionsSource); + +end + +function test_checkOptionsSourceDoNotOverwrite() + + optSource.dataType = 666; + optSource.someExtraField = 'test'; + optSource.nbDummies = 42; + + optSource = checkOptionsSource(optSource); + + assertEqual(optSource.dataType, 666); + assertEqual(optSource.someExtraField, 'test'); + assertEqual(optSource.nbDummies, 42); + +end + +function expectedOptionsSource = defaultOptionsSource() + + expectedOptionsSource.sourceDir = ''; + + expectedOptionsSource.dataDir = ''; + + expectedOptionsSource.sequenceToIgnore = {}; + + expectedOptionsSource.dataType = 0; + + expectedOptionsSource.zip = 0; + + expectedOptionsSource.nbDummies = 0; + + expectedOptionsSource.sequenceRmDummies = {}; + +end diff --git a/tests/test_cleanCrash.m b/tests/test_cleanCrash.m new file mode 100644 index 00000000..d1781f88 --- /dev/null +++ b/tests/test_cleanCrash.m @@ -0,0 +1,21 @@ +function test_suite = test_cleanCrash %#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_getcleanCrash() + + system('touch spm_001.png'); + system('touch 001.png'); + + cleanCrash(); + + assertEqual(exist(fullfile(pwd, '001.png'), 'file'), 2); + assertEqual(exist(fullfile(pwd, 'spm_001.png'), 'file'), 0); + + delete(fullfile(pwd, '001.png')); + +end diff --git a/tests/test_copyGraphWindownOutput.m b/tests/test_copyGraphWindownOutput.m new file mode 100644 index 00000000..0958cc5a --- /dev/null +++ b/tests/test_copyGraphWindownOutput.m @@ -0,0 +1,76 @@ +function test_suite = test_copyGraphWindownOutput %#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_copyGraphWindownOutputBasic() + + delete('*.png'); + + pause(1); + + opt.derivativesDir = pwd; + opt.taskName = 'testTask'; + subID = '01'; + imgNb = 1:2; + action = 'testStep'; + + system('touch spm_001.png'); + system('touch spm_002.png'); + + imgNb = copyGraphWindownOutput(opt, subID, action, imgNb); + + assertEqual(imgNb, 3); + + files = spm_select( ... + 'List', ... + fullfile(opt.derivativesDir, ['sub-' subID], 'figures'), ... + ['^' datestr(now, 'yyyymmddHH') '.*_[0-9]_sub-01_task-testTask_testStep.png']); + + assert(~isempty(files)); + assertEqual(size(files, 1), 2); + + pause(1); + + if isOctave() + confirm_recursive_rmdir (true, 'local'); + end + rmdir(fullfile(opt.derivativesDir, ['sub-' subID]), 's'); + +end + +function test_copyGraphWindownOutputWarning() + + delete('*.png'); + + pause(1); + + opt.derivativesDir = pwd; + opt.taskName = 'testTask'; + subID = '01'; + action = 'testStep'; + + system('touch spm_002.png'); + system('touch spm_test_002.png'); + + copyGraphWindownOutput(opt, subID, action, 2); + + if ~isOctave() + assertWarning( ... + @()copyGraphWindownOutput(opt, subID, action, 2), ... + 'copyGraphWindownOutput:tooManyFiles'); + + assertWarning( ... + @()copyGraphWindownOutput(opt, subID, action, 3), ... + 'copyGraphWindownOutput:noFile'); + end + + if isOctave() + confirm_recursive_rmdir (true, 'local'); + end + rmdir(fullfile(opt.derivativesDir, ['sub-' subID]), 's'); + +end diff --git a/tests/test_createAndReturnOnsetFile.m b/tests/test_createAndReturnOnsetFile.m index cba56757..a3189511 100644 --- a/tests/test_createAndReturnOnsetFile.m +++ b/tests/test_createAndReturnOnsetFile.m @@ -32,7 +32,7 @@ function test_createAndReturnOnsetFileBasic() onsetFileName = createAndReturnOnsetFile(opt, subID, tsvFile, funcFWHM); expectedFileName = fullfile(fileparts(mfilename('fullpath')), ... - 'dummyData', 'derivatives', 'SPM12_CPPL', 'sub-01', 'stats', ... + 'dummyData', 'derivatives', 'cpp_spm', 'sub-01', 'stats', ... 'ffx_task-vislocalizer', 'ffx_space-MNI_FWHM-6', ... 'onsets_sub-01_ses-01_task-vislocalizer_events.mat'); diff --git a/tests/test_createDefaultModel.m b/tests/test_createDefaultModel.m new file mode 100644 index 00000000..b10f963b --- /dev/null +++ b/tests/test_createDefaultModel.m @@ -0,0 +1,33 @@ +function test_suite = test_createDefaultModel %#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_createDefaultModelBasic() + + opt.taskName = 'vislocalizer'; + opt.derivativesDir = fullfile(fileparts(mfilename('fullpath')), 'dummyData'); + + opt = checkOptions(opt); + + [~, opt, BIDS] = getData(opt); + + createDefaultModel(BIDS, opt); + + % make sure the file was created where expected + expectedFileName = fullfile(pwd, 'models', 'model-defaultVislocalizer_smdl.json'); + assertEqual(exist(expectedFileName, 'file'), 2); + + % check it has the right content + content = spm_jsonread(expectedFileName); + expectedContent = spm_jsonread(fullfile(fileparts(mfilename('fullpath')), ... + 'dummyData', ... + 'models', ... + 'model-default_smdl.json')); + + assertEqual(content.Steps, expectedContent.Steps); + +end diff --git a/tests/test_getAnatFilename.m b/tests/test_getAnatFilename.m index 2433b98a..c888f724 100644 --- a/tests/test_getAnatFilename.m +++ b/tests/test_getAnatFilename.m @@ -6,6 +6,12 @@ initTestSuite; end +% TODO +% add tests to check: +% - errors when the requested file is not in the correct session +% - that the fucntion is smart enough to find an anat even when user has not +% specified a session + function test_getAnatFilenameBasic() subID = '01'; @@ -24,7 +30,7 @@ function test_getAnatFilenameBasic() expectedFileName = 'sub-01_ses-01_T1w.nii'; expectedAnatDataDir = fullfile(fileparts(mfilename('fullpath')), ... - 'dummyData', 'derivatives', 'SPM12_CPPL', ... + 'dummyData', 'derivatives', 'cpp_spm', ... 'sub-01', 'ses-01', 'anat'); assertEqual(anatDataDir, expectedAnatDataDir); diff --git a/tests/test_getBoldFilename.m b/tests/test_getBoldFilename.m index 60f9d6c1..be89963e 100644 --- a/tests/test_getBoldFilename.m +++ b/tests/test_getBoldFilename.m @@ -31,7 +31,7 @@ function test_getBoldFilenameBasic() expectedFileName = 'sub-01_ses-01_task-vislocalizer_bold.nii'; expectedSubFuncDataDir = fullfile(fileparts(mfilename('fullpath')), ... - 'dummyData', 'derivatives', 'SPM12_CPPL', ... + 'dummyData', 'derivatives', 'cpp_spm', ... 'sub-01', 'ses-01', 'func'); assertEqual(expectedSubFuncDataDir, subFuncDataDir); diff --git a/tests/test_getBoldFilenameForFFX.m b/tests/test_getBoldFilenameForFFX.m index f78ec52d..bfbc0c25 100644 --- a/tests/test_getBoldFilenameForFFX.m +++ b/tests/test_getBoldFilenameForFFX.m @@ -24,11 +24,11 @@ function test_getBoldFilenameForFFXBasic() [boldFileName, prefix] = getBoldFilenameForFFX(BIDS, opt, subID, funcFWHM, iSes, iRun); expectedFileName = fullfile(fileparts(mfilename('fullpath')), ... - 'dummyData', 'derivatives', 'SPM12_CPPL', 'sub-01', ... + 'dummyData', 'derivatives', 'cpp_spm', 'sub-01', ... 'ses-01', 'func', ... - 's6wsub-01_ses-01_task-vislocalizer_bold.nii'); + 's6wusub-01_ses-01_task-vislocalizer_bold.nii'); - assertEqual('s6w', prefix); + assertEqual('s6wu', prefix); assertEqual(expectedFileName, boldFileName); end @@ -52,7 +52,7 @@ function test_getBoldFilenameForFFXNativeSpace() [boldFileName, prefix] = getBoldFilenameForFFX(BIDS, opt, subID, funcFWHM, iSes, iRun); expectedFileName = fullfile(fileparts(mfilename('fullpath')), ... - 'dummyData', 'derivatives', 'SPM12_CPPL', 'sub-01', ... + 'dummyData', 'derivatives', 'cpp_spm', 'sub-01', ... 'ses-01', 'func', ... 's6usub-01_ses-01_task-vislocalizer_bold.nii'); diff --git a/tests/test_getFFXdir.m b/tests/test_getFFXdir.m index 5fb88422..ba269464 100644 --- a/tests/test_getFFXdir.m +++ b/tests/test_getFFXdir.m @@ -18,7 +18,7 @@ function test_getFFXdirBasic() opt = checkOptions(opt); expectedOutput = fullfile(fileparts(mfilename('fullpath')), 'dummyData', 'derivatives', ... - 'SPM12_CPPL', 'sub-01', 'stats', 'ffx_task-funcLocalizer', ... + 'cpp_spm', 'sub-01', 'stats', 'ffx_task-funcLocalizer', ... 'ffx_space-MNI_FWHM-0'); ffxDir = getFFXdir(subID, funcFWFM, opt); @@ -40,7 +40,7 @@ function test_getFFXdirMvpa() opt = checkOptions(opt); expectedOutput = fullfile(fileparts(mfilename('fullpath')), 'dummyData', 'derivatives', ... - 'SPM12_CPPL', 'sub-02', 'stats', 'ffx_task-nBack', ... + 'cpp_spm', 'sub-02', 'stats', 'ffx_task-nBack', ... 'ffx_space-individual_FWHM-6'); ffxDir = getFFXdir(subID, funcFWFM, opt); diff --git a/tests/test_getInfo.m b/tests/test_getInfo.m index c94a7f6b..837a276e 100644 --- a/tests/test_getInfo.m +++ b/tests/test_getInfo.m @@ -12,7 +12,7 @@ function test_getInfoBasic() % write tests for when no session or only one run opt.derivativesDir = fullfile(fileparts(mfilename('fullpath')), ... - 'dummyData', 'derivatives', 'SPM12_CPPL'); + 'dummyData', 'derivatives', 'cpp_spm'); opt.groups = {''}; opt.subjects = {[], []}; @@ -51,7 +51,7 @@ function test_getInfoBasic() [~, opt, BIDS] = getData(opt); filename = getInfo(BIDS, subID, opt, info, session, run, 'bold'); FileName = fullfile(fileparts(mfilename('fullpath')), 'dummyData', ... - 'derivatives', 'SPM12_CPPL', ... + 'derivatives', 'cpp_spm', ... ['sub-' subID], ['ses-' session], 'func', ... ['sub-' subID, ... '_ses-' session, ... diff --git a/tests/test_getMeanFuncFilename.m b/tests/test_getMeanFuncFilename.m index 29e78916..d57159ad 100644 --- a/tests/test_getMeanFuncFilename.m +++ b/tests/test_getMeanFuncFilename.m @@ -24,7 +24,7 @@ function test_getMeanFuncFilenameBasic() expectedMeanImage = 'meanusub-01_ses-01_task-vislocalizer_bold.nii'; expectedmeanFuncDir = fullfile(fileparts(mfilename('fullpath')), ... - 'dummyData', 'derivatives', 'SPM12_CPPL', ... + 'dummyData', 'derivatives', 'cpp_spm', ... 'sub-01', 'ses-01', 'func'); assertEqual(meanFuncDir, expectedmeanFuncDir); diff --git a/tests/test_getPrefix.m b/tests/test_getPrefix.m index b934a10d..dfbdeccb 100644 --- a/tests/test_getPrefix.m +++ b/tests/test_getPrefix.m @@ -8,86 +8,171 @@ function test_getPrefixSTC() - step = 'STC'; + step = 'realign'; funcFWHM = 6; opt.metadata.SliceTiming = 1:0.2:1.8; opt.sliceOrder = 1:10; - expectedPrefxOutput = ''; + [prefix, motionRegressorPrefix] = getPrefix(step, opt); + + expectedPrefixOutput = spm_get_defaults('slicetiming.prefix'); expectedMotionRegressorPrefix = ''; - [prefix, motionRegressorPrefix] = getPrefix(step, opt, funcFWHM); + assertEqual(prefix, expectedPrefixOutput); + assertEqual(motionRegressorPrefix, expectedMotionRegressorPrefix); + +end + +function test_getPrefixNoSTC() + + step = 'realign'; + opt.metadata = []; + opt.sliceOrder = []; + + prefix = getPrefix(step, opt); - assertEqual(expectedPrefxOutput, prefix); - assertEqual(expectedMotionRegressorPrefix, motionRegressorPrefix); + expectedPrefixOutput = ''; + expectedMotionRegressorPrefix = ''; + + assertEqual(prefix, expectedPrefixOutput); end -function test_getPrefixPreprocess() +function test_getPrefixMean() - step = 'preprocess'; - funcFWHM = 6; + step = 'mean'; + opt.metadata = []; + opt.sliceOrder = []; + opt.realign.useUnwarp = true; + opt.space = 'MNI'; + + prefix = getPrefix(step, opt); + + expectedPrefixOutput = ['mean' spm_get_defaults('unwarp.write.prefix')]; + + assertEqual(prefix, expectedPrefixOutput); + + %% no unwarp + + step = 'mean'; opt.metadata.SliceTiming = 1:0.2:1.8; opt.sliceOrder = 1:10; + opt.realign.useUnwarp = false; + opt.space = 'MNI'; - expectedPrefxOutput = spm_get_defaults('slicetiming.prefix'); - expectedMotionRegressorPrefix = ''; + prefix = getPrefix(step, opt); - [prefix, motionRegressorPrefix] = getPrefix(step, opt, funcFWHM); + expectedPrefixOutput = 'meana'; - assertEqual(expectedPrefxOutput, prefix); - assertEqual(expectedMotionRegressorPrefix, motionRegressorPrefix); + assertEqual(prefix, expectedPrefixOutput); end -function test_getPrefixPreprocessNoSTC() +function test_getPrefixNormalise() - step = 'preprocess'; - funcFWHM = 6; + step = 'normalise'; opt.metadata = []; opt.sliceOrder = []; + opt.realign.useUnwarp = true; + opt.space = 'MNI'; - expectedPrefxOutput = ''; - expectedMotionRegressorPrefix = ''; + prefix = getPrefix(step, opt); - [prefix, motionRegressorPrefix] = getPrefix(step, opt, funcFWHM); + expectedPrefixOutput = spm_get_defaults('unwarp.write.prefix'); + + assertEqual(prefix, expectedPrefixOutput); + + %% no unwarp + + step = 'normalise'; + opt.metadata = []; + opt.sliceOrder = []; + opt.realign.useUnwarp = false; + opt.space = 'MNI'; + + prefix = getPrefix(step, opt); - assertEqual(expectedPrefxOutput, prefix); - assertEqual(expectedMotionRegressorPrefix, motionRegressorPrefix); + expectedPrefixOutput = ''; + + assertEqual(prefix, expectedPrefixOutput); end -function test_getPrefixSmoothing() +function test_getPrefixFuncQA() - step = 'smoothing'; - funcFWHM = 6; + step = 'funcQA'; opt.metadata = []; opt.sliceOrder = []; + opt.realign.useUnwarp = true; + opt.space = 'MNI'; - expectedPrefxOutput = spm_get_defaults('normalise.write.prefix'); - expectedMotionRegressorPrefix = ''; + prefix = getPrefix(step, opt); - [prefix, motionRegressorPrefix] = getPrefix(step, opt, funcFWHM); + expectedPrefixOutput = spm_get_defaults('unwarp.write.prefix'); - assertEqual(expectedPrefxOutput, prefix); - assertEqual(expectedMotionRegressorPrefix, motionRegressorPrefix); + assertEqual(prefix, expectedPrefixOutput); + + %% no unwarp + + step = 'funcQA'; + opt.metadata = []; + opt.sliceOrder = []; + opt.realign.useUnwarp = false; + opt.space = 'MNI'; + + prefix = getPrefix(step, opt); + + expectedPrefixOutput = spm_get_defaults('realign.write.prefix'); + + assertEqual(prefix, expectedPrefixOutput); end -function test_getPrefixSmoothingIndividual() +function test_getPrefixSmooth() - step = 'smoothing_space-individual'; - funcFWHM = 6; + step = 'smooth'; opt.metadata = []; opt.sliceOrder = []; + opt.realign.useUnwarp = true; + opt.space = 'MNI'; - expectedPrefxOutput = spm_get_defaults('unwarp.write.prefix'); - expectedMotionRegressorPrefix = ''; + prefix = getPrefix(step, opt); - [prefix, motionRegressorPrefix] = getPrefix(step, opt, funcFWHM); + expectedPrefixOutput = [ ... + spm_get_defaults('normalise.write.prefix'), ... + spm_get_defaults('unwarp.write.prefix')]; + + assertEqual(prefix, expectedPrefixOutput); + + %% native space + opt.realign.useUnwarp = true; + opt.space = 'individual'; + + prefix = getPrefix(step, opt); + + expectedPrefixOutput = spm_get_defaults('unwarp.write.prefix'); + + assertEqual(prefix, expectedPrefixOutput); + + %% native space no unwarp + opt.realign.useUnwarp = false; + opt.space = 'individual'; + + prefix = getPrefix(step, opt); + + expectedPrefixOutput = spm_get_defaults('realign.write.prefix'); + + assertEqual(prefix, expectedPrefixOutput); + + %% MNI space no unwarp + opt.realign.useUnwarp = false; + opt.space = 'MNI'; - assertEqual(expectedPrefxOutput, prefix); - assertEqual(expectedMotionRegressorPrefix, motionRegressorPrefix); + prefix = getPrefix(step, opt); + + expectedPrefixOutput = spm_get_defaults('normalise.write.prefix'); + + assertEqual(prefix, expectedPrefixOutput); end @@ -97,37 +182,69 @@ function test_getPrefixFFX() funcFWHM = 6; opt.metadata = []; opt.sliceOrder = []; + opt.realign.useUnwarp = true; + opt.space = 'MNI'; - expectedPrefxOutput = [ ... - spm_get_defaults('smooth.prefix'), ... - num2str(funcFWHM), ... - spm_get_defaults('normalise.write.prefix')]; + [prefix, motionRegressorPrefix] = getPrefix(step, opt, funcFWHM); + + expectedPrefixOutput = sprintf('%s%i%s%s', ... + spm_get_defaults('smooth.prefix'), ... + funcFWHM, ... + spm_get_defaults('normalise.write.prefix'), ... + spm_get_defaults('unwarp.write.prefix')); expectedMotionRegressorPrefix = ''; + assertEqual(prefix, expectedPrefixOutput); + assertEqual(motionRegressorPrefix, expectedMotionRegressorPrefix); + + %% native space + opt.realign.useUnwarp = true; + opt.space = 'individual'; + [prefix, motionRegressorPrefix] = getPrefix(step, opt, funcFWHM); - assertEqual(expectedPrefxOutput, prefix); - assertEqual(expectedMotionRegressorPrefix, motionRegressorPrefix); + expectedPrefixOutput = sprintf('%s%i%s', ... + spm_get_defaults('smooth.prefix'), ... + funcFWHM, ... + spm_get_defaults('unwarp.write.prefix')); + expectedMotionRegressorPrefix = ''; -end + assertEqual(prefix, expectedPrefixOutput); + assertEqual(motionRegressorPrefix, expectedMotionRegressorPrefix); -function test_getPrefixFfxIndividual() + %% STC, native space no unwarp + opt.realign.useUnwarp = false; + opt.space = 'individual'; + opt.metadata.SliceTiming = 1:0.2:1.8; + opt.sliceOrder = 1:10; - step = 'FFX_space-individual'; - funcFWHM = 6; + [prefix, motionRegressorPrefix] = getPrefix(step, opt, funcFWHM); + + expectedPrefixOutput = sprintf('%s%i%s%s', ... + spm_get_defaults('smooth.prefix'), ... + funcFWHM, ... + spm_get_defaults('realign.write.prefix'), ... + spm_get_defaults('slicetiming.prefix')); + expectedMotionRegressorPrefix = spm_get_defaults('slicetiming.prefix'); + + assertEqual(prefix, expectedPrefixOutput); + assertEqual(motionRegressorPrefix, expectedMotionRegressorPrefix); + + %% MNI space no unwarp + opt.realign.useUnwarp = false; + opt.space = 'MNI'; opt.metadata = []; opt.sliceOrder = []; - expectedPrefxOutput = [ ... - spm_get_defaults('smooth.prefix'), ... - num2str(funcFWHM), ... - spm_get_defaults('unwarp.write.prefix')]; - expectedMotionRegressorPrefix = ''; + [prefix] = getPrefix(step, opt, funcFWHM); - [prefix, motionRegressorPrefix] = getPrefix(step, opt, funcFWHM); + expectedPrefixOutput = sprintf('%s%i%s%s', ... + spm_get_defaults('smooth.prefix'), ... + funcFWHM, ... + spm_get_defaults('normalise.write.prefix')); + expectedMotionRegressorPrefix = spm_get_defaults('slicetiming.prefix'); - assertEqual(expectedPrefxOutput, prefix); - assertEqual(expectedMotionRegressorPrefix, motionRegressorPrefix); + assertEqual(prefix, expectedPrefixOutput); end diff --git a/tests/test_getRFXdir.m b/tests/test_getRFXdir.m index d5014cb2..3473ae94 100644 --- a/tests/test_getRFXdir.m +++ b/tests/test_getRFXdir.m @@ -16,19 +16,16 @@ function test_getRFXdirBasic() opt = setDerivativesDir(opt); - contrastName = 'stim_gt_baseline'; - - rfxDir = getRFXdir(opt, funcFWHM, conFWHM, contrastName); + rfxDir = getRFXdir(opt, funcFWHM, conFWHM); expectedOutput = fullfile( ... fileparts(mfilename('fullpath')), ... 'dummyData', ... 'derivatives', ... - 'SPM12_CPPL', ... + 'cpp_spm', ... 'group', ... 'rfx_task-funcLocalizer', ... - 'rfx_funcFWHM-0_conFWHM-0', ... - 'stim_gt_baseline'); + 'rfx_funcFWHM-0_conFWHM-0'); assertEqual(exist(expectedOutput, 'dir'), 7); diff --git a/tests/test_getRealignParamFile.m b/tests/test_getRealignParamFile.m index ba16fc1b..e4e66f66 100644 --- a/tests/test_getRealignParamFile.m +++ b/tests/test_getRealignParamFile.m @@ -24,7 +24,7 @@ function test_getRealignParamFileBasic() realignParamFile = getRealignParamFile(fullfile(subFuncDataDir, boldFileName)); expectedFileName = fullfile(fileparts(mfilename('fullpath')), ... - 'dummyData', 'derivatives', 'SPM12_CPPL', 'sub-01', ... + 'dummyData', 'derivatives', 'cpp_spm', 'sub-01', ... 'ses-01', 'func', ... 'rp_sub-01_ses-01_task-vislocalizer_bold.txt'); @@ -41,7 +41,7 @@ function test_getRealignParamFileNativeSpace() opt.taskName = 'vislocalizer'; opt.derivativesDir = fullfile(fileparts(mfilename('fullpath')), 'dummyData'); opt.subjects = {subID}; - opt.space = 'T1w'; + opt.space = 'individual'; opt = checkOptions(opt); @@ -51,7 +51,7 @@ function test_getRealignParamFileNativeSpace() realignParamFile = getRealignParamFile(fullfile(subFuncDataDir, boldFileName)); expectedFileName = fullfile(fileparts(mfilename('fullpath')), ... - 'dummyData', 'derivatives', 'SPM12_CPPL', 'sub-01', ... + 'dummyData', 'derivatives', 'cpp_spm', 'sub-01', ... 'ses-01', 'func', ... 'rp_sub-01_ses-01_task-vislocalizer_bold.txt'); @@ -69,7 +69,7 @@ function test_getRealignParamFileFFX() opt.taskName = 'vislocalizer'; opt.derivativesDir = fullfile(fileparts(mfilename('fullpath')), 'dummyData'); opt.subjects = {subID}; - opt.space = 'T1w'; + opt.space = 'MNI'; opt = checkOptions(opt); @@ -80,7 +80,7 @@ function test_getRealignParamFileFFX() realignParamFile = getRealignParamFile(fullfile(subFuncDataDir, [boldFileName, ext]), prefix); expectedFileName = fullfile(fileparts(mfilename('fullpath')), ... - 'dummyData', 'derivatives', 'SPM12_CPPL', 'sub-01', ... + 'dummyData', 'derivatives', 'cpp_spm', 'sub-01', ... 'ses-01', 'func', ... 'rp_sub-01_ses-01_task-vislocalizer_bold.txt'); diff --git a/tests/test_loadAndCheckOptions.m b/tests/test_loadAndCheckOptions.m index ca8c4673..7da32e0a 100644 --- a/tests/test_loadAndCheckOptions.m +++ b/tests/test_loadAndCheckOptions.m @@ -25,6 +25,21 @@ function test_loadAndCheckOptionsBasic() end +function test_loadAndCheckOptionsStructure() + + % create dummy json file + opt.taskName = 'vismotion'; + + % makes sure that it is picked up by default + opt = loadAndCheckOptions(opt); + + expectedOptions = defaultOptions(); + expectedOptions.taskName = 'vismotion'; + + assertEqual(opt, expectedOptions); + +end + function test_loadAndCheckOptionsFromFile() delete('*.json'); @@ -32,6 +47,9 @@ function test_loadAndCheckOptionsFromFile() % create dummy json file jsonContent.taskName = 'vismotion'; jsonContent.space = 'individual'; + jsonContent.groups = {''}; + jsonContent.subjects = {[]}; + filename = 'options_task-vismotion_space-T1w.json'; spm_jsonwrite(filename, jsonContent); @@ -104,11 +122,12 @@ function test_loadAndCheckOptionsFromSeveralFiles() expectedOptions.space = 'MNI'; expectedOptions.anatReference.type = 'T1w'; - expectedOptions.anatReference.session = 1; + expectedOptions.anatReference.session = []; expectedOptions.skullstrip.threshold = 0.75; - expectedOptions.ignoreFieldmaps = false; + expectedOptions.realign.useUnwarp = true; + expectedOptions.useFieldmaps = true; expectedOptions.taskName = ''; @@ -116,16 +135,13 @@ function test_loadAndCheckOptionsFromSeveralFiles() expectedOptions.contrastList = {}; expectedOptions.model.file = ''; + expectedOptions.model.hrfDerivatives = [0 0]; + + expectedOptions.result.Steps = returnDefaultResultsStructure(); - expectedOptions.result.Steps = struct( ... - 'Level', '', ... - 'Contrasts', struct( ... - 'Name', '', ... - 'Mask', false, ... - 'MC', 'FWE', ... - 'p', 0.05, ... - 'k', 0, ... - 'NIDM', true)); + expectedOptions.parallelize.do = false; + expectedOptions.parallelize.nbWorkers = 3; + expectedOptions.parallelize.killOnExit = true; expectedOptions = orderfields(expectedOptions); diff --git a/tests/test_manageWorkersPool.m b/tests/test_manageWorkersPool.m new file mode 100644 index 00000000..9754a9ea --- /dev/null +++ b/tests/test_manageWorkersPool.m @@ -0,0 +1,69 @@ +function test_suite = test_manageWorkersPool %#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_manageWorkersPoolBasic() + + opt.parallelize.do = true; + opt.parallelize.nbWorkers = 3; + opt.parallelize.killOnExit = true; + + matlabVer = version('-release'); + + manageWorkersPool('close', opt); + + manageWorkersPool('open', opt); + + if ~isOctave() + if str2double(matlabVer(1:4)) > 2013 + + pool = gcp('nocreate'); + nbWorkers = pool.NumWorkers; + + else + nbWorkers = matlabpool('size'); %#ok + + end + + manageWorkersPool('close', opt); + + assertEqual(nbWorkers, opt.parallelize.nbWorkers); + + end + +end + +function test_manageWorkersPoolNoParallel() + + opt.parallelize.do = false; + opt.parallelize.nbWorkers = 3; + opt.parallelize.killOnExit = true; + + matlabVer = version('-release'); + + manageWorkersPool('close', opt); + + manageWorkersPool('open', opt); + + if ~isOctave() + if str2double(matlabVer(1:4)) > 2013 + + pool = gcp('nocreate'); + nbWorkers = pool.NumWorkers; + + else + nbWorkers = matlabpool('size'); %#ok + + end + + manageWorkersPool('close', opt); + + assertEqual(nbWorkers, 1); + + end + +end diff --git a/tests/test_returnDefaultResultsStructure.m b/tests/test_returnDefaultResultsStructure.m new file mode 100644 index 00000000..75cd688c --- /dev/null +++ b/tests/test_returnDefaultResultsStructure.m @@ -0,0 +1,35 @@ +% (C) Copyright 2020 CPP BIDS SPM-pipeline developers + +function test_suite = test_returnDefaultResultsStructure %#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_returnDefaultResultsStructureBasic() + + results = returnDefaultResultsStructure(); + + expected.Level = ''; + + expected.Contrasts.Name = ''; + expected.Contrasts.useMask = false(); + expected.Contrasts.MC = 'FWE'; + expected.Contrasts.p = 0.05; + expected.Contrasts.k = 0; + + expected.Output.png = false(); + expected.Output.csv = false(); + expected.Output.thresh_spm = false(); + expected.Output.binary = false(); + expected.Output.montage.do = false(); + expected.Output.montage.slices = []; + expected.Output.montage.orientation = 'axial'; + expected.Output.montage.background = fullfile(spm('dir'), 'canonical', 'avg152T1.nii,1'); + expected.Output.NIDM_results = false(); + + assertEqual(results, expected); + +end diff --git a/tests/test_returnEmptyModel.m b/tests/test_returnEmptyModel.m new file mode 100644 index 00000000..343ee25b --- /dev/null +++ b/tests/test_returnEmptyModel.m @@ -0,0 +1,22 @@ +% (C) Copyright 2019 CPP BIDS SPM-pipeline developers + +function test_suite = test_returnEmptyModel %#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_returnEmptyModelBasic() + + content = returnEmptyModel(); + + expectedContent = spm_jsonread(fullfile(fileparts(mfilename('fullpath')), ... + 'dummyData', ... + 'models', ... + 'model-empty_smdl.json')); + + assertEqual(content, expectedContent); + +end diff --git a/tests/test_setBatch3Dto4D.m b/tests/test_setBatch3Dto4D.m new file mode 100644 index 00000000..6a169d3e --- /dev/null +++ b/tests/test_setBatch3Dto4D.m @@ -0,0 +1,27 @@ +function test_suite = test_setBatch3Dto4D %#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_setBatch3Dto4DBasic() + + volumesList = [ ... + fullfile(pwd, 'sub-01_task-rest_bold.nii,1'); ... + fullfile(pwd, 'sub-01_task-rest_bold.nii,2')]; + + RT = 2; + + matlabbatch = []; + matlabbatch = setBatch3Dto4D(matlabbatch, volumesList, RT); + + expectedBatch{1}.spm.util.cat.vols = volumesList; + expectedBatch{1}.spm.util.cat.name = fullfile(pwd, 'sub-01_task-rest_bold_4D.nii'); + expectedBatch{1}.spm.util.cat.dtype = 0; + expectedBatch{1}.spm.util.cat.RT = 2; + + assertEqual(matlabbatch, expectedBatch); + +end diff --git a/tests/test_setBatchComputeVDM.m b/tests/test_setBatchComputeVDM.m new file mode 100644 index 00000000..64774ded --- /dev/null +++ b/tests/test_setBatchComputeVDM.m @@ -0,0 +1,70 @@ +function test_suite = test_setBatchComputeVDM %#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_setBatchComputeVDMBasic() + + fmapType = 'phasediff'; + refImage = fullfile(pwd, 'mean_sub-01-task-rest_bold.nii'); + + matlabbatch = []; + matlabbatch = setBatchComputeVDM(matlabbatch, fmapType, refImage); + + expectedBatch = returnExpectedBatch(refImage); + + assertEqual(matlabbatch, expectedBatch); + +end + +function expectedBatch = returnExpectedBatch(refImage) + + expectedBatch = []; + + expectedBatch{end + 1}.spm.tools.fieldmap.calculatevdm.subj(1).data.presubphasemag.phase = ''; + expectedBatch{end}.spm.tools.fieldmap.calculatevdm.subj(1).data.presubphasemag.magnitude = ''; + + UF = struct( ... + 'method', 'Mark3D', ... + 'fwhm', 10, ... + 'pad', 0, ... + 'ws', 1); + + MF = struct( ... + 'fwhm', 5, ... + 'nerode', 2, ... + 'ndilate', 4, ... + 'thresh', 0.5, ... + 'reg', 0.02); + + defaultsval = struct( ... + 'et', [NaN NaN], ... + 'maskbrain', 1, ... + 'blipdir', 1, ... + 'tert', '', ... + 'epifm', 0, ... % can be changed + 'ajm', 0, ... + 'uflags', UF, ... + 'mflags', MF); + + defaultsval.mflags.template = { spm_select( ... + 'FPList', ... + fullfile( ... + spm('dir'), ... + 'toolbox', ... + 'FieldMap'), ... + '^T1.nii$')}; + + expectedBatch{end}.spm.tools.fieldmap.calculatevdm.subj(1).defaults.defaultsval = defaultsval; + + expectedBatch{end}.spm.tools.fieldmap.calculatevdm.subj(1).session.epi = {refImage}; + expectedBatch{end}.spm.tools.fieldmap.calculatevdm.subj(1).matchvdm = 1; + expectedBatch{end}.spm.tools.fieldmap.calculatevdm.subj(1).sessname = 'vdm-'; + expectedBatch{end}.spm.tools.fieldmap.calculatevdm.subj(1).writeunwarped = 1; + expectedBatch{end}.spm.tools.fieldmap.calculatevdm.subj(1).anat = ''; + expectedBatch{end}.spm.tools.fieldmap.calculatevdm.subj(1).matchanat = 0; + +end diff --git a/tests/test_setBatchCoregistrationFuncToAnat.m b/tests/test_setBatchCoregistrationFuncToAnat.m index 138029a1..fe07cb59 100644 --- a/tests/test_setBatchCoregistrationFuncToAnat.m +++ b/tests/test_setBatchCoregistrationFuncToAnat.m @@ -24,12 +24,12 @@ function test_setBatchCoregistrationFuncToAnatBasic() opt.orderBatches.realign = 2; matlabbatch = {}; - matlabbatch = setBatchCoregistrationFuncToAnat(matlabbatch, BIDS, subID, opt); + matlabbatch = setBatchCoregistrationFuncToAnat(matlabbatch, BIDS, opt, subID); nbRuns = 4; - meanImageToUse = 'rmean'; - otherImageToUse = 'cfiles'; + meanImageToUse = 'meanuwr'; + otherImageToUse = 'uwrfiles'; expectedBatch = returnExpectedBatch(nbRuns, meanImageToUse, otherImageToUse); assertEqual( ... @@ -44,14 +44,14 @@ function test_setBatchCoregistrationFuncToAnatBasic() end -function test_setBatchCoregistrationFuncToAnatNative() +function test_setBatchCoregistrationFuncToAnatNoUnwarp() % necessarry to deal with SPM module dependencies spm_jobman('initcfg'); opt.derivativesDir = fullfile(fileparts(mfilename('fullpath')), 'dummyData'); opt.taskName = 'vismotion'; - opt.space = 'individual'; + opt.realign.useUnwarp = false; opt = checkOptions(opt); @@ -63,12 +63,12 @@ function test_setBatchCoregistrationFuncToAnatNative() opt.orderBatches.realign = 2; matlabbatch = {}; - matlabbatch = setBatchCoregistrationFuncToAnat(matlabbatch, BIDS, subID, opt); + matlabbatch = setBatchCoregistrationFuncToAnat(matlabbatch, BIDS, opt, subID); nbRuns = 4; - meanImageToUse = 'meanuwr'; - otherImageToUse = 'uwrfiles'; + meanImageToUse = 'rmean'; + otherImageToUse = 'cfiles'; expectedBatch = returnExpectedBatch(nbRuns, meanImageToUse, otherImageToUse); assertEqual( ... diff --git a/tests/test_setBatchFactorialDesign.m b/tests/test_setBatchFactorialDesign.m new file mode 100644 index 00000000..f8b75325 --- /dev/null +++ b/tests/test_setBatchFactorialDesign.m @@ -0,0 +1,28 @@ +function test_suite = test_setBatchFactorialDesign %#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_setBatchFactorialDesignBasic() + + funcFWHM = 6; + conFWHM = 6; + + opt.subjects = {'01' '02'}; + opt.derivativesDir = fullfile(fileparts(mfilename('fullpath')), 'dummyData'); + opt.taskName = 'vismotion'; + opt.model.file = fullfile(fileparts(mfilename('fullpath')), ... + 'dummyData', 'models', 'model-visMotionLoc_smdl.json'); + + opt = checkOptions(opt); + + matlabbatch = []; + matlabbatch = setBatchFactorialDesign(matlabbatch, opt, funcFWHM, conFWHM); + + % TODO + % add assert + +end diff --git a/tests/test_setBatchGZip.m b/tests/test_setBatchGZip.m new file mode 100644 index 00000000..2651e069 --- /dev/null +++ b/tests/test_setBatchGZip.m @@ -0,0 +1,22 @@ +function test_suite = test_setBatchGZip %#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_setBatchGZipBasic() + + unzippedNiifiles = 'sub-01_ses-01_T1w.nii'; + + matlabbatch = []; + matlabbatch = setBatchGZip(matlabbatch, unzippedNiifiles); + + expectedBatch{1}.cfg_basicio.file_dir.file_ops.cfg_gzip_files.files = 'sub-01_ses-01_T1w.nii'; + expectedBatch{1}.cfg_basicio.file_dir.file_ops.cfg_gzip_files.outdir = {''}; + expectedBatch{1}.cfg_basicio.file_dir.file_ops.cfg_gzip_files.keep = false(); + + assertEqual(matlabbatch, expectedBatch); + +end diff --git a/tests/test_setBatchImageCalculation.m b/tests/test_setBatchImageCalculation.m new file mode 100644 index 00000000..235d27a4 --- /dev/null +++ b/tests/test_setBatchImageCalculation.m @@ -0,0 +1,26 @@ +function test_suite = test_setBatchImageCalculation %#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_setBatchImageCalculationBasic() + + input = {'sub-01_ses-01_T1w.nii'}; + output = 'sub-01_ses-01_T1w_thres.nii'; + outDir = pwd; + expression = 'i1 > 10'; + + matlabbatch = []; + matlabbatch = setBatchImageCalculation(matlabbatch, input, output, outDir, expression); + + expectedBatch{1}.spm.util.imcalc.input{1} = 'sub-01_ses-01_T1w.nii'; + expectedBatch{end}.spm.util.imcalc.output = 'sub-01_ses-01_T1w_thres.nii'; + expectedBatch{end}.spm.util.imcalc.outdir{1} = pwd; + expectedBatch{end}.spm.util.imcalc.expression = 'i1 > 10'; + + assertEqual(matlabbatch, expectedBatch); + +end diff --git a/tests/test_setBatchMeanAnatAndMask.m b/tests/test_setBatchMeanAnatAndMask.m new file mode 100644 index 00000000..780567c4 --- /dev/null +++ b/tests/test_setBatchMeanAnatAndMask.m @@ -0,0 +1,59 @@ +function test_suite = test_setBatchMeanAnatAndMask %#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_setBatchMeanAnatAndMaskBasic() + + funcFWHM = 6; + + opt.subjects = {'01', '02'}; + opt.taskName = 'vismotion'; + opt.space = 'MNI'; + opt.derivativesDir = fullfile(fileparts(mfilename('fullpath')), ... + 'dummyData', ... + 'derivatives', ... + 'cpp_spm'); + + opt = checkOptions(opt); + + matlabbatch = []; + matlabbatch = setBatchMeanAnatAndMask(matlabbatch, opt, funcFWHM, pwd); + + % + expectedBatch{1}.spm.util.imcalc.input{1, 1} = fullfile(opt.derivativesDir, ... + 'sub-01', ... + 'ses-01', ... + 'anat', ... + 'wmsub-01_ses-01_T1w.nii'); + expectedBatch{1}.spm.util.imcalc.input{2, 1} = fullfile(opt.derivativesDir, ... + 'sub-02', ... + 'ses-01', ... + 'anat', ... + 'wmsub-02_ses-01_T1w.nii'); + + expectedBatch{1}.spm.util.imcalc.output = 'meanAnat.nii'; + expectedBatch{1}.spm.util.imcalc.outdir{1} = pwd; + expectedBatch{1}.spm.util.imcalc.expression = '(i1+i2)/2'; + + % + expectedBatch{2}.spm.util.imcalc.input{1, 1} = fullfile(opt.derivativesDir, 'sub-01', ... + 'stats', ... + 'ffx_task-vismotion', ... + 'ffx_space-MNI_FWHM-6', 'mask.nii'); + expectedBatch{2}.spm.util.imcalc.input{2, 1} = fullfile(opt.derivativesDir, ... + 'sub-02', ... + 'stats', ... + 'ffx_task-vismotion', ... + 'ffx_space-MNI_FWHM-6', 'mask.nii'); + + expectedBatch{2}.spm.util.imcalc.output = 'meanMask.nii'; + expectedBatch{2}.spm.util.imcalc.outdir{1} = pwd; + expectedBatch{2}.spm.util.imcalc.expression = '(i1+i2)>0.75*2'; + + assertEqual(matlabbatch, expectedBatch); + +end diff --git a/tests/test_setBatchNormalizationSpatialPrepro.m b/tests/test_setBatchNormalizationSpatialPrepro.m index 2d69117c..7a3199a5 100644 --- a/tests/test_setBatchNormalizationSpatialPrepro.m +++ b/tests/test_setBatchNormalizationSpatialPrepro.m @@ -16,7 +16,7 @@ function test_setBatchNormalizationSpatialPreproBasic() matlabbatch = {}; voxDim = [3 3 3]; - matlabbatch = setBatchNormalizationSpatialPrepro(matlabbatch, voxDim, opt); + matlabbatch = setBatchNormalizationSpatialPrepro(matlabbatch, opt, voxDim); expectedBatch = returnExpectedBatch(voxDim); diff --git a/tests/test_setBatchRealign.m b/tests/test_setBatchRealign.m index bed3aa26..5be5d90f 100644 --- a/tests/test_setBatchRealign.m +++ b/tests/test_setBatchRealign.m @@ -25,10 +25,10 @@ function test_setBatchRealignBasic() subID = '01'; matlabbatch = []; - matlabbatch = setBatchRealign(matlabbatch, BIDS, subID, opt); + matlabbatch = setBatchRealign(matlabbatch, BIDS, opt, subID); - expectedBatch{1}.spm.spatial.realign.estwrite.eoptions.weight = {''}; - expectedBatch{end}.spm.spatial.realign.estwrite.roptions.which = [0 1]; + expectedBatch{1}.spm.spatial.realignunwarp.eoptions.weight = {''}; + expectedBatch{end}.spm.spatial.realignunwarp.uwroptions.uwwhich = [2 1]; runCounter = 1; for iSes = 1 @@ -37,7 +37,8 @@ function test_setBatchRealignBasic() 'task', opt.taskName, ... 'type', 'bold'); - expectedBatch{1}.spm.spatial.realign.estwrite.data{iSes} = cellstr(fileName); + expectedBatch{end}.spm.spatial.realignunwarp.data(1, iSes).pmscan = { '' }; + expectedBatch{end}.spm.spatial.realignunwarp.data(1, iSes).scans = cellstr(fileName); end assertEqual(matlabbatch, expectedBatch); diff --git a/tests/test_setBatchResults.m b/tests/test_setBatchResults.m new file mode 100644 index 00000000..ca500734 --- /dev/null +++ b/tests/test_setBatchResults.m @@ -0,0 +1,150 @@ +function test_suite = test_setBatchResults %#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_setBatchResultsBasic() + + iStep = 1; + iCon = 1; + + result.dir = pwd; + result.label = '01'; + result.nbSubj = 1; + result.contrastNb = 1; + + matlabbatch = []; + matlabbatch = setBatchResults(matlabbatch, result); + + expectedBatch = returnBasicExpectedResultsBatch(); + + assertEqual(matlabbatch, expectedBatch); + +end + +function test_setBatchResultsExport() + + iStep = 1; + iCon = 1; + + opt.result.Steps.Contrasts.Name = 'test'; + opt.result.Steps.Contrasts.MC = 'FDR'; + opt.result.Steps.Contrasts.p = 0.05; + opt.result.Steps.Contrasts.k = 0; + + opt.result.Steps.Output.png = true; + opt.result.Steps.Output.csv = true; + opt.result.Steps.Output.thresh_spm = true; + opt.result.Steps.Output.binary = true; + opt.result.Steps.Output.NIDM_results = true; + + opt.space = 'individual'; + + result.dir = pwd; + result.label = '01'; + result.nbSubj = 1; + result.contrastNb = 1; + + %% + result.Contrasts = opt.result.Steps(iStep).Contrasts; + result.Output = opt.result.Steps(iStep).Output; + result.space = opt.space; + + matlabbatch = []; + matlabbatch = setBatchResults(matlabbatch, result); + + expectedBatch = returnBasicExpectedResultsBatch(); + + %% + expectedBatch{end}.spm.stats.results.conspec.titlestr = returnName(result); + expectedBatch{end}.spm.stats.results.conspec.threshdesc = 'FDR'; + + expectedBatch{end}.spm.stats.results.export{1}.png = true; + expectedBatch{end}.spm.stats.results.export{2}.csv = true; + expectedBatch{end}.spm.stats.results.export{3}.tspm.basename = returnName(result); + expectedBatch{end}.spm.stats.results.export{4}.binary.basename = [returnName(result) '_mask']; + + expectedBatch{end}.spm.stats.results.export{end + 1}.nidm.modality = 'FMRI'; + expectedBatch{end}.spm.stats.results.export{end}.nidm.refspace = 'ixi'; + expectedBatch{end}.spm.stats.results.export{end}.nidm.refspace = 'subject'; + expectedBatch{end}.spm.stats.results.export{end}.nidm.group.nsubj = 1; + expectedBatch{end}.spm.stats.results.export{end}.nidm.group.label = '01'; + + assertEqual(matlabbatch{end}.spm.stats.results, expectedBatch{end}.spm.stats.results); + +end + +function test_setBatchResultsMontage() + + iStep = 1; + iCon = 1; + + opt.result.Steps.Contrasts.Name = ''; + opt.result.Steps.Contrasts.MC = 'FWE'; + opt.result.Steps.Contrasts.p = 0.05; + opt.result.Steps.Contrasts.k = 0; + + opt.result.Steps.Output.montage.do = true; + + opt.space = 'MNI'; + + result.dir = pwd; + result.label = '01'; + result.nbSubj = 1; + result.contrastNb = 1; + + %% + result.Contrasts = opt.result.Steps(iStep).Contrasts; + result.Output = opt.result.Steps(iStep).Output; + result.space = opt.space; + + matlabbatch = []; + matlabbatch = setBatchResults(matlabbatch, result); + + expectedBatch = returnBasicExpectedResultsBatch(); + + expectedBatch{end}.spm.stats.results.conspec.titlestr = returnName(result); + expectedBatch{end}.spm.stats.results.conspec.threshdesc = 'FWE'; + + expectedBatch{end}.spm.stats.results.export{1}.montage.background = ... + {fullfile(spm('dir'), 'canonical', 'avg152T1.nii,1')}; + expectedBatch{end}.spm.stats.results.export{end}.montage.orientation = 'axial'; + expectedBatch{end}.spm.stats.results.export{end}.montage.slices = []; + + expectedBatch{end + 1}.spm.util.print.fname = ['Montage' returnName(result)]; + expectedBatch{end}.spm.util.print.fig.figname = 'SliceOverlay'; + expectedBatch{end}.spm.util.print.opts = 'png'; + + assertEqual(matlabbatch{1}.spm.stats.results.conspec, expectedBatch{1}.spm.stats.results.conspec); + assertEqual( ... + matlabbatch{1}.spm.stats.results.export{1}.montage, ... + expectedBatch{1}.spm.stats.results.export{1}.montage); + +end + +function expectedBatch = returnBasicExpectedResultsBatch() + + result.Contrasts.Name = ''; + result.Contrasts.p = 0.05; + result.Contrasts.k = 0; + result.Contrasts.MC = 'FWE'; + + expectedBatch = {}; + expectedBatch{end + 1}.spm.stats.results.spmmat = {fullfile(pwd, 'SPM.mat')}; + + expectedBatch{end}.spm.stats.results.conspec.titlestr = returnName(result); + expectedBatch{end}.spm.stats.results.conspec.contrasts = 1; + expectedBatch{end}.spm.stats.results.conspec.threshdesc = 'FWE'; + expectedBatch{end}.spm.stats.results.conspec.thresh = 0.05; + expectedBatch{end}.spm.stats.results.conspec.extent = 0; + expectedBatch{end}.spm.stats.results.conspec.conjunction = 1; + expectedBatch{end}.spm.stats.results.conspec.mask.none = true(); + + expectedBatch{end}.spm.stats.results.units = 1; + + expectedBatch{end}.spm.stats.results.export = []; + +end diff --git a/tests/test_setBatchSTC.m b/tests/test_setBatchSTC.m index bdb66312..10dda843 100644 --- a/tests/test_setBatchSTC.m +++ b/tests/test_setBatchSTC.m @@ -16,7 +16,8 @@ function test_setBatchSTCEmpty() [~, opt, BIDS] = getData(opt); subID = '02'; - matlabbatch = setBatchSTC(BIDS, opt, subID); + matlabbatch = []; + matlabbatch = setBatchSTC(matlabbatch, BIDS, opt, subID); % no slice timing info for this run so nothing should be returned. assertEqual(matlabbatch, []); @@ -28,15 +29,18 @@ function test_setBatchSTCForce() opt.derivativesDir = fullfile(fileparts(mfilename('fullpath')), 'dummyData'); opt.taskName = 'vislocalizer'; % we give it some slice timing value to force slice timing to happen - opt.sliceOrder = 1:5; - opt.STC_referenceSlice = 1.24 / 2; + opt.sliceOrder = linspace(0, 1.6, 10); + opt.sliceOrder(end - 1:end) = []; + opt.STC_referenceSlice = 1.6 / 2; opt = checkOptions(opt); [~, opt, BIDS] = getData(opt); subID = '02'; - matlabbatch = setBatchSTC(BIDS, opt, subID); + + matlabbatch = []; + matlabbatch = setBatchSTC(matlabbatch, BIDS, opt, subID); TR = 1.55; expectedBatch = returnExpectedBatch(opt.sliceOrder, opt.STC_referenceSlice, TR); @@ -66,7 +70,9 @@ function test_setBatchSTCBasic() [~, opt, BIDS] = getData(opt); subID = '02'; - matlabbatch = setBatchSTC(BIDS, opt, subID); + + matlabbatch = []; + matlabbatch = setBatchSTC(matlabbatch, BIDS, opt, subID); TR = 1.5; sliceOrder = repmat([ ... @@ -95,6 +101,29 @@ function test_setBatchSTCBasic() end +function test_setBatchSTCErrorInvalidInputTime() + + opt.derivativesDir = fullfile(fileparts(mfilename('fullpath')), 'dummyData'); + opt.taskName = 'vislocalizer'; + + opt.sliceOrder = linspace(0, 1.6, 10); + opt.sliceOrder(end) = []; + opt.STC_referenceSlice = 2; % impossible reference value + + opt = checkOptions(opt); + + subID = '02'; + + [~, opt, BIDS] = getData(opt); + + matlabbatch = []; + + assertExceptionThrown( ... + @()setBatchSTC(matlabbatch, BIDS, opt, subID), ... + 'setBatchSTC:invalidInputTime'); + +end + function expectedBatch = returnExpectedBatch(sliceOrder, referenceSlice, TR) nbSlices = length(sliceOrder); @@ -103,7 +132,7 @@ function test_setBatchSTCBasic() expectedBatch{1}.spm.temporal.st.nslices = nbSlices; expectedBatch{1}.spm.temporal.st.tr = TR; expectedBatch{1}.spm.temporal.st.ta = TA; - expectedBatch{1}.spm.temporal.st.so = sliceOrder; - expectedBatch{1}.spm.temporal.st.refslice = referenceSlice; + expectedBatch{1}.spm.temporal.st.so = sliceOrder * 1000; + expectedBatch{1}.spm.temporal.st.refslice = referenceSlice * 1000; end diff --git a/tests/test_setBatchSaveCoregistrationMatrix.m b/tests/test_setBatchSaveCoregistrationMatrix.m index 8b1b7850..89889ce1 100644 --- a/tests/test_setBatchSaveCoregistrationMatrix.m +++ b/tests/test_setBatchSaveCoregistrationMatrix.m @@ -22,7 +22,7 @@ function test_setBatchSaveCoregistrationMatrixBasic() opt.orderBatches.coregister = 1; matlabbatch = {}; - matlabbatch = setBatchSaveCoregistrationMatrix(matlabbatch, BIDS, subID, opt); + matlabbatch = setBatchSaveCoregistrationMatrix(matlabbatch, BIDS, opt, subID); expectedBatch = returnExpectedBatch(); assertEqual(matlabbatch, expectedBatch); @@ -37,7 +37,7 @@ function test_setBatchSaveCoregistrationMatrixBasic() mfilename('fullpath')), ... 'dummyData', ... 'derivatives', ... - 'SPM12_CPPL', ... + 'cpp_spm', ... 'sub-02', ... 'ses-01', ... 'func'); diff --git a/tests/test_setBatchSegmentation.m b/tests/test_setBatchSegmentation.m index ac5f221f..9e63d69a 100644 --- a/tests/test_setBatchSegmentation.m +++ b/tests/test_setBatchSegmentation.m @@ -6,7 +6,7 @@ initTestSuite; end -function test_setBatchSegmentationBasic() +function test_setBatchSegmentationPipeline() spmLocation = spm('dir'); @@ -19,24 +19,68 @@ function test_setBatchSegmentationBasic() matlabbatch = setBatchSegmentation(matlabbatch, opt); expectedBatch = returnExpectedBatch(spmLocation); + expectedBatch{end}.spm.spatial.preproc.channel.vols(1) = ... + cfg_dep('Named File Selector: Anatomical(1) - Files', ... + substruct( ... + '.', 'val', '{}', {1}, ... + '.', 'val', '{}', {1}, ... + '.', 'val', '{}', {1}, ... + '.', 'val', '{}', {1}), ... + substruct('.', 'files', '{}', {1})); assertEqual(expectedBatch, matlabbatch); end +function test_setBatchSegmentationImages() + + spmLocation = spm('dir'); + + opt = struct(); + + anatImage = returnLocalAnatFilename(); + + % check with one file + matlabbatch = []; + matlabbatch = setBatchSegmentation(matlabbatch, opt, anatImage); + expectedBatch = returnExpectedBatch(spmLocation); + expectedBatch{end}.spm.spatial.preproc.channel.vols{1} = anatImage; + + assertEqual(expectedBatch, matlabbatch); + + % check with several files passed as a cell + matlabbatch = []; + anatImage = {anatImage; anatImage}; + matlabbatch = setBatchSegmentation(matlabbatch, opt, anatImage); + expectedBatch{end}.spm.spatial.preproc.channel.vols = anatImage; + + assertEqual(expectedBatch, matlabbatch); + +end + +function anatImage = returnLocalAnatFilename() + + subID = '01'; + + opt.taskName = 'vislocalizer'; + opt.derivativesDir = fullfile(fileparts(mfilename('fullpath')), 'dummyData'); + opt.subjects = {subID}; + + opt = checkOptions(opt); + + [~, opt, BIDS] = getData(opt); + + [anatImage, anatDataDir] = getAnatFilename(BIDS, subID, opt); + + anatImage = fullfile(anatDataDir, anatImage); + +end + function expectedBatch = returnExpectedBatch(spmLocation) expectedBatch = []; - expectedBatch{end + 1}.spm.spatial.preproc.channel.vols(1) = ... - cfg_dep('Named File Selector: Anatomical(1) - Files', ... - substruct( ... - '.', 'val', '{}', {1}, ... - '.', 'val', '{}', {1}, ... - '.', 'val', '{}', {1}, ... - '.', 'val', '{}', {1}), ... - substruct('.', 'files', '{}', {1})); - expectedBatch{end}.spm.spatial.preproc.channel.biasreg = 0.001; + expectedBatch{end + 1}.spm.spatial.preproc.channel.biasreg = 0.001; expectedBatch{end}.spm.spatial.preproc.channel.biasfwhm = 60; expectedBatch{end}.spm.spatial.preproc.channel.write = [0 1]; diff --git a/tests/test_setBatchSelectAnat.m b/tests/test_setBatchSelectAnat.m index e374ea04..14ba13c7 100644 --- a/tests/test_setBatchSelectAnat.m +++ b/tests/test_setBatchSelectAnat.m @@ -31,10 +31,10 @@ function test_setBatchSelectAnatBasic() fullfile( ... opt.dataDir, '..', ... 'derivatives', ... - 'SPM12_CPPL', ... + 'cpp_spm', ... 'sub-01', ... 'anat'), ... - 'sub-01_T1w.nii'); + '^sub-01_T1w.nii$'); expectedBatch{1}.cfg_basicio.cfg_named_file.files = { {anatFile} }; assertEqual(matlabbatch, expectedBatch); diff --git a/tests/test_setBatchSkullStripping.m b/tests/test_setBatchSkullStripping.m index cd4005a1..51d5cba2 100644 --- a/tests/test_setBatchSkullStripping.m +++ b/tests/test_setBatchSkullStripping.m @@ -1,3 +1,5 @@ +% (C) Copyright 2019 CPP BIDS SPM-pipeline developers + function test_suite = test_setBatchSkullStripping %#ok<*STOUT> try % assignment of 'localfunctions' is necessary in Matlab >= 2016 test_functions = localfunctions(); %#ok<*NASGU> @@ -22,7 +24,7 @@ function test_setBatchSkullStrippingBasic() opt.orderBatches.segment = 2; matlabbatch = []; - matlabbatch = setBatchSkullStripping(matlabbatch, BIDS, subID, opt); + matlabbatch = setBatchSkullStripping(matlabbatch, BIDS, opt, subID); expectedBatch = returnExpectedBatch(opt); @@ -35,7 +37,7 @@ function test_setBatchSkullStrippingBasic() expectedFileName = 'sub-01_ses-01_T1w.nii'; expectedAnatDataDir = fullfile(fileparts(mfilename('fullpath')), ... - 'dummyData', 'derivatives', 'SPM12_CPPL', ... + 'dummyData', 'derivatives', 'cpp_spm', ... 'sub-01', 'ses-01', 'anat'); expectedBatch = []; diff --git a/tests/test_setBatchSmoothConImages.m b/tests/test_setBatchSmoothConImages.m new file mode 100644 index 00000000..87cf445e --- /dev/null +++ b/tests/test_setBatchSmoothConImages.m @@ -0,0 +1,72 @@ +function test_suite = test_setBatchSmoothConImages %#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_setBatchSmoothConImagesBasic() + + opt.groups = {''}; + opt.subjects = {'01', '02'}; + opt.derivativesDir = fullfile(fileparts(mfilename('fullpath')), ... + 'dummyData', ... + 'derivatives', ... + 'cpp_spm'); + opt.taskName = 'vismotion'; + + funcFWHM = 6; + conFWHM = 6; + + opt = checkOptions(opt); + [group, opt] = getData(opt); + + matlabbatch = []; + matlabbatch = setBatchSmoothConImages(matlabbatch, group, opt, funcFWHM, conFWHM); + + expectedBatch{1}.spm.spatial.smooth.fwhm = [6 6 6]; + expectedBatch{1}.spm.spatial.smooth.prefix = 's6'; + expectedBatch{1}.spm.spatial.smooth.data = {fullfile(opt.derivativesDir, 'sub-01', 'stats', ... + 'ffx_task-vismotion', ... + 'ffx_space-MNI_FWHM-6', ... + 'con_0001.nii'); ... + fullfile(opt.derivativesDir, 'sub-01', 'stats', ... + 'ffx_task-vismotion', ... + 'ffx_space-MNI_FWHM-6', ... + 'con_0002.nii'); ... + fullfile(opt.derivativesDir, 'sub-01', 'stats', ... + 'ffx_task-vismotion', ... + 'ffx_space-MNI_FWHM-6', ... + 'con_0003.nii'); ... + fullfile(opt.derivativesDir, 'sub-01', 'stats', ... + 'ffx_task-vismotion', ... + 'ffx_space-MNI_FWHM-6', ... + 'con_0004.nii')}; + expectedBatch{1}.spm.spatial.smooth.dtype = 0; + expectedBatch{1}.spm.spatial.smooth.im = 0; + + expectedBatch{2}.spm.spatial.smooth.fwhm = [6 6 6]; + expectedBatch{2}.spm.spatial.smooth.prefix = 's6'; + expectedBatch{2}.spm.spatial.smooth.data = {fullfile(opt.derivativesDir, 'sub-02', 'stats', ... + 'ffx_task-vismotion', ... + 'ffx_space-MNI_FWHM-6', ... + 'con_0001.nii'); ... + fullfile(opt.derivativesDir, 'sub-02', 'stats', ... + 'ffx_task-vismotion', ... + 'ffx_space-MNI_FWHM-6', ... + 'con_0002.nii'); ... + fullfile(opt.derivativesDir, 'sub-02', 'stats', ... + 'ffx_task-vismotion', ... + 'ffx_space-MNI_FWHM-6', ... + 'con_0003.nii'); ... + fullfile(opt.derivativesDir, 'sub-02', 'stats', ... + 'ffx_task-vismotion', ... + 'ffx_space-MNI_FWHM-6', ... + 'con_0004.nii')}; + expectedBatch{2}.spm.spatial.smooth.dtype = 0; + expectedBatch{2}.spm.spatial.smooth.im = 0; + + assertEqual(matlabbatch{1}.spm.spatial.smooth.data, expectedBatch{1}.spm.spatial.smooth.data); + +end diff --git a/tests/test_setBatchSmoothing.m b/tests/test_setBatchSmoothing.m index abaec521..982219e3 100644 --- a/tests/test_setBatchSmoothing.m +++ b/tests/test_setBatchSmoothing.m @@ -8,44 +8,21 @@ function test_setBatchSmoothingBasic() - % TODO - % need a test with several sessions and runs + FWHM = 6; + prefix = 's6_'; - subID = '01'; - - funcFWHM = 6; - - opt.dataDir = fullfile(fileparts(mfilename('fullpath')), '..', 'demos', ... - 'MoAE', 'output', 'MoAEpilot'); - opt.taskName = 'auditory'; - - opt = checkOptions(opt); - - [~, opt, BIDS] = getData(opt); - - % create dummy normalized file - fileName = spm_BIDS(BIDS, 'data', ... - 'sub', subID, ... - 'task', opt.taskName, ... - 'type', 'bold'); - [filepath, filename, ext] = fileparts(fileName{1}); - fileName = fullfile( ... - filepath, ... - [spm_get_defaults('normalise.write.prefix') filename ext]); - system(sprintf('touch %s', fileName)); + images = { fullfile(pwd, 'sub-01_T1w.nii') }; matlabbatch = []; - matlabbatch = setBatchSmoothing(BIDS, opt, subID, funcFWHM); + matlabbatch = setBatchSmoothing(matlabbatch, images, FWHM, prefix); expectedBatch{1}.spm.spatial.smooth.fwhm = [6 6 6]; + expectedBatch{1}.spm.spatial.smooth.prefix = 's6_'; + expectedBatch{1}.spm.spatial.smooth.data = {fullfile(pwd, 'sub-01_T1w.nii')}; + expectedBatch{1}.spm.spatial.smooth.dtype = 0; expectedBatch{1}.spm.spatial.smooth.im = 0; - expectedBatch{1}.spm.spatial.smooth.prefix = ... - [spm_get_defaults('smooth.prefix'), '6']; - expectedBatch{1}.spm.spatial.smooth.data{1} = fileName; - assertEqual( ... - matlabbatch{1}.spm.spatial.smooth, ... - expectedBatch{1}.spm.spatial.smooth); + assertEqual(matlabbatch, expectedBatch); end diff --git a/tests/test_setBatchSmoothingFunc.m b/tests/test_setBatchSmoothingFunc.m new file mode 100644 index 00000000..0ea2ea31 --- /dev/null +++ b/tests/test_setBatchSmoothingFunc.m @@ -0,0 +1,51 @@ +function test_suite = test_setBatchSmoothingFunc %#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_setBatchSmoothingFuncBasic() + + % TODO + % need a test with several sessions and runs + + subID = '01'; + + funcFWHM = 6; + + opt.dataDir = fullfile(fileparts(mfilename('fullpath')), '..', 'demos', ... + 'MoAE', 'output', 'MoAEpilot'); + opt.taskName = 'auditory'; + + opt = checkOptions(opt); + + [~, opt, BIDS] = getData(opt); + + % create dummy normalized file + fileName = spm_BIDS(BIDS, 'data', ... + 'sub', subID, ... + 'task', opt.taskName, ... + 'type', 'bold'); + [filepath, filename, ext] = fileparts(fileName{1}); + fileName = fullfile( ... + filepath, ... + [spm_get_defaults('normalise.write.prefix'), ... + spm_get_defaults('unwarp.write.prefix'), ... + filename ext]); + system(sprintf('touch %s', fileName)); + + matlabbatch = []; + matlabbatch = setBatchSmoothingFunc(matlabbatch, BIDS, opt, subID, funcFWHM); + + expectedBatch{1}.spm.spatial.smooth.fwhm = [6 6 6]; + expectedBatch{1}.spm.spatial.smooth.dtype = 0; + expectedBatch{1}.spm.spatial.smooth.im = 0; + expectedBatch{1}.spm.spatial.smooth.prefix = ... + [spm_get_defaults('smooth.prefix'), '6']; + expectedBatch{1}.spm.spatial.smooth.data{1} = fileName; + + assertEqual(matlabbatch, expectedBatch); + +end diff --git a/tests/test_setBatchSubjectLevelContrasts.m b/tests/test_setBatchSubjectLevelContrasts.m index 511e879e..5fc7855c 100644 --- a/tests/test_setBatchSubjectLevelContrasts.m +++ b/tests/test_setBatchSubjectLevelContrasts.m @@ -8,37 +8,51 @@ function test_setBatchSubjectLevelContrastsBasic() - funcFWHM = 6; subID = '01'; - iSes = 1; - iRun = 1; - - % directory with this script becomes the current directory - opt.subjects = {subID}; - opt.taskName = 'auditory'; - opt.dataDir = fullfile( ... - fileparts(mfilename('fullpath')), ... - '..', 'demos', 'MoAE', 'output', 'MoAEpilot'); - opt.model.file = fullfile(fileparts(mfilename('fullpath')), ... - '..', 'demos', 'MoAE', 'models', 'model-MoAE_smdl.json'); + funcFWHM = 6; + opt.derivativesDir = fullfile(fileparts(mfilename('fullpath')), 'dummyData'); + opt.space = 'MNI'; + opt.taskName = 'vismotion'; + opt.model.file = ... + fullfile(fileparts(mfilename('fullpath')), ... + 'dummyData', ... + 'models', ... + 'model-visMotionLoc_smdl.json'); + + opt = setDerivativesDir(opt); opt = checkOptions(opt); - ffxDir = fullfile(opt.derivativesDir, 'sub-01', 'stats', 'ffx_auditory', ... - 'ffx_space-MNI_FWHM-6'); + matlabbatch = []; + matlabbatch = setBatchSubjectLevelContrasts(matlabbatch, opt, subID, funcFWHM); - bidsCopyRawFolder(opt, 1); + expectedBatch = []; + expectedBatch{end + 1}.spm.stats.con.spmmat = {fullfile(opt.derivativesDir, ... + 'sub-01', ... + 'stats', ... + 'ffx_task-vismotion', ... + 'ffx_space-MNI_FWHM-6', ... + 'SPM.mat')}; + expectedBatch{end}.spm.stats.con.delete = 1; - % matlabbatch = setBatchSubjectLevelContrasts(opt, subID, funcFWHM); + consess{1}.tcon.name = 'VisMot'; %#ok<*AGROW> + consess{1}.tcon.convec = [1 0 0 0 0 0 0 0 0]; + consess{1}.tcon.sessrep = 'none'; - % TODO add assert - % expectedBatch = returnExpectedBatch(); - % assert(matlabbatch, returnExpectedBatch); + consess{2}.tcon.name = 'VisStat'; %#ok<*AGROW> + consess{2}.tcon.convec = [0 1 0 0 0 0 0 0 0]; + consess{2}.tcon.sessrep = 'none'; -end + consess{3}.tcon.name = 'VisMot_gt_VisStat'; %#ok<*AGROW> + consess{3}.tcon.convec = [1 -1 0 0 0 0 0 0 0]; + consess{3}.tcon.sessrep = 'none'; -% function expectedBatch = returnExpectedBatch() -% -% -% -% end + consess{4}.tcon.name = 'VisStat_gt_VisMot'; %#ok<*AGROW> + consess{4}.tcon.convec = [-1 1 0 0 0 0 0 0 0]; + consess{4}.tcon.sessrep = 'none'; + + expectedBatch{end}.spm.stats.con.consess = consess; + + assertEqual(matlabbatch{1}.spm.stats.con, expectedBatch{1}.spm.stats.con); + +end diff --git a/tests/test_setBatchSubjectLevelGLMSpec.m b/tests/test_setBatchSubjectLevelGLMSpec.m index cc693e1a..d2a13cd2 100644 --- a/tests/test_setBatchSubjectLevelGLMSpec.m +++ b/tests/test_setBatchSubjectLevelGLMSpec.m @@ -33,13 +33,14 @@ function test_setBatchSubjectLevelGLMSpecBasic() BIDS, ... subID, sessions{iSes}, runs{iRun}, opt); copyfile(fullfile(subFuncDataDir, fileName), ... - fullfile(subFuncDataDir, ['s6w', fileName])); + fullfile(subFuncDataDir, ['s6wu', fileName])); % create dummy realign parameter file system(sprintf('touch %s', ... fullfile(subFuncDataDir, ['rp_', strrep(fileName, '.nii', '.txt')]))); - matlabbatch = setBatchSubjectLevelGLMSpec(BIDS, opt, subID, funcFWHM); + matlabbatch = []; + matlabbatch = setBatchSubjectLevelGLMSpec(matlabbatch, BIDS, opt, subID, funcFWHM); % TODO add assert % expectedBatch = returnExpectedBatch(); @@ -76,7 +77,7 @@ function test_setBatchSubjectLevelGLMSpecBasic() % end % % % Things that may change -% matlabbatch{1}.spm.stats.fmri_spec.bases.hrf.derivs = [0 0]; +% matlabbatch{1}.spm.stats.fmri_spec.bases.hrf.derivs = opt.model.hrfDerivatives; % % matlabbatch{1}.spm.stats.fmri_spec.mthresh = 0.8; % diff --git a/tests/test_setBatchSubjectLevelResults.m b/tests/test_setBatchSubjectLevelResults.m new file mode 100644 index 00000000..b50a0b67 --- /dev/null +++ b/tests/test_setBatchSubjectLevelResults.m @@ -0,0 +1,108 @@ +function test_suite = test_setBatchSubjectLevelResults %#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_setBatchSubjectLevelResultsBasic() + + iStep = 1; + iCon = 1; + + subID = '01'; + funcFWHM = 6; + + opt.derivativesDir = fullfile(fileparts(mfilename('fullpath')), 'dummyData'); + opt.space = 'MNI'; + opt.taskName = 'vismotion'; + + opt = setDerivativesDir(opt); + opt = checkOptions(opt); + + opt.result.Steps.Contrasts.Name = 'VisMot'; + + matlabbatch = []; + matlabbatch = setBatchSubjectLevelResults(matlabbatch, opt, subID, funcFWHM, iStep, iCon); + + expectedBatch = {}; + + expectedBatch{end + 1}.spm.stats.results.spmmat = {fullfile(opt.derivativesDir, ... + 'sub-01', ... + 'stats', ... + 'ffx_task-vismotion', ... + 'ffx_space-MNI_FWHM-6', ... + 'SPM.mat')}; + + expectedBatch{end}.spm.stats.results.conspec.titlestr = 'VisMot_p-0050_k-0_MC-FWE'; + expectedBatch{end}.spm.stats.results.conspec.contrasts = 1; + expectedBatch{end}.spm.stats.results.conspec.threshdesc = 'FWE'; + expectedBatch{end}.spm.stats.results.conspec.thresh = 0.05; + expectedBatch{end}.spm.stats.results.conspec.extent = 0; + expectedBatch{end}.spm.stats.results.conspec.conjunction = 1; + expectedBatch{end}.spm.stats.results.conspec.mask.none = true(); + + expectedBatch{end}.spm.stats.results.units = 1; + + expectedBatch{end}.spm.stats.results.export = []; + + assertEqual(matlabbatch, expectedBatch); + +end + +function test_setBatchSubjectLevelResultsErrorMissingContrastName() + + iStep = 1; + iCon = 1; + + subID = '01'; + funcFWHM = 6; + + opt.derivativesDir = fullfile(fileparts(mfilename('fullpath')), 'dummyData'); + opt.space = 'MNI'; + opt.taskName = 'vismotion'; + + opt = setDerivativesDir(opt); + opt = checkOptions(opt); + + matlabbatch = []; + assertExceptionThrown( ... + @()setBatchSubjectLevelResults(matlabbatch, ... + opt, ... + subID, ... + funcFWHM, ... + iStep, ... + iCon), ... + 'setBatchSubjectLevelResults:missingContrastName'); + +end + +function test_setBatchSubjectLevelResultsErrorNoMAtchingContrast() + + iStep = 1; + iCon = 1; + + subID = '01'; + funcFWHM = 6; + + opt.derivativesDir = fullfile(fileparts(mfilename('fullpath')), 'dummyData'); + opt.space = 'MNI'; + opt.taskName = 'vismotion'; + + opt.result.Steps.Contrasts.Name = 'NotAContrast'; + + opt = setDerivativesDir(opt); + opt = checkOptions(opt); + + matlabbatch = []; + assertExceptionThrown( ... + @()setBatchSubjectLevelResults(matlabbatch, ... + opt, ... + subID, ... + funcFWHM, ... + iStep, ... + iCon), ... + 'setBatchSubjectLevelResults:NoMatchingContrastName'); + +end diff --git a/tests/test_setDerivativesDir.m b/tests/test_setDerivativesDir.m index 4ee4a6b3..4afab5cb 100644 --- a/tests/test_setDerivativesDir.m +++ b/tests/test_setDerivativesDir.m @@ -12,7 +12,8 @@ function test_setDerivativesDirBasic() opt.taskName = 'testTask'; opt = setDerivativesDir(opt); - assertEqual(opt.derivativesDir, fullfile(pwd, '..', 'derivatives', 'SPM12_CPPL')); + expected = spm_file(fullfile(pwd, '..', 'derivatives', 'cpp_spm'), 'cpath'); + assertEqual(opt.derivativesDir, expected); end @@ -22,6 +23,29 @@ function test_setDerivativesDirMissing() opt.taskName = 'testTask'; opt = setDerivativesDir(opt); - assertEqual(opt.derivativesDir, fullfile(pwd, 'derivatives', 'SPM12_CPPL')); + expected = spm_file(fullfile(pwd, 'derivatives', 'cpp_spm'), 'cpath'); + assertEqual(opt.derivativesDir, expected); + +end + +function test_setDerivativesDirPreset1() + + opt.derivativesDir = fullfile(pwd, 'derivatives'); + opt.taskName = 'testTask'; + opt = setDerivativesDir(opt); + + expected = spm_file(fullfile(pwd, 'derivatives', 'cpp_spm'), 'cpath'); + assertEqual(opt.derivativesDir, expected); + +end + +function test_setDerivativesDirPreset2() + + opt.derivativesDir = fullfile(pwd, 'derivatives', 'default'); + opt.taskName = 'testTask'; + opt = setDerivativesDir(opt); + + expected = spm_file(fullfile(pwd, 'derivatives', 'default'), 'cpath'); + assertEqual(opt.derivativesDir, expected); end diff --git a/tests/test_specifyContrasts.m b/tests/test_specifyContrasts.m index f31380a4..5c3ff220 100644 --- a/tests/test_specifyContrasts.m +++ b/tests/test_specifyContrasts.m @@ -9,16 +9,19 @@ function test_specifyContrastsBasic() % Small test to ensure that pmCon returns what we asked for + subID = '01'; + funcFWFM = 6; + opt.derivativesDir = fullfile(fileparts(mfilename('fullpath')), 'dummyData'); - opt.taskName = 'visMotion'; + opt.space = 'MNI'; + opt.taskName = 'vismotion'; opt.model.file = ... fullfile(fileparts(mfilename('fullpath')), ... 'dummyData', 'models', 'model-visMotionLoc_smdl.json'); opt = setDerivativesDir(opt); - ffxDir = fullfile(opt.derivativesDir, 'sub-01', 'stats', 'ffx_visMotion', ... - 'ffx_space-MNI_FWHM-6'); + ffxDir = getFFXdir(subID, funcFWFM, opt); contrasts = specifyContrasts(ffxDir, opt.taskName, opt); diff --git a/tests/test_validationInputFile.m b/tests/test_validationInputFile.m index 2ff1ca98..b72e8413 100644 --- a/tests/test_validationInputFile.m +++ b/tests/test_validationInputFile.m @@ -9,12 +9,12 @@ function test_validationInputFileBasic() directory = fullfile(fileparts(mfilename('fullpath')), 'dummyData', 'derivatives', ... - 'SPM12_CPPL', 'sub-01', 'ses-01', 'func'); + 'cpp_spm', 'sub-01', 'ses-01', 'func'); prefix = ''; fileName = 'sub-01_ses-01_task-vislocalizer_bold.nii'; expectedOutput = fullfile(fileparts(mfilename('fullpath')), 'dummyData', 'derivatives', ... - 'SPM12_CPPL', 'sub-01', 'ses-01', 'func', ... + 'cpp_spm', 'sub-01', 'ses-01', 'func', ... 'sub-01_ses-01_task-vislocalizer_bold.nii'); file = validationInputFile(directory, fileName, prefix); diff --git a/tests/test_writeDatasetDescription.m b/tests/test_writeDatasetDescription.m new file mode 100644 index 00000000..cbb964ff --- /dev/null +++ b/tests/test_writeDatasetDescription.m @@ -0,0 +1,79 @@ +function test_suite = test_writeDatasetDescription %#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_writeDatasetDescriptionBasic() + + opt.derivativesDir = fullfile(fileparts(mfilename('fullpath')), ... + 'dummyData', ... + 'derivatives', ... + 'cpp_spm'); + + copyfile( ... + fullfile(opt.derivativesDir, 'dataset_description_old.json'), ... + fullfile(opt.derivativesDir, 'dataset_description.json')); + + writeDatasetDescription(opt); + + content = spm_jsonread(fullfile(opt.derivativesDir, 'dataset_description.json')); + + expectedContent = returnExpectedContent(); + + assertEqual(content, expectedContent); + +end + +function expectedContent = returnExpectedContent() + + expectedContent.Name = 'cpp_spm outputs'; + expectedContent.BIDSVersion = '1.4.1'; + expectedContent.DatasetType = 'derivative'; + + expectedContent.GeneratedBy = struct( ... + 'Name', 'cpp_spm', ... + 'Version', getVersion(), ... + 'Container', struct('Type', '', 'Tag', '')); + + % RECOMMENDED + expectedContent.License = ''; + expectedContent.Authors = {''}; + expectedContent.Acknowledgements = ''; + expectedContent.HowToAcknowledge = ''; + expectedContent.Funding = {''}; + expectedContent.ReferencesAndLinks = {''}; + expectedContent.DatasetDOI = ''; + expectedContent.SourceDatasets = struct( ... + 'DOI', 'doi:10.18112/openneuro.ds000114.v1.0.1', ... + 'URL', '', 'Version', ''); + + expectedContent = orderfields(expectedContent); + +end + +% { +% "License": "", +% "Authors": [ +% "John Doe", +% "Charles Darwin", +% "Freddy Krueger" +% ], +% "Acknowledgements": "Thanks to all to tests that failed courageously.", +% "HowToAcknowledge": "", +% "Funding": [ +% "", +% "", +% "" +% ], +% "ReferencesAndLinks": [ +% "", +% "", +% "" +% ], +% "DatasetDOI": "doi:10.18112/openneuro.ds000114.v1.0.1", +% "Name": "dummyData", +% "BIDSVersion": "1.1.0" +% } diff --git a/version.txt b/version.txt index 9ff151c5..81fd7ba0 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -v0.1.0 \ No newline at end of file +v0.2.0 \ No newline at end of file