diff --git a/.github/workflows/miss_hit_code_quality.yml b/.github/workflows/miss_hit_code_quality.yml index 78c6117..faa0b44 100644 --- a/.github/workflows/miss_hit_code_quality.yml +++ b/.github/workflows/miss_hit_code_quality.yml @@ -32,7 +32,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip setuptools - pip3 install -r requirements.txt + pip3 install -r requirements.txt - name: MISS_HIT Metrics run: | @@ -40,4 +40,4 @@ jobs: - name: MISS_HIT Bug finder run: | - mh_lint \ No newline at end of file + mh_lint diff --git a/.github/workflows/miss_hit_code_style.yml b/.github/workflows/miss_hit_code_style.yml index eaef1ef..ea44532 100644 --- a/.github/workflows/miss_hit_code_style.yml +++ b/.github/workflows/miss_hit_code_style.yml @@ -32,8 +32,8 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip setuptools - pip3 install -r requirements.txt + pip3 install -r requirements.txt - name: MISS_HIT Code style run: | - mh_style --process-slx \ No newline at end of file + mh_style --process-slx diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 5f474ed..596a22c 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -6,15 +6,12 @@ on: - master - main - dev - pull_request: - branches: '*' -env: - OCTFLAGS: --no-gui --no-window-system --silent + pull_request: + branches: ['*'] jobs: build: - runs-on: ubuntu-20.04 steps: @@ -24,37 +21,36 @@ jobs: sudo apt-get -y -qq update sudo apt-get -y install octave liboctave-dev - - name: Clone spm_2_bids + - name: Clone spm_2_bids 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 + - name: get bids-matlab and JSONio 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: get bids-matlab and set up paths - run: | - make install_dev - octave $OCTFLAGS --eval "init_env; savepath();" - - - name: Run tests - run: | - octave $OCTFLAGS --eval "run_tests" - cat test_report.log | grep 0 - bash <(curl -s https://codecov.io/bash) + make install_dev_octave - + - name: MOxUnit Action + uses: joergbrech/moxunit-action@v1.2.0 + with: + tests: tests # files or directories containing the MOxUnit test cases + src: src # directories to be added to path before running the tests. + ext: tests/utils lib/bids-matlab lib/JSONio # External resources to add to the search put (excluded from coverage) + # data: # Directory for test data + with_coverage: true + cover_xml_file: coverage.xml + + - name: Upload coverage + uses: actions/upload-artifact@v1 + with: + name: coverage_file + path: coverage.xml + - name: Code coverage + uses: codecov/codecov-action@v1 + with: + file: coverage.xml # optional + flags: unittests # optional + name: codecov-umbrella # optional + fail_ci_if_error: true # optional (default = false) diff --git a/.gitignore b/.gitignore index 2b533bb..b35d9b8 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,11 @@ env/ lib/bids-matlab +lib/JSONio +coverage_html +coverage.xml +test_report.log ## MATLAB / OCTAVE gitignore template @@ -44,5 +48,3 @@ codegen/ # Octave session info octave-workspace - - diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a2858fb..310aa22 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,4 +25,18 @@ repos: entry: mh_lint files: ^(.*\.(m|slx))$ language: python - additional_dependencies: [miss_hit] \ No newline at end of file + additional_dependencies: [miss_hit] + +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.1.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + +- repo: https://github.com/pre-commit/pygrep-hooks + rev: v1.9.0 + hooks: + - id: rst-backticks # Detect common mistake of using single backticks when writing rst + - id: rst-inline-touching-normal # Detect mistake of inline code touching normal text in rst diff --git a/Makefile b/Makefile index 4db37b9..a252fd6 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,14 @@ install_dev: git clone https://github.com/bids-standard/bids-matlab.git lib/bids-matlab cd lib/bids-matlab && git checkout dev + git clone https://github.com/gllmflndn/JSONio.git --depth 1 lib/JSONio + +install_dev_octave: + git clone https://github.com/bids-standard/bids-matlab.git lib/bids-matlab + cd lib/bids-matlab && git checkout dev + git clone https://github.com/gllmflndn/JSONio.git --depth 1 lib/JSONio + cd lib/JSONio && mkoctfile --mex jsonread.c jsmn.c -DJSMN_PARENT_LINKS clean: - rm -rf lib/bids-matlab \ No newline at end of file + rm -rf lib/bids-matlab + rm -rf lib/JSONio diff --git a/README.md b/README.md index 0f01f9f..dc10cd0 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ When you have set up your repo [![Build Status](https://travis-ci.com/Remi-gau/template_matlab_analysis.svg?branch=master)](https://travis-ci.com/Remi-gau/template_matlab_analysis) --> + # spm_2_bids Small code base to help convert the MRI spm output to a valid bids derivatives. @@ -26,4 +27,4 @@ Most of the renaming is based on the SPM prefixes combinations. It is configurable to adapt to new set of prefixes. - [Dependencies](./lib/README.md) -- [Documentation](https://spm-2-bids.readthedocs.io/en/latest/) \ No newline at end of file +- [Documentation](https://spm-2-bids.readthedocs.io/en/latest/) diff --git a/binder/apt.txt b/binder/apt.txt new file mode 100644 index 0000000..23b6a54 --- /dev/null +++ b/binder/apt.txt @@ -0,0 +1,5 @@ +octave +liboctave-dev +gnuplot +ghostscript +tree diff --git a/binder/environment.yml b/binder/environment.yml new file mode 100644 index 0000000..19c10f8 --- /dev/null +++ b/binder/environment.yml @@ -0,0 +1,2 @@ +dependencies: +- octave_kernel diff --git a/binder/postBuild b/binder/postBuild new file mode 100644 index 0000000..26dfe78 --- /dev/null +++ b/binder/postBuild @@ -0,0 +1,11 @@ +cd ${HOME} + +octave --no-gui --no-window-system --silent --eval "addpath (getenv (\"HOME\")); savepath ();" +octave --no-gui --no-window-system --silent --eval "addpath (fullfile (getenv (\"HOME\"), \"+bids\")); savepath ();" + +git clone git://github.com/gllmflndn/JSONio.git --depth 1 +cd JSONio; mkoctfile --mex jsonread.c jsmn.c -DJSMN_PARENT_LINKS; cd .. + +octave --no-gui --no-window-system --silent --eval "addpath (fullfile (getenv (\"HOME\"), \"JSONio\")); savepath ();" + +cd ${HOME}/examples diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..378eac2 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +build diff --git a/docs/Makefile b/docs/Makefile index 8996f31..d0c3cbf 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -18,6 +18,3 @@ help: # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -doc: - @$(SPHINXBUILD) -b html "$(SOURCEDIR)" "$(BUILDDIR)" diff --git a/docs/README.md b/docs/README.md index 910c208..a35d655 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,8 +3,8 @@ ## Set up virtual environment ```bash -virtualenv -p python3 your_repo_name -source your_repo_name/bin/activate +virtualenv -p python3.8 env +source env/bin/activate pip install -r requirements.txt ``` diff --git a/docs/requirements.txt b/docs/requirements.txt index a4b9ccc..8d7a63f 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,4 +1,4 @@ Sphinx sphinxcontrib-matlabdomain sphinxcontrib-napoleon -sphinx_rtd_theme \ No newline at end of file +sphinx_rtd_theme diff --git a/docs/source/conf.py b/docs/source/conf.py index d05d57b..2d89d7a 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -17,12 +17,11 @@ # -- Project information ----------------------------------------------------- -project = 'your repo name' -copyright = 'you' -author = 'you' +project = 'spm_2_bids' +author = 'RĂ©mi Gau' # The full version, including alpha/beta/rc tags -release = 'v0.0.0' +release = 'v0.1.0' # -- General configuration --------------------------------------------------- @@ -31,7 +30,7 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinxcontrib.matlab', + 'sphinxcontrib.matlab', 'sphinx.ext.autodoc'] matlab_src_dir = os.path.dirname(os.path.abspath('../../src')) primary_domain = 'mat' @@ -53,6 +52,8 @@ # source_suffix = ['.rst', '.md'] source_suffix = '.rst' +autodoc_member_order = 'bysource' + # -- Options for HTML output ------------------------------------------------- @@ -64,7 +65,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +# html_static_path = ['_static'] # html_logo = '_static/logo.png' @@ -82,4 +83,4 @@ 'searchbox.html', 'donate.html', ] -} \ No newline at end of file +} diff --git a/docs/source/index.rst b/docs/source/index.rst index d452f33..aff256a 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,7 +1,7 @@ .. cpp_bids documentation master file -Welcome to CPP BIDS documentation! -********************************** +Welcome to spm_2_bids documentation! +************************************ .. toctree:: :maxdepth: 2 @@ -12,11 +12,18 @@ Welcome to CPP BIDS documentation! ``spm_2_bids`` only provides names to use but does not actually rename the files. .. automodule:: src + .. autofunction:: spm_2_bids .. automodule:: src.defaults + .. autofunction:: check_cfg +.. autoclass:: Mapping + :members: + + + Indices and tables ================== diff --git a/init_env.m b/init_spm_2_bids.m similarity index 96% rename from init_env.m rename to init_spm_2_bids.m index fdaa191..9859dd3 100644 --- a/init_env.m +++ b/init_spm_2_bids.m @@ -1,4 +1,4 @@ -function init_env +function init_spm_2_bids % % 1 - Check if version requirements % are satisfied and the packages are @@ -18,10 +18,12 @@ OCTAVE_VER = '4.0.3'; MATLAB_VER = '8.6.0'; - INSTALL_LIST = {'io', 'statistics', 'image'}; + INSTALL_LIST = {}; if is_octave + more off; + % Exit if min version is not satisfied if ~compare_versions(OCTAVE_VERSION, OCTAVE_VER, '>=') error('Minimum required Octave version: %s', OCTAVE_VER); diff --git a/lib/README.md b/lib/README.md index e8b8685..e1aa869 100644 --- a/lib/README.md +++ b/lib/README.md @@ -4,8 +4,4 @@ This code requires: - SPM12 for some actions ??? -- [BIDS-matlab](https://github.com/bids-standard/bids-matlab) from commi - [b7732b0cb](https://github.com/bids-standard/bids-matlab/commit/b7732b0cb2103ee0cfa095ee604bee4086844cad) - or later. In doubt use the `dev` branch of bids-matlab. - - +- [BIDS-matlab](https://github.com/bids-standard/bids-matlab) ; version 0.1.0 diff --git a/miss_hit.cfg b/miss_hit.cfg index 5d969f8..f4b0e98 100644 --- a/miss_hit.cfg +++ b/miss_hit.cfg @@ -4,7 +4,10 @@ project_root line_length: 100 regex_function_name: "[a-z]+(_[a-z0-9]+)*" -regex_script_name: "[a-z]+(_[a-z0-9]+)*" +regex_class_name: "[A-Z]{1}[a-z]+" +regex_script_name: "[a-z]+(_[a-z]+)*" + +# suppress_rule: "naming_parameters" # regex_parameter_name: "[a-z]+(_[a-z0-9]+)*" exclude_dir: "lib" @@ -12,7 +15,7 @@ exclude_dir: "lib" copyright_entity: "spm_2_bids developers" # metric for code quality -metric "cnest": limit 6 +metric "cnest": limit 5 metric "file_length": limit 500 -metric "cyc": limit 25 -metric "parameters": limit 5 \ No newline at end of file +metric "cyc": limit 13 +metric "parameters": limit 5 diff --git a/notebooks/.gitignore b/notebooks/.gitignore new file mode 100644 index 0000000..763513e --- /dev/null +++ b/notebooks/.gitignore @@ -0,0 +1 @@ +.ipynb_checkpoints diff --git a/notebooks/demo.ipynb b/notebooks/demo.ipynb new file mode 100644 index 0000000..dc1b658 --- /dev/null +++ b/notebooks/demo.ipynb @@ -0,0 +1,285 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# SPM_2_BIDS DEMO\n", + "\n", + "**Helping you convert your SPM output into a BIDS compliant datasets.**\n", + "\n", + "## BIDS filenames\n", + "\n", + "Ideally we would like to have the same pipeline for statistical analysis \n", + "whether our data was preprocessed with SPM or with fmriprep (for example).\n", + "\n", + "This is possible under the condition that the input files for the statistical analysis are BIDS compliant: \n", + "meaning that they follow the typical pattern of BIDS files:\n", + "\n", + "- pseudo \"regular expression\" : `entity-label(_entity-label)+_suffix.extension`\n", + "\n", + "\n", + "- `entity`, `label`, `suffix`, `extension` are alphanumeric only (no special character): `([a-zA-Z0-9])+`\n", + " - suffixes can be: `T1w` or `bold` but not `T1w_skullstripped` (no underscore allowed)\n", + "\n", + "\n", + "- entity and label are separated by a dash: \n", + " `entity-label --> ([a-zA-Z0-9])+-([a-zA-Z0-9])+`\n", + " - you can have: `sub-01` but not `sub-01-blind`\n", + " \n", + "\n", + "\n", + "- entity-label pairs are separated by an underscore:\n", + " `entity-label(_entity-label)+ --> ([a-zA-Z0-9])+-([a-zA-Z0-9])+(_([a-zA-Z0-9])+-([a-zA-Z0-9])+)+`\n", + "\n", + "\n", + "- **prefixes are not a thing in official BIDS names**\n", + "\n", + "BIDS has a number of [officially recognised entities](https://bids-specification.readthedocs.io/en/stable/99-appendices/04-entity-table.html) (`sub`, `ses`, `task`...) \n", + "that must come in a specific order for each data type.\n", + "\n", + "BIDS derivatives adds a few more entities (`desc`, `space`, `res`...) \n", + "and suffixes (`pseg`, `dseg`, `mask`...) \n", + "that can be used to name and describe preprocessed data.\n", + "\n", + "## Typical SPM filenames\n", + "\n", + "SPM typically adds prefixes to filenames and concatenates them.\n", + "\n", + "- `r` for realigned or resliced\n", + "- `w` for warped (often means normalized in MNI space)\n", + "- `a` for slice time corrected images\n", + "- `u` for unwarped\n", + "- `s` for smoothed\n", + "- `c1` for grey matter tissue probability maps\n", + "- ...\n", + "\n", + "## SPM to BIDS\n", + "\n", + "spm_2_bids offers a default mapping to rename the output of raw BIDS datasets preprocessed with SPM12\n", + "into BIDS compliant derivatives datasets, by suggesting a name for each file by removing prefixes and \n", + "adding the appropriate entities to be added.\n", + "\n", + "Files are not actually renamed by spm_2_bids (yet).\n" + ], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 2, + "source": [ + "run ../init_spm_2_bids.m" + ], + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Correct matlab/octave verions and added to the path!\n" + ] + } + ], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 13, + "source": [ + "input_files = { 'sub-01_T1w_seg8.mat'; ...\n", + " 'sub-01_task-auditory_bold_uw.mat'; ...\n", + " 'y_sub-01_T1w.nii'; ...\n", + " 'iy_sub-01_T1w.nii'; ...\n", + " 'y_sub-01_T2w.nii'; ...\n", + " 'susub-01_task-auditory_bold.nii'; ...\n", + " 'swuasub-01_task-auditory_bold.nii'; ...\n", + " 'c1sub-01_T1w.nii'; ... \n", + " 'c2sub-01_T1w.nii'; ... \n", + " 'c3sub-01_T1w.nii'; ... \n", + " 'msub-01_T1w.nii'; ... \n", + " 'wmsub-01_T1w.nii'; ... \n", + " 'wsub-01_T1w.nii'; ...\n", + " 'wc1sub-01_T1w.nii'; ... \n", + " 'wc2sub-01_T1w.nii'; ... \n", + " 'wc3sub-01_T1w.nii'; ... \n", + " 'asub-01_task-auditory_bold.nii'; ...\n", + " 'usub-01_task-auditory_bold.nii'; ...\n", + " 'rp_sub-01_task-auditory_bold.nii'; ... \n", + " 'rp_asub-01_task-auditory_bold.nii'; ...\n", + " 'meansub-01_task-auditory_bold.nii'; ... \n", + " 'meanusub-01_task-auditory_bold.nii'; ... \n", + " 'meanuasub-01_task-auditory_bold.nii'; ...\n", + " 'wsub-01_task-auditory_bold.nii'; ... \n", + " 'wuasub-01_task-auditory_bold.nii'; ... \n", + " 'wusub-01_task-auditory_bold.nii'; ... \n", + " 'wrsub-01_task-auditory_bold.nii'; ... \n", + " 'wrasub-01_task-auditory_bold.nii'; ...\n", + " 'wmeanusub-01_task-auditory_bold.nii'; ...\n", + " 'swsub-01_task-auditory_bold.nii'; ... \n", + " 'swuasub-01_task-auditory_bold.nii'; ... \n", + " 'swusub-01_task-auditory_bold.nii'; ... \n", + " 'swrsub-01_task-auditory_bold.nii'; ... \n", + " 'swrasub-01_task-auditory_bold.nii'; ...\n", + " 'ssub-01_task-auditory_bold.nii'; ... \n", + " 'suasub-01_task-auditory_bold.nii'; ... \n", + " 'susub-01_task-auditory_bold.nii'; ... \n", + " 'srsub-01_task-auditory_bold.nii'; ... \n", + " 'srasub-01_task-auditory_bold.nii'; ...\n", + "};\n", + " \n", + "\n", + "for i = 1:size(input_files, 1)\n", + "\n", + " filename = spm_2_bids(input_files{i, 1});\n", + " \n", + " fprintf(1, '%s\\t-->\\t%s\\n', input_files{i, 1}, filename);\n", + "\n", + "end" + ], + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "sub-01_T1w_seg8.mat\t-->\tsub-01_label-T1w_segparam.mat\n", + "sub-01_task-auditory_bold_uw.mat\t-->\tsub-01_task-auditory_label-bold_unwarpparam.mat\n", + "y_sub-01_T1w.nii\t-->\tsub-01_from-T1w_to-IXI549Space_mode-image_xfm.nii\n", + "iy_sub-01_T1w.nii\t-->\tsub-01_from-IXI549Space_to-T1w_mode-image_xfm.nii\n", + "y_sub-01_T2w.nii\t-->\tsub-01_from-T2w_to-IXI549Space_mode-image_xfm.nii\n", + "susub-01_task-auditory_bold.nii\t-->\tsub-01_task-auditory_space-individual_desc-smth_bold.nii\n", + "swuasub-01_task-auditory_bold.nii\t-->\tsub-01_task-auditory_space-IXI549Space_desc-smth_bold.nii\n", + "c1sub-01_T1w.nii\t-->\tsub-01_space-individual_label-GM_probseg.nii\n", + "c2sub-01_T1w.nii\t-->\tsub-01_space-individual_label-WM_probseg.nii\n", + "c3sub-01_T1w.nii\t-->\tsub-01_space-individual_label-CSF_probseg.nii\n", + "msub-01_T1w.nii\t-->\tsub-01_space-individual_desc-biascor_T1w.nii\n", + "wmsub-01_T1w.nii\t-->\tsub-01_space-IXI549Space_desc-preproc_T1w.nii\n", + "wsub-01_T1w.nii\t-->\tsub-01_space-IXI549Space_desc-preproc_T1w.nii\n", + "wc1sub-01_T1w.nii\t-->\tsub-01_space-IXI549Space_label-GM_probseg.nii\n", + "wc2sub-01_T1w.nii\t-->\tsub-01_space-IXI549Space_label-WM_probseg.nii\n", + "wc3sub-01_T1w.nii\t-->\tsub-01_space-IXI549Space_label-CSF_probseg.nii\n", + "asub-01_task-auditory_bold.nii\t-->\tsub-01_task-auditory_space-individual_desc-stc_bold.nii\n", + "usub-01_task-auditory_bold.nii\t-->\tsub-01_task-auditory_space-individual_desc-realignUnwarp_bold.nii\n", + "rp_sub-01_task-auditory_bold.nii\t-->\tsub-01_task-auditory_desc-confounds_regressors.tsv\n", + "rp_asub-01_task-auditory_bold.nii\t-->\tsub-01_task-auditory_desc-confounds_regressors.tsv\n", + "meansub-01_task-auditory_bold.nii\t-->\tsub-01_task-auditory_space-individual_desc-mean_bold.nii\n", + "meanusub-01_task-auditory_bold.nii\t-->\tsub-01_task-auditory_space-individual_desc-mean_bold.nii\n", + "meanuasub-01_task-auditory_bold.nii\t-->\tsub-01_task-auditory_space-individual_desc-mean_bold.nii\n", + "wsub-01_task-auditory_bold.nii\t-->\tsub-01_task-auditory_space-IXI549Space_desc-preproc_bold.nii\n", + "wuasub-01_task-auditory_bold.nii\t-->\tsub-01_task-auditory_space-IXI549Space_desc-preproc_bold.nii\n", + "wusub-01_task-auditory_bold.nii\t-->\tsub-01_task-auditory_space-IXI549Space_desc-preproc_bold.nii\n", + "wrsub-01_task-auditory_bold.nii\t-->\tsub-01_task-auditory_space-IXI549Space_desc-preproc_bold.nii\n", + "wrasub-01_task-auditory_bold.nii\t-->\tsub-01_task-auditory_space-IXI549Space_desc-preproc_bold.nii\n", + "wmeanusub-01_task-auditory_bold.nii\t-->\tsub-01_task-auditory_space-IXI549Space_desc-mean_bold.nii\n", + "swsub-01_task-auditory_bold.nii\t-->\tsub-01_task-auditory_space-IXI549Space_desc-smth_bold.nii\n", + "swuasub-01_task-auditory_bold.nii\t-->\tsub-01_task-auditory_space-IXI549Space_desc-smth_bold.nii\n", + "swusub-01_task-auditory_bold.nii\t-->\tsub-01_task-auditory_space-IXI549Space_desc-smth_bold.nii\n", + "swrsub-01_task-auditory_bold.nii\t-->\tsub-01_task-auditory_space-IXI549Space_desc-smth_bold.nii\n", + "swrasub-01_task-auditory_bold.nii\t-->\tsub-01_task-auditory_space-IXI549Space_desc-smth_bold.nii\n", + "ssub-01_task-auditory_bold.nii\t-->\tsub-01_task-auditory_space-individual_desc-smth_bold.nii\n", + "suasub-01_task-auditory_bold.nii\t-->\tsub-01_task-auditory_space-individual_desc-smth_bold.nii\n", + "susub-01_task-auditory_bold.nii\t-->\tsub-01_task-auditory_space-individual_desc-smth_bold.nii\n", + "srsub-01_task-auditory_bold.nii\t-->\tsub-01_task-auditory_space-individual_desc-smth_bold.nii\n", + "srasub-01_task-auditory_bold.nii\t-->\tsub-01_task-auditory_space-individual_desc-smth_bold.nii\n" + ] + } + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "# Override the default name map" + ], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 20, + "source": [ + "map = Mapping();\n", + "\n", + "map = map.default();\n", + "\n", + "name_spec = map.cfg.preproc_norm;\n", + "name_spec.entities.res = '1pt0';\n", + "map = map.add_mapping('prefix', 'wm', ...\n", + " 'suffix', 'T1w', ...\n", + " 'ext', '.nii', ...\n", + " 'entities', struct('desc', 'skullstripped'), ...\n", + " 'name_spec', name_spec);\n", + "\n", + "name_spec = struct('suffix', 'T1w', ...\n", + " 'ext', '.gii', ...\n", + " 'entities', struct('desc', 'pialsurf'));\n", + "map = map.add_mapping('prefix', 'c1', ...\n", + " 'suffix', 'T1w', ...\n", + " 'ext', '.surf.gii', ...\n", + " 'entities', '*', ... % allows any entity, if empty only prefix is used\n", + " 'name_spec', name_spec);\n", + "\n", + "map = map.flatten_mapping();\n" + ], + "outputs": [], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 21, + "source": [ + "input_output = {'c1sub-01_T1w.surf.gii'; ... % new mapping for surface data\n", + " 'wmsub-01_desc-skullstripped_T1w.nii'; ... % new mapping for skulltripped data\n", + " 'wmsub-01_desc-skullstripped_T2w.nii'; ... % wrong suffix: use only prefix\n", + " 'wmsub-01_desc-preproc_T1w.nii'; ... % wrong entity: use only prefix\n", + " };\n", + "\n", + "for i = 1:size(input_output, 1)\n", + "\n", + " filename = spm_2_bids(input_output{i, 1}, map);\n", + " \n", + " fprintf(1, '%s\\t-->\\t%s\\n', input_output{i, 1}, filename);\n", + "\n", + "end" + ], + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "c1sub-01_T1w.surf.gii\t-->\tsub-01_desc-pialsurf_T1w.gii\n", + "wmsub-01_desc-skullstripped_T1w.nii\t-->\tsub-01_space-IXI549Space_res-1pt0_desc-preproc_T1w.nii\n", + "wmsub-01_desc-skullstripped_T2w.nii\t-->\tsub-01_space-IXI549Space_desc-preproc_T2w.nii\n", + "wmsub-01_desc-preproc_T1w.nii\t-->\tsub-01_space-IXI549Space_desc-preproc_T1w.nii\n" + ] + } + ], + "metadata": {} + } + ], + "metadata": { + "kernelspec": { + "display_name": "Octave", + "language": "octave", + "name": "octave" + }, + "language_info": { + "file_extension": ".m", + "help_links": [ + { + "text": "GNU Octave", + "url": "https://www.gnu.org/software/octave/support.html" + }, + { + "text": "Octave Kernel", + "url": "https://github.com/Calysto/octave_kernel" + }, + { + "text": "MetaKernel Magics", + "url": "https://metakernel.readthedocs.io/en/latest/source/README.html" + } + ], + "mimetype": "text/x-octave", + "name": "octave", + "version": "4.2.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/requirements.txt b/requirements.txt index 667fbf6..84a24f5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ -Sphinx -sphinxcontrib-matlabdomain -sphinxcontrib-napoleon -sphinx_rtd_theme -miss_hit==0.9.22 -pre-commit \ No newline at end of file +miss_hit==0.9.27 +pre-commit +jupyterlab +octave_kernel +jinja2 +-r docs/requirements.txt diff --git a/run_tests.m b/run_tests.m index b458ef5..e19aa5b 100644 --- a/run_tests.m +++ b/run_tests.m @@ -1,9 +1,5 @@ % (C) Copyright 2019 spm_2_bids developers -warning('OFF'); - -spm('defaults', 'fMRI'); - folderToCover = fullfile(pwd, 'src'); testFolder = fullfile(pwd, 'tests'); diff --git a/src/README.md b/src/README.md index e413cd5..27cbbff 100644 --- a/src/README.md +++ b/src/README.md @@ -1,3 +1,3 @@ # Source code -# Main functions \ No newline at end of file +# Main functions diff --git a/src/defaults/Mapping.m b/src/defaults/Mapping.m index b631bee..89c1a56 100644 --- a/src/defaults/Mapping.m +++ b/src/defaults/Mapping.m @@ -3,28 +3,28 @@ % Creates a mapping object that will contain the list how an spm file will be renamed % into a bids derivatives. % - % Has the following attributes:: + % Has the following attributes: % - % - mapping : (n X 1) structure with the following fiels + % - mapping, (n X 1) structure with the following fields: % - % - prefix - % - suffix - % - entities - % - ext - % - name_spec: structure that must resemble the output of bids.internal.parse_filename + % - ``prefix`` + % - ``suffix`` + % - ``entities`` + % - ``ext`` + % - ``name_spec``: structure that must resemble the output of bids.File % - % - cfg : describes the common properties to be used for several names in the output. - % See ``check_cfg`` + % - cfg describes the common properties to be used for several names in the output. + % (See ``check_cfg()``) % - % - list of SPM prefixes from ``get_spm_prefix_list()`` + % - list of SPM prefixes from ``get_spm_prefix_list()``: % - % - stc = '' - % - realign = '' - % - unwarp = '' - % - coreg = '' - % - bias_cor = '' - % - norm = '' - % - smooth = '' + % - ``stc = ''`` + % - ``realign = ''`` + % - ``unwarp = ''`` + % - ``coreg = ''`` + % - ``bias_cor = ''`` + % - ``norm = ''`` + % - ``smooth = ''`` % % (C) Copyright 2021 spm_2_bids developers @@ -91,7 +91,7 @@ % map = add_mapping('prefix', prefix, 'suffix', 'entities', 'ext', 'name_spec') % - % TODO add possibility to pass "filter" arugment that is a structure + % TODO add possibility to pass "filter" argument that is a structure % with shape (allows to chain the output from bids parsing) % % filter.prefix @@ -164,31 +164,30 @@ % map = map.default; % - prfx_spec = { ... - { obj.bias_cor }, obj.cfg.segment.bias_corrected; ... + prfx_spec = {{ obj.bias_cor }, obj.cfg.segment.bias_corrected; ... { 'c1' }, obj.cfg.segment.gm; ... { 'c2' }, obj.cfg.segment.wm; ... { 'c3' }, obj.cfg.segment.csf; ... { 'iy_' }, obj.cfg.segment.deformation_field.from_mni; ... { 'y_' }, obj.cfg.segment.deformation_field.to_mni; ... - { 'segparam_' }, obj.cfg.segment.param; ... - { obj.stc, [obj.unwarp, obj.stc]}, obj.cfg.stc; ... - { 'unwarpparam_' }, obj.cfg.realign_unwarp_param; ... - { obj.unwarp }, obj.cfg.realign_unwarp; ... - { 'rp_', ['rp_' obj.stc] }, obj.cfg.real_param; ... + { 'segparam_' }, obj.cfg.segment.param; ... + { obj.stc, [obj.unwarp, obj.stc]}, obj.cfg.stc; ... + { 'unwarpparam_' }, obj.cfg.realign_unwarp_param; ... + { obj.unwarp }, obj.cfg.realign_unwarp; ... + { 'rp_', ['rp_' obj.stc] }, obj.cfg.real_param; ... { 'mean', ... ['mean' obj.unwarp], ... - ['mean' obj.unwarp, obj.stc] }, obj.cfg.mean; ... + ['mean' obj.unwarp, obj.stc] }, obj.cfg.mean; ... { obj.norm, ... [obj.norm, obj.bias_cor], ... [obj.norm, obj.unwarp, obj.stc], ... [obj.norm, obj.realign, obj.stc], ... [obj.norm, obj.unwarp], ... - [obj.norm, obj.realign] }, obj.cfg.preproc_norm; ... - { [obj.norm, 'mean', obj.unwarp] }, obj.cfg.normalized_mean; ... - { [obj.norm, 'c1'] }, obj.cfg.segment.gm_norm; ... - { [obj.norm, 'c2'] }, obj.cfg.segment.wm_norm; ... - { [obj.norm, 'c3'] }, obj.cfg.segment.csf_norm; ... + [obj.norm, obj.realign] }, obj.cfg.preproc_norm; ... + { [obj.norm, 'mean', obj.unwarp] }, obj.cfg.normalized_mean; ... + { [obj.norm, 'c1'] }, obj.cfg.segment.gm_norm; ... + { [obj.norm, 'c2'] }, obj.cfg.segment.wm_norm; ... + { [obj.norm, 'c3'] }, obj.cfg.segment.csf_norm; ... {[obj.smooth, obj.norm], ... [obj.smooth, obj.norm, obj.unwarp, obj.stc], ... [obj.smooth, obj.norm, obj.realign, obj.stc], ... @@ -198,8 +197,7 @@ [obj.smooth, obj.unwarp, obj.stc], ... [obj.smooth, obj.realign, obj.stc], ... [obj.smooth, obj.unwarp], ... - [obj.smooth, obj.realign]}, obj.cfg.smooth - }; + [obj.smooth, obj.realign]}, obj.cfg.smooth}; for i_map = 1:size(prfx_spec, 1) obj = obj.add_mapping('prefix', prfx_spec{i_map, 1}, ... diff --git a/src/defaults/check_cfg.m b/src/defaults/check_cfg.m index e12bc0a..75b8b80 100644 --- a/src/defaults/check_cfg.m +++ b/src/defaults/check_cfg.m @@ -5,7 +5,7 @@ % % USAGE:: % - % cfg = check_cfg(cfg) + % cfg = check_cfg(cfg) % % :param cfg: structure or json filename containing the spm_2_bids.anat. % :type cfg: structure @@ -14,29 +14,31 @@ % % - :cfg: the option structure with missing values filled in by the defaults. % - % ``cfg`` fields:: + % ``cfg`` fields: % - % :param entity_order: order of the entities in bids derivatives - % :param fwhm: value to append to smoothing desctiption label - % :param spec: specfication details to over ride some of the defaults + % - ``entity_order``: order of the entities in bids derivatives + % - ``fwhm``: value to append to smoothing desctiption label + % - ``spec``: specfication details to over ride some of the defaults % % BIDS derivatives defining fields: % % Each of those fields contain a structure that lists the BIDS suffix % and entities-label pairs for each type of preprocessed image. % - % :param segment: - % :param stc: - % :param realign_unwarp: - % :param real_param: - % :param mean: - % :param normalized_mean: - % :param preproc: - % :param preproc_norm: - % :param smooth: - % :param smooth_norm: + % - ``segment`` + % - ``stc`` + % - ``realign_unwarp`` + % - ``real_param`` + % - ``mean`` + % - ``normalized_mean`` + % - ``preproc`` + % - ``preproc_norm`` + % - ``smooth`` + % - ``smooth_norm`` % - % For example:: + % For example: + % + % .. code-block:: matlab % % % for grey matter segmentation output % cfg.segment.gm = struct('entities', struct('space', 'individual', ... @@ -44,8 +46,6 @@ % 'suffix', 'probseg') % % - % :mapping: a n X 2 cell that maps a prefix ``mapping{i, 1}`` to a - % to a given bids-derivatives name specification ``mapping{i, 2}`` % % (C) Copyright 2021 spm_2_bids developers diff --git a/src/defaults/get_spm_prefix_list.m b/src/defaults/get_spm_prefix_list.m index ea9fef3..9636174 100644 --- a/src/defaults/get_spm_prefix_list.m +++ b/src/defaults/get_spm_prefix_list.m @@ -5,7 +5,18 @@ % % (C) Copyright 2021 spm_2_bids developers - spm_defaults = spm_get_defaults(); + try + spm_defaults = spm_get_defaults(); + catch + spm_defaults.slicetiming.prefix = 'a'; + spm_defaults.realign.write.prefix = 'r'; + spm_defaults.unwarp.write.prefix = 'u'; + spm_defaults.coreg.write.prefix = 'r'; + spm_defaults.deformations.modulate.prefix = 'm'; + spm_defaults.normalise.write.prefix = 'w'; + spm_defaults.smooth.prefix = 's'; + end + prefix_list.stc = spm_defaults.slicetiming.prefix; prefix_list.realign = spm_defaults.realign.write.prefix; prefix_list.unwarp = spm_defaults.unwarp.write.prefix; diff --git a/src/spm_2_bids.m b/src/spm_2_bids.m index fa8bf35..a3b435a 100644 --- a/src/spm_2_bids.m +++ b/src/spm_2_bids.m @@ -34,29 +34,23 @@ mapping = map.mapping; cfg = map.cfg; - use_suffix_as_label = false; - % deal with suffixes modified by SPM % turns them into prefixes that can be handled by the default mapping - if strfind(file, '_uw.mat') %#ok<*STRIFCND> - file = spm_file(file, 'prefix', 'unwarpparam_'); - file = strrep(file, '_uw.mat', '.mat'); - use_suffix_as_label = true; - end - if strfind(file, '_seg8.mat') %#ok<*STRIFCND> - file = spm_file(file, 'prefix', 'segparam_'); - file = strrep(file, '_seg8.mat', '.mat'); + use_suffix_as_label = false; + [file, status(1)] = turn_spm_suffix_in_prefix(file, '_uw.mat', 'unwarpparam_'); + [file, status(2)] = turn_spm_suffix_in_prefix(file, '_seg8.mat', 'segparam_'); + if any(status) use_suffix_as_label = true; end - pth = spm_fileparts(file); - new_filename = spm_file(file, 'filename'); - json = []; + bf = bids.File(file, 'use_schema', false); + pth = bf.bids_path; + new_filename = bf.filename; - p = bids.internal.parse_filename(file); + json = []; % TO DO allow renaming even if there is no prefix ? - if isempty(p.prefix) + if isempty(bf.prefix) return end @@ -66,7 +60,7 @@ % the right mapping % look for the right prefix in the mapping - prefix_match = map.find_mapping('prefix', p.prefix); + prefix_match = map.find_mapping('prefix', bf.prefix); % TODO implement methods in Mapping to filter by suffix / extention / % entities @@ -75,12 +69,12 @@ % if none is mentioned anywhere in the mapping then anything goes suffix_match = true(size(mapping)); if ~all(cellfun('isempty', {mapping.suffix}')) - suffix_match = any([strcmp({mapping.suffix}', p.suffix), ... + suffix_match = any([strcmp({mapping.suffix}', bf.suffix), ... strcmp({mapping.suffix}', '*')], 2); end ext_match = true(size(mapping)); if ~all(cellfun('isempty', {mapping.ext}')) - ext_match = any([strcmp({mapping.ext}', p.ext), ... + ext_match = any([strcmp({mapping.ext}', bf.extension), ... strcmp({mapping.ext}', '*')], 2); end @@ -96,7 +90,7 @@ idx = find(needs_entity_check); for i = 1:numel(idx) - status = check_field_content(p.entities, mapping(idx(i)).entities); + status = check_field_content(bf.entities, mapping(idx(i)).entities); entitiy_match(idx(i)) = status; end end @@ -119,60 +113,78 @@ if isempty(spec) % TODO this warning should probably go in the find_mapping methods - msg = sprintf('Unknown prefix: %s', p.prefix); + msg = sprintf('Unknown prefix: %s', bf.prefix); warning('spm_2_bids:unknownPrefix', msg); %#ok return end spec = add_fwhm_to_smooth_label(spec, cfg); - spec = adapt_from_label_to_input(spec, p); + spec = adapt_from_label_to_input(spec, bf); if use_suffix_as_label - spec.entities.label = p.suffix; + spec.entities.label = bf.suffix; end spec = use_config_spec(spec, cfg); - overwrite = true; - spec.prefix = ''; - spec.use_schema = false; - p = set_missing_fields(p, spec, overwrite); + bf = update_filename(bf, spec, cfg); - p = reorder_entities(p, cfg); + % arg out + pth = bf.bids_path; - [new_filename, pth, json] = bids.create_filename(p, file); + new_filename = bf.filename; - % TODO update json content - p = bids.internal.parse_filename(file); - json.content.RawSources{1} = strrep(p.filename, p.prefix, ''); + json = bids.derivatives_json(bf.filename); + json.content.RawSources{1} = strrep(bf.filename, bf.prefix, ''); end -function spec = add_fwhm_to_smooth_label(spec, cfg) +function bf = update_filename(bf, spec, cfg) + + bf.prefix = ''; + if isfield(spec, 'suffix') + bf.suffix = spec.suffix; + end + if isfield(spec, 'ext') + bf.extension = spec.ext; + end + if isfield(spec, 'entities') + entities = fieldnames(spec.entities); + for i = 1:numel(entities) + bf = bf.set_entity(entities{i}, spec.entities.(entities{i})); + end + end + + bf = reorder_entities(bf, cfg); + bf = bf.update; + +end + +function bf = add_fwhm_to_smooth_label(bf, cfg) % % adds the FWHM to the description label for smoothing % - if isfield(spec, 'entities') && ... - isfield(spec.entities, 'desc') && ... - strcmp(spec.entities.desc, 'smth') && ... + if isfield(bf, 'entities') && ... + isfield(bf.entities, 'desc') && ... + strcmp(bf.entities.desc, 'smth') && ... ~isempty(cfg.fwhm) - spec.entities.desc = sprintf('smth%i', cfg.fwhm); + bf.entities.desc = sprintf('smth%i', cfg.fwhm); end end -function spec = adapt_from_label_to_input(spec, p) +function spec = adapt_from_label_to_input(spec, bf) % % for deformation fields % - if strcmp(p.prefix, 'y_') - spec.entities.from = p.suffix; + if strcmp(bf.prefix, 'y_') + spec.entities.from = bf.suffix; spec.entities = orderfields(spec.entities, {'from', 'to', 'mode'}); - elseif strcmp(p.prefix, 'iy_') - spec.entities.to = p.suffix; + elseif strcmp(bf.prefix, 'iy_') + spec.entities.to = bf.suffix; spec.entities = orderfields(spec.entities, {'from', 'to', 'mode'}); end @@ -198,14 +210,14 @@ end -function p = reorder_entities(p, cfg) +function bf = reorder_entities(bf, cfg) % % put entity from raw bids before those of derivatives % and make sure that derivatives entities are in the right order % % - entities = fieldnames(p.entities); + entities = fieldnames(bf.entities); is_raw_entity = ~ismember(entities, cfg.entity_order); @@ -215,6 +227,18 @@ entities(~is_raw_entity)); derivative_entities = cfg.entity_order(derivative_entities_present); - p.entity_order = cat(1, raw_entities, derivative_entities); + bf = bf.reorder_entities(cat(1, raw_entities, derivative_entities)); + +end +function [filename, status] = turn_spm_suffix_in_prefix(filename, pattern, string) + status = false; + + if strfind(filename, pattern) %#ok<*STRIFCND> + filename = bids.internal.file_utils(filename, 'prefix', string); + filename = strrep(filename, ... + pattern, ... + ['.' bids.internal.file_utils(filename, 'ext')]); + status = true; + end end diff --git a/src/utils/check_field_content.m b/src/utils/check_field_content.m index 1faa1c8..8796680 100644 --- a/src/utils/check_field_content.m +++ b/src/utils/check_field_content.m @@ -1,18 +1,18 @@ -function status = check_field_content(struct_1, struct_2) +function status = check_field_content(struct_one, struct_two) % % (C) Copyright 2021 spm_2_bids developers status = true; - if isempty(struct_2) + if isempty(struct_two) return end - if strcmp(struct_2, '*') + if strcmp(struct_two, '*') return end - shared_fields = intersect(fieldnames(struct_1), fieldnames(struct_2)); + shared_fields = intersect(fieldnames(struct_one), fieldnames(struct_two)); if isempty(shared_fields) status = false; @@ -20,7 +20,7 @@ end for i = 1:numel(shared_fields) - if ~strcmp(struct_1.(shared_fields{i}), struct_2.(shared_fields{i})) + if ~strcmp(struct_one.(shared_fields{i}), struct_two.(shared_fields{i})) status = false; return end diff --git a/tests/test_mapping.m b/tests/test_mapping.m index 5f25a9e..42c35ce 100644 --- a/tests/test_mapping.m +++ b/tests/test_mapping.m @@ -39,7 +39,7 @@ function test_find_mapping() map = map.default(); idx = map.find_mapping('prefix', 'rp_'); - assertEqual(find(idx), 10); + assertEqual(find(idx), 12); end diff --git a/tests/test_spm_2_bids.m b/tests/test_spm_2_bids.m index e224a11..92095cf 100644 --- a/tests/test_spm_2_bids.m +++ b/tests/test_spm_2_bids.m @@ -8,11 +8,21 @@ initTestSuite; end +function test_spm_2_bids_order_entities() + + file = 'wmsub-01_desc-skullstripped_T1w.nii'; + new_filename = spm_2_bids(file); + assertEqual(new_filename, 'sub-01_space-IXI549Space_desc-preproc_T1w.nii'); + +end + function test_spm_2_bids_suffix() input_output = { - 'sub-01_T1w_seg8.mat', 'sub-01_label-T1w_segparam.mat' - 'sub-01_task-auditory_bold_uw.mat', 'sub-01_task-auditory_label-bold_unwarpparam.mat'}; + 'sub-01_T1w_seg8.mat', ... + 'sub-01_label-T1w_segparam.mat' + 'sub-01_task-auditory_bold_uw.mat', ... + 'sub-01_task-auditory_label-bold_unwarpparam.mat'}; for i = 1:numel(size(input_output, 1)) @@ -72,14 +82,6 @@ function test_spm_2_bids_new_mapping() end -function test_spm_2_bids_order_entities() - - file = 'wmsub-01_desc-skullstripped_T1w.nii'; - new_filename = spm_2_bids(file); - assertEqual(new_filename, 'sub-01_space-IXI549Space_desc-preproc_T1w.nii'); - -end - function test_spm_2_bids_no_prefix() file = 'sub-01_ses-02_T1w.nii'; @@ -90,6 +92,10 @@ function test_spm_2_bids_no_prefix() function test_spm_2_bids_unknown_prefix() + if is_octave() + return + end + file = 'wtfsub-01_ses-02_T1w.nii'; assertWarning( ... @()spm_2_bids(file), ... diff --git a/version.txt b/version.txt index 9ff151c..b82608c 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -v0.1.0 \ No newline at end of file +v0.1.0