From f8f64bb6a6d9f9aefa98732460a71e336b30d851 Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Sat, 11 Mar 2023 21:02:24 -0500 Subject: [PATCH] ENH: Remove ICA-AROMA from workflow and docs --- .circleci/config.yml | 2 - ...ds005_legacy_partial_fasttrack_outputs.txt | 4 - .circleci/ds005_legacy_partial_outputs.txt | 4 - .vscode/launch.json | 3 +- Dockerfile | 8 - REFERENCES.md | 1 - docs/_static/sbatch.slurm | 2 +- docs/faq.rst | 2 +- docs/installation.rst | 1 - docs/outputs.rst | 53 +-- docs/spaces.rst | 6 +- docs/usage.rst | 2 +- docs/workflows.rst | 52 +-- fmriprep/cli/parser.py | 12 +- fmriprep/cli/run.py | 13 +- fmriprep/config.py | 4 - fmriprep/data/boilerplate.bib | 14 - fmriprep/data/reports-spec.yml | 7 - fmriprep/interfaces/confounds.py | 109 ------ fmriprep/tests/test_config.py | 12 +- fmriprep/utils/bids.py | 1 - fmriprep/workflows/bold/__init__.py | 3 +- fmriprep/workflows/bold/base.py | 81 ----- fmriprep/workflows/bold/confounds.py | 317 ------------------ fmriprep/workflows/bold/outputs.py | 50 --- .../workflows/bold/tests/test_confounds.py | 70 ---- 26 files changed, 39 insertions(+), 794 deletions(-) delete mode 100644 fmriprep/workflows/bold/tests/test_confounds.py diff --git a/.circleci/config.yml b/.circleci/config.yml index 84b73c314..5c0ddcba6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -551,7 +551,6 @@ jobs: mkdir -p /tmp/${DATASET}/derivatives_partial sudo cp -a /tmp/${DATASET}/work /tmp/${DATASET}/work_partial sudo cp -a /tmp/${DATASET}/work /tmp/${DATASET}/work_bids - sudo rm -rf /tmp/${DATASET}/work_partial/fmriprep_*_wf/single_subject_01_wf/func_preproc_task_mixedgamblestask_run_02_wf/ica_aroma_wf # Nipype sometimes fails to pick up when the base directory changes # This is a cheap workflow, so let's not fuss sudo rm -rf /tmp/${DATASET}/work /tmp/${DATASET}/work_bids/fmriprep_*_wf/single_subject_01_wf/anat_preproc_wf/surface_recon_wf/gifti_surface_wf || true @@ -608,7 +607,6 @@ jobs: --output-layout legacy \ --sloppy --write-graph --use-syn-sdc --mem-mb 14336 \ --output-spaces MNI152NLin2009cAsym fsaverage5 fsnative MNI152NLin6Asym anat \ - --aroma-melodic-dimensionality 2 --use-aroma \ --nthreads 4 --cifti-output --project-goodvoxels -vv - store_artifacts: path: /tmp/ds005/derivatives_partial diff --git a/.circleci/ds005_legacy_partial_fasttrack_outputs.txt b/.circleci/ds005_legacy_partial_fasttrack_outputs.txt index 95fa6ee67..a864b6cd2 100644 --- a/.circleci/ds005_legacy_partial_fasttrack_outputs.txt +++ b/.circleci/ds005_legacy_partial_fasttrack_outputs.txt @@ -17,11 +17,9 @@ fmriprep/sub-01/fmap/sub-01_run-02_fmapid-auto00000_desc-magnitude_fieldmap.nii. fmriprep/sub-01/fmap/sub-01_run-02_fmapid-auto00000_desc-preproc_fieldmap.json fmriprep/sub-01/fmap/sub-01_run-02_fmapid-auto00000_desc-preproc_fieldmap.nii.gz fmriprep/sub-01/func -fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_AROMAnoiseICs.csv fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_boldref.nii.gz fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_desc-confounds_timeseries.json fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_desc-confounds_timeseries.tsv -fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_desc-MELODIC_mixing.tsv fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_from-scanner_to-boldref_mode-image_xfm.txt fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_from-scanner_to-T1w_mode-image_xfm.txt fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_from-T1w_to-scanner_mode-image_xfm.txt @@ -49,8 +47,6 @@ fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_space-MNI152NLin6Asym_d fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_space-MNI152NLin6Asym_desc-brain_mask.nii.gz fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_space-MNI152NLin6Asym_desc-preproc_bold.json fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_space-MNI152NLin6Asym_desc-preproc_bold.nii.gz -fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_space-MNI152NLin6Asym_desc-smoothAROMAnonaggr_bold.json -fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_space-MNI152NLin6Asym_desc-smoothAROMAnonaggr_bold.nii.gz fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_space-T1w_boldref.nii.gz fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_space-T1w_desc-aparcaseg_dseg.nii.gz fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_space-T1w_desc-aseg_dseg.nii.gz diff --git a/.circleci/ds005_legacy_partial_outputs.txt b/.circleci/ds005_legacy_partial_outputs.txt index cdb2c813b..272a83529 100644 --- a/.circleci/ds005_legacy_partial_outputs.txt +++ b/.circleci/ds005_legacy_partial_outputs.txt @@ -71,11 +71,9 @@ fmriprep/sub-01/fmap/sub-01_run-02_fmapid-auto00000_desc-magnitude_fieldmap.nii. fmriprep/sub-01/fmap/sub-01_run-02_fmapid-auto00000_desc-preproc_fieldmap.json fmriprep/sub-01/fmap/sub-01_run-02_fmapid-auto00000_desc-preproc_fieldmap.nii.gz fmriprep/sub-01/func -fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_AROMAnoiseICs.csv fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_boldref.nii.gz fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_desc-confounds_timeseries.json fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_desc-confounds_timeseries.tsv -fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_desc-MELODIC_mixing.tsv fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_from-scanner_to-boldref_mode-image_xfm.txt fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_from-scanner_to-T1w_mode-image_xfm.txt fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_from-T1w_to-scanner_mode-image_xfm.txt @@ -103,8 +101,6 @@ fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_space-MNI152NLin6Asym_d fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_space-MNI152NLin6Asym_desc-brain_mask.nii.gz fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_space-MNI152NLin6Asym_desc-preproc_bold.json fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_space-MNI152NLin6Asym_desc-preproc_bold.nii.gz -fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_space-MNI152NLin6Asym_desc-smoothAROMAnonaggr_bold.json -fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_space-MNI152NLin6Asym_desc-smoothAROMAnonaggr_bold.nii.gz fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_space-T1w_boldref.nii.gz fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_space-T1w_desc-aparcaseg_dseg.nii.gz fmriprep/sub-01/func/sub-01_task-mixedgamblestask_run-02_space-T1w_desc-aseg_dseg.nii.gz diff --git a/.vscode/launch.json b/.vscode/launch.json index 000f222fb..a0e812210 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -34,7 +34,6 @@ "/tmp/ds005/derivatives", "participant", "--sloppy", "--write-graph", - "--use-aroma", "--skull-strip-template", "OASIS30ANTs:res-1", "--output-space", "T1w", "template", "fsaverage5", "fsnative", "--template-resampling-grid", "native", @@ -98,4 +97,4 @@ "justMyCode": false } ] -} \ No newline at end of file +} diff --git a/Dockerfile b/Dockerfile index 8cdbb0b14..d353f81d5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -223,14 +223,6 @@ WORKDIR $ANTSPATH RUN curl -sSL "https://dl.dropbox.com/s/gwf51ykkk5bifyj/ants-Linux-centos6_x86_64-v2.3.4.tar.gz" \ | tar -xzC $ANTSPATH --strip-components 1 -# Installing and setting up ICA_AROMA -WORKDIR /opt/ICA-AROMA -RUN curl -sSL "https://github.com/oesteban/ICA-AROMA/archive/v0.4.5.tar.gz" \ - | tar -xzC /opt/ICA-AROMA --strip-components 1 && \ - chmod +x /opt/ICA-AROMA/ICA_AROMA.py -ENV PATH="/opt/ICA-AROMA:$PATH" \ - AROMA_VERSION="0.4.5" - WORKDIR /opt RUN curl -sSLO https://www.humanconnectome.org/storage/app/media/workbench/workbench-linux64-v1.5.0.zip && \ unzip workbench-linux64-v1.5.0.zip && \ diff --git a/REFERENCES.md b/REFERENCES.md index d8209b3fc..86c5084d6 100644 --- a/REFERENCES.md +++ b/REFERENCES.md @@ -17,7 +17,6 @@ | MCFLIRT | https://doi.org/10.1006/nimg.2002.1132 | https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/MCFLIRT | | SUSAN | https://doi.org/10.1023/A:1007963824710 | https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/SUSAN | | MELODIC | | https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/MELODIC | -| ICA-AROMA | http://www.sciencedirect.com/science/article/pii/S1053811915001822 | https://github.com/rhr-pruim/ICA-AROMA/ | | PRELUDE & FUGUE | https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FUGUE/Guide | https://nipype.readthedocs.io/en/latest/interfaces/generated/workflows.dmri/fsl.utils.html#cleanup-edge-pipeline | | **AFNI** | | https://doi.org/10.1006/cbmr.1996.0014; https://doi.org/10.1016/j.neuroimage.2011.08.056 | 3dvolreg | | https://afni.nimh.nih.gov/pub/dist/doc/program_help/3dvolreg.html | diff --git a/docs/_static/sbatch.slurm b/docs/_static/sbatch.slurm index 0e8b65469..60c0affc3 100644 --- a/docs/_static/sbatch.slurm +++ b/docs/_static/sbatch.slurm @@ -40,7 +40,7 @@ subject=$( sed -n -E "$((${SLURM_ARRAY_TASK_ID} + 1))s/sub-(\S*)\>.*/\1/gp" ${BI find ${LOCAL_FREESURFER_DIR}/sub-$subject/ -name "*IsRunning*" -type f -delete # Compose the command line -cmd="${SINGULARITY_CMD} /data /data/${DERIVS_DIR} participant --participant-label $subject -w /work/ -vv --omp-nthreads 8 --nthreads 12 --mem_mb 30000 --output-spaces MNI152NLin2009cAsym:res-2 anat fsnative fsaverage5 --use-aroma --fs-subjects-dir /fsdir" +cmd="${SINGULARITY_CMD} /data /data/${DERIVS_DIR} participant --participant-label $subject -w /work/ -vv --omp-nthreads 8 --nthreads 12 --mem_mb 30000 --output-spaces MNI152NLin2009cAsym:res-2 anat fsnative fsaverage5 --fs-subjects-dir /fsdir" # Setup done, run the command echo Running task ${SLURM_ARRAY_TASK_ID} diff --git a/docs/faq.rst b/docs/faq.rst index 95a635818..5c54a360c 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -202,7 +202,7 @@ In addition to the ``--output-spaces`` that you specify, *fMRIPrep* will internally require the ``MNI152NLin2009cAsym`` template. If the ``--skull-strip-template`` option is not set, then ``OASIS30ANTs`` will be used. -Finally, both the ``--cifti-output`` and ``--use-aroma`` arguments require ``MNI152NLin6Asym``. +Finally, the ``--cifti-output`` argument requires ``MNI152NLin6Asym``. To do so, follow the next steps. 1. By default, a mirror of *TemplateFlow* to store the resources will be diff --git a/docs/installation.rst b/docs/installation.rst index 40541733c..e912f2fab 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -80,7 +80,6 @@ the ``fmriprep`` package: - AFNI_ (version 22.3.06) - `C3D `_ (version 1.3.0) - FreeSurfer_ (version 7.3.2) -- `ICA-AROMA `_ (version 0.4.5) - `bids-validator `_ (version 1.8.0) - `connectome-workbench `_ (version 1.5.0) diff --git a/docs/outputs.rst b/docs/outputs.rst index 32b7219ca..9df8a4acd 100644 --- a/docs/outputs.rst +++ b/docs/outputs.rst @@ -30,10 +30,9 @@ upcoming `BEP 011`_ and `BEP 012`_). .. important:: In order to remain agnostic to any possible subsequent analysis, *fMRIPrep* does not perform any denoising (e.g., spatial smoothing) itself. - There are two exceptions to this principle (described in their corresponding - sections below): + There are exceptions to this principle (described in its corresponding + section below): - - ICA-AROMA's *non-aggressive* denoised outputs, and - CompCor regressors, which are calculated after temporal high-pass filtering. Layout @@ -228,19 +227,11 @@ These :abbr:`TSV (tab-separated values)` tables look like the example below, where each row of the file corresponds to one time point found in the corresponding :abbr:`BOLD (blood-oxygen level dependent)` time series:: - csf white_matter global_signal std_dvars dvars framewise_displacement t_comp_cor_00 t_comp_cor_01 t_comp_cor_02 t_comp_cor_03 t_comp_cor_04 t_comp_cor_05 a_comp_cor_00 a_comp_cor_01 a_comp_cor_02 a_comp_cor_03 a_comp_cor_04 a_comp_cor_05 non_steady_state_outlier00 trans_x trans_y trans_z rot_x rot_y rot_z aroma_motion_02 aroma_motion_04 - 682.75275 0.0 491.64752000000004 n/a n/a n/a 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 -0.00017029 -0.0 0.0 0.0 0.0 - 669.14166 0.0 489.4421 1.168398 17.575331 0.07211929999999998 -0.4506846719 0.1191909139 -0.0945884724 0.1542023065 -0.2302324641 0.0838194238 -0.032426848599999995 0.4284323184 -0.5809158299 0.1382414008 -0.1203486637 0.3783661265 0.0 0.0 0.0207752 0.0463124 -0.000270924 -0.0 0.0 -2.402958171 -0.7574011893 - 665.3969 0.0 488.03 1.085204 16.323903999999995 0.0348966 0.010819676200000001 0.0651895837 -0.09556632150000001 -0.033148835 -0.4768871111 0.20641088559999998 0.2818768463 0.4303863764 0.41323714850000004 -0.2115232212 -0.0037154909000000004 0.10636180070000001 0.0 0.0 0.0 0.0457372 0.0 -0.0 0.0 -1.341359143 0.1636017242 - 662.82715 0.0 487.37302 1.01591 15.281561 0.0333937 0.3328022893 -0.2220965269 -0.0912891436 0.2326688125 0.279138129 -0.111878887 0.16901660629999998 0.0550480212 0.1798747037 -0.25383302620000003 0.1646403629 0.3953613889 0.0 0.010164 -0.0103568 0.0424513 0.0 -0.0 0.00019174 -0.1554834655 0.6451987913 - -Finally, if ICA-AROMA is used, the MELODIC mixing matrix and the components classified as noise -are saved:: - - sub-/ - func/ - sub-_[specifiers]_AROMAnoiseICs.csv - sub-_[specifiers]_desc-MELODIC_mixing.tsv + csf white_matter global_signal std_dvars dvars framewise_displacement t_comp_cor_00 t_comp_cor_01 t_comp_cor_02 t_comp_cor_03 t_comp_cor_04 t_comp_cor_05 a_comp_cor_00 a_comp_cor_01 a_comp_cor_02 a_comp_cor_03 a_comp_cor_04 a_comp_cor_05 non_steady_state_outlier00 trans_x trans_y trans_z rot_x rot_y rot_z + 682.75275 0.0 491.64752000000004 n/a n/a n/a 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 -0.00017029 -0.0 0.0 + 669.14166 0.0 489.4421 1.168398 17.575331 0.07211929999999998 -0.4506846719 0.1191909139 -0.0945884724 0.1542023065 -0.2302324641 0.0838194238 -0.032426848599999995 0.4284323184 -0.5809158299 0.1382414008 -0.1203486637 0.3783661265 0.0 0.0 0.0207752 0.0463124 -0.000270924 -0.0 0.0 + 665.3969 0.0 488.03 1.085204 16.323903999999995 0.0348966 0.010819676200000001 0.0651895837 -0.09556632150000001 -0.033148835 -0.4768871111 0.20641088559999998 0.2818768463 0.4303863764 0.41323714850000004 -0.2115232212 -0.0037154909000000004 0.10636180070000001 0.0 0.0 0.0 0.0457372 0.0 -0.0 0.0 + 662.82715 0.0 487.37302 1.01591 15.281561 0.0333937 0.3328022893 -0.2220965269 -0.0912891436 0.2326688125 0.279138129 -0.111878887 0.16901660629999998 0.0550480212 0.1798747037 -0.25383302620000003 0.1646403629 0.3953613889 0.0 0.010164 -0.0103568 0.0424513 0.0 -0.0 0.00019174 **Multi-echo derivatives**. For multi-echo datasets, the output ``_bold`` series are "optimally combined" by @@ -529,31 +520,6 @@ the brain's outer edge (*crown*) mask. The procedure essentially follows the initial proposal of the approach by Patriat et al. [Patriat2017]_ and is described in our ISMRM abstract [Provins2022]_. -**AROMA confounds**. -:abbr:`AROMA (Automatic Removal Of Motion Artifacts)` is an :abbr:`ICA (independent components analysis)` -based procedure to identify confounding time series related to head-motion [Prium2015]_. -ICA-AROMA can be enabled with the flag ``--use-aroma``. - -- ``aroma_motion_XX`` - the motion-related components identified by ICA-AROMA. - -.. danger:: - If you are already using AROMA-cleaned data (``~desc-smoothAROMAnonaggr_bold.nii.gz``), - do not include ICA-AROMA confounds during your design specification or denoising procedure. - - Additionally, as per [Hallquist2013]_ and [Lindquist2019]_, when using AROMA-cleaned data - most of the confound regressors should be recalculated (this feature is a work-in-progress, - follow up on `#1905 `__). - Surprisingly, `our simulations - `__ - (with thanks to JD. Kent) suggest that using the confounds as currently calculated by - *fMRIPrep* --before denoising-- would be just fine. - -.. caution:: - *Nonsteady-states* (or *dummy scans*) in the beginning of every run - are dropped **before** ICA-AROMA is performed. - Therefore, any subsequent analysis of ICA-AROMA outputs must drop the same - number of *nonsteady-states*. - Confounds and "carpet"-plot on the visual reports ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The visual reports provide several sections per task and run to aid designing @@ -698,11 +664,6 @@ the following invocation:: .. [Power2016] Power JD, A simple but useful way to assess fMRI scan qualities. NeuroImage. 2016. doi:`10.1016/j.neuroimage.2016.08.009 `_ - .. [Prium2015] Pruim RHR, Mennes M, van Rooij D, Llera A, Buitelaar JK, Beckmann CF. - ICA-AROMA: A robust ICA-based strategy for removing motion artifacts from fMRI data. - Neuroimage. 2015 May 15;112:267–77. - doi:`10.1016/j.neuroimage.2015.02.064 `_. - .. [Provins2022] Provins C et al., Quality control and nuisance regression of fMRI, looking out where signal should not be found. Proc. Intl. Soc. Mag. Reson. Med. 31, London (UK). 2022 doi:`10.31219/osf.io/hz52v `_. diff --git a/docs/spaces.rst b/docs/spaces.rst index 12a1992e8..738692875 100644 --- a/docs/spaces.rst +++ b/docs/spaces.rst @@ -138,11 +138,11 @@ Modifiers are not allowed when providing nonstandard spaces. Preprocessing blocks depending on standard templates """""""""""""""""""""""""""""""""""""""""""""""""""" -Some modules of the pipeline (e.g., the ICA-AROMA denoising, the generation of -HCP compatible *grayordinates* files, or the *fieldmap-less* distortion correction) +Some modules of the pipeline (e.g., the generation of HCP compatible +*grayordinates* files, or the *fieldmap-less* distortion correction) operate in specific template spaces. When selecting those modules to be included (using any of the following flags: -``--use-aroma``, ``--cifti-outputs``, ``--use-syn-sdc``) will modify the list of +``--cifti-outputs``, ``--use-syn-sdc``) will modify the list of *internal* spaces to include the space identifiers they require, should the identifier not be found within the ``--output-spaces`` list already. In other words, running *fMRIPrep* with ``--output-spaces MNI152NLin6Asym:res-2 diff --git a/docs/usage.rst b/docs/usage.rst index 942040658..bc72f2668 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -180,7 +180,7 @@ Because processing this emerging type of datasets (*densely sampled neuroimaging .. attention:: When the intention is to combine the *anatomical fast-track* with some advanced options that involve - standard spaces (e.g., ``--use-aroma`` or ``--cifti-output``), please make sure you include the + standard spaces (e.g., ``--cifti-output``), please make sure you include the ``MNI152NLin6Asym`` space to the ``--output-spaces`` list in the first invocation of *fMRIPrep* (or *sMRIPrep*) from which the results are to be reused. Otherwise, a warning message indicating that *fMRIPrep*'s expectations were not met will be issued, diff --git a/docs/workflows.rst b/docs/workflows.rst index 30561612a..d5da471f0 100644 --- a/docs/workflows.rst +++ b/docs/workflows.rst @@ -567,56 +567,8 @@ segmentation, the `discover_wf` sub-workflow calculates potential confounds per volume. Calculated confounds include the mean global signal, mean tissue class signal, -tCompCor, aCompCor, Frame-wise Displacement, 6 motion parameters, DVARS, spike regressors, -and, if the ``--use-aroma`` flag is enabled, the noise components identified by ICA-AROMA -(those to be removed by the "aggressive" denoising strategy). -Particular details about ICA-AROMA are given below. - -ICA-AROMA -~~~~~~~~~ -:py:func:`~fmriprep.workflows.bold.confounds.init_ica_aroma_wf` - -ICA-AROMA denoising is performed in ``MNI152NLin6Asym`` space, which is automatically -added to the list of ``--output-spaces`` if it was not already requested by the user. -The number of ICA-AROMA components depends on a dimensionality estimate made by -FSL MELODIC. -For datasets with a very short TR and a large number of timepoints, this may result -in an unusually high number of components. -By default, dimensionality is limited to a maximum of 200 components. -To override this upper limit one may specify the number of components to be extracted -with ``--aroma-melodic-dimensionality``. -Further details on the implementation are given within the workflow generation -function (:py:func:`~fmriprep.workflows.bold.confounds.init_ica_aroma_wf`). - -*Note*: *non*-aggressive AROMA denoising is a fundamentally different procedure -from its "aggressive" counterpart and cannot be performed only by using a set of noise -regressors (a separate GLM with both noise and signal regressors needs to be used). -Therefore instead of regressors, *fMRIPrep* produces *non*-aggressive denoised 4D NIFTI -files in the MNI space: - -``*space-MNI152NLin6Asym_desc-smoothAROMAnonaggr_bold.nii.gz`` - -Additionally, the MELODIC mix and noise component indices will -be generated, so non-aggressive denoising can be manually performed in the T1w space with ``fsl_regfilt``, *e.g.*:: - - fsl_regfilt -i sub-_task-_space-T1w_desc-preproc_bold.nii.gz \ - -f $(cat sub-_task-_AROMAnoiseICs.csv) \ - -d sub-_task-_desc-MELODIC_mixing.tsv \ - -o sub-_task-_space-T1w_desc-AROMAnonaggr_bold.nii.gz - -*Note*: The non-steady state volumes are removed for the determination of components in melodic. -Therefore ``*MELODIC_mixing.tsv`` may have zero padded rows to account for the volumes not used in -melodic's estimation of components. - -A visualization of the AROMA component classification is also included in the HTML reports. - -.. figure:: _static/aroma.svg - - Maps created with maximum intensity projection (glass brain) with a black - brain outline. - Right hand side of each map: time series (top in seconds), - frequency spectrum (bottom in Hertz). - Components classified as signal in green; noise in red. +tCompCor, aCompCor, Frame-wise Displacement, 6 motion parameters, DVARS, and +spike regressors. .. _bold_t2s: diff --git a/fmriprep/cli/parser.py b/fmriprep/cli/parser.py index 62ab432d3..433836bbc 100644 --- a/fmriprep/cli/parser.py +++ b/fmriprep/cli/parser.py @@ -413,29 +413,27 @@ def _slice_time_ref(value, parser): "(default is 91k, which equates to 2mm resolution)", ) - g_aroma = parser.add_argument_group("Specific options for running ICA_AROMA") + g_aroma = parser.add_argument_group("[DEPRECATED] Options for running ICA_AROMA") g_aroma.add_argument( "--use-aroma", action="store_true", default=False, - help="Add ICA-AROMA to your preprocessing stream", + help="Deprecated. Will raise an error in 24.0.", ) g_aroma.add_argument( "--aroma-melodic-dimensionality", dest="aroma_melodic_dim", action="store", - default=-200, + default=0, type=int, - help="Exact or maximum number of MELODIC components to estimate " - "(positive = exact, negative = maximum)", + help="Deprecated. Will raise an error in 24.0.", ) g_aroma.add_argument( "--error-on-aroma-warnings", action="store_true", dest="aroma_err_on_warn", default=False, - help="Raise an error if ICA_AROMA does not produce sensible output " - "(e.g., if all the components are classified as signal or noise)", + help="Deprecated. Will raise an error in 24.0.", ) g_confounds = parser.add_argument_group("Options relating to confounds") diff --git a/fmriprep/cli/run.py b/fmriprep/cli/run.py index 60000a360..57d6f2dec 100644 --- a/fmriprep/cli/run.py +++ b/fmriprep/cli/run.py @@ -43,8 +43,17 @@ def main(): parse_args() # Deprecated flags - if config.workflow.use_aroma: - warnings.warn("ICA-AROMA support will be removed in fMRIPrep 23.1.0", FutureWarning) + if any( + ( + config.workflow.use_aroma, + config.workflow.aroma_err_on_warn, + config.workflow.aroma_melodic_dim, + ) + ): + warnings.warn( + "ICA-AROMA was removed in fMRIPrep 23.1.0. The --use-aroma, --aroma-err-on-warn, " + "and --aroma-melodic-dim flags will error in fMRIPrep 24.0.0." + ) # Code Carbon if config.execution.track_carbon: diff --git a/fmriprep/config.py b/fmriprep/config.py index be7af3e96..c7b95a6f3 100644 --- a/fmriprep/config.py +++ b/fmriprep/config.py @@ -761,10 +761,6 @@ def init_spaces(checkpoint=True): # Ensure user-defined spatial references for outputs are correctly parsed. # Certain options require normalization to a space not explicitly defined by users. # These spaces will not be included in the final outputs. - if workflow.use_aroma: - # Make sure there's a normalization to FSL for AROMA to use. - spaces.add(Reference("MNI152NLin6Asym", {"res": "2"})) - cifti_output = workflow.cifti_output if cifti_output: # CIFTI grayordinates to corresponding FSL-MNI resolutions. diff --git a/fmriprep/data/boilerplate.bib b/fmriprep/data/boilerplate.bib index c3b17b7c4..341dcbba0 100644 --- a/fmriprep/data/boilerplate.bib +++ b/fmriprep/data/boilerplate.bib @@ -214,20 +214,6 @@ @article{bbr year = 2009 } -@article{aroma, - author = {Pruim, Raimon H. R. and Mennes, Maarten and van Rooij, Daan and Llera, Alberto and Buitelaar, Jan K. and Beckmann, Christian F.}, - doi = {10.1016/j.neuroimage.2015.02.064}, - issn = {1053-8119}, - journal = {NeuroImage}, - number = {Supplement C}, - pages = {267-277}, - shorttitle = {ICA-AROMA}, - title = {ICA-{AROMA}: A robust {ICA}-based strategy for removing motion artifacts from fMRI data}, - url = {http://www.sciencedirect.com/science/article/pii/S1053811915001822}, - volume = 112, - year = 2015 -} - @article{power_fd_dvars, author = {Power, Jonathan D. and Mitra, Anish and Laumann, Timothy O. and Snyder, Abraham Z. and Schlaggar, Bradley L. and Petersen, Steven E.}, doi = {10.1016/j.neuroimage.2013.08.048}, diff --git a/fmriprep/data/reports-spec.yml b/fmriprep/data/reports-spec.yml index 0a00e0549..23ddd1a59 100644 --- a/fmriprep/data/reports-spec.yml +++ b/fmriprep/data/reports-spec.yml @@ -196,13 +196,6 @@ sections: effects and can inform decisions about feature orthogonalization prior to confound regression. subtitle: Correlations among nuisance regressors - - bids: {datatype: figures, desc: aroma, suffix: bold} - caption: | - Maps created with maximum intensity projection (glass brain) with a - black brain outline. Right hand side of each map: time series (top in seconds), - frequency spectrum (bottom in Hertz). Components classified as signal are plotted - in green; noise components in red. - subtitle: ICA Components classified by AROMA - name: About reportlets: - bids: {datatype: figures, desc: about, suffix: T1w} diff --git a/fmriprep/interfaces/confounds.py b/fmriprep/interfaces/confounds.py index 62fbbda12..1c55dc621 100644 --- a/fmriprep/interfaces/confounds.py +++ b/fmriprep/interfaces/confounds.py @@ -196,7 +196,6 @@ class GatherConfoundsInputSpec(BaseInterfaceInputSpec): crowncompcor = File(exists=True, desc='input crown-based regressors') cos_basis = File(exists=True, desc='input cosine basis') motion = File(exists=True, desc='input motion parameters') - aroma = File(exists=True, desc='input ICA-AROMA') class GatherConfoundsOutputSpec(TraitedSpec): @@ -251,7 +250,6 @@ def _run_interface(self, runtime): crowncompcor=self.inputs.crowncompcor, cos_basis=self.inputs.cos_basis, motion=self.inputs.motion, - aroma=self.inputs.aroma, newpath=runtime.cwd, ) self._results['confounds_file'] = combined_out @@ -259,43 +257,6 @@ def _run_interface(self, runtime): return runtime -class ICAConfoundsInputSpec(BaseInterfaceInputSpec): - in_directory = Directory(mandatory=True, desc='directory where ICA derivatives are found') - skip_vols = traits.Int(desc='number of non steady state volumes identified') - err_on_aroma_warn = traits.Bool(False, usedefault=True, desc='raise error if aroma fails') - - -class ICAConfoundsOutputSpec(TraitedSpec): - aroma_confounds = traits.Either( - None, File(exists=True, desc='output confounds file extracted from ICA-AROMA') - ) - aroma_noise_ics = File(exists=True, desc='ICA-AROMA noise components') - melodic_mix = File(exists=True, desc='melodic mix file') - aroma_metadata = File(exists=True, desc='tabulated ICA-AROMA metadata') - - -class ICAConfounds(SimpleInterface): - """Extract confounds from ICA-AROMA result directory""" - - input_spec = ICAConfoundsInputSpec - output_spec = ICAConfoundsOutputSpec - - def _run_interface(self, runtime): - (aroma_confounds, motion_ics_out, melodic_mix_out, aroma_metadata) = _get_ica_confounds( - self.inputs.in_directory, self.inputs.skip_vols, newpath=runtime.cwd - ) - - if self.inputs.err_on_aroma_warn and aroma_confounds is None: - raise RuntimeError('ICA-AROMA failed') - - aroma_confounds = self._results['aroma_confounds'] = aroma_confounds - - self._results['aroma_noise_ics'] = motion_ics_out - self._results['melodic_mix'] = melodic_mix_out - self._results['aroma_metadata'] = aroma_metadata - return runtime - - def _gather_confounds( signals=None, dvars=None, @@ -307,7 +268,6 @@ def _gather_confounds( crowncompcor=None, cos_basis=None, motion=None, - aroma=None, newpath=None, ): r""" @@ -364,7 +324,6 @@ def _adjust_indices(left_df, right_df): (crowncompcor, 'crownCompCor'), (cos_basis, 'Cosine basis'), (motion, 'Motion parameters'), - (aroma, 'ICA-AROMA'), ): if confound is not None and isdefined(confound): confounds_list.append(name) @@ -395,74 +354,6 @@ def _adjust_indices(left_df, right_df): return combined_out, confounds_list -def _get_ica_confounds(ica_out_dir, skip_vols, newpath=None): - if newpath is None: - newpath = os.getcwd() - - # load the txt files from ICA-AROMA - melodic_mix = os.path.join(ica_out_dir, 'melodic.ica/melodic_mix') - motion_ics = os.path.join(ica_out_dir, 'classified_motion_ICs.txt') - aroma_metadata = os.path.join(ica_out_dir, 'classification_overview.txt') - aroma_icstats = os.path.join(ica_out_dir, 'melodic.ica/melodic_ICstats') - - # Change names of motion_ics and melodic_mix for output - melodic_mix_out = os.path.join(newpath, 'MELODICmix.tsv') - motion_ics_out = os.path.join(newpath, 'AROMAnoiseICs.csv') - aroma_metadata_out = os.path.join(newpath, 'classification_overview.tsv') - - # copy metion_ics file to derivatives name - shutil.copyfile(motion_ics, motion_ics_out) - - # -1 since python lists start at index 0 - motion_ic_indices = np.loadtxt(motion_ics, dtype=int, delimiter=',', ndmin=1) - 1 - melodic_mix_arr = np.loadtxt(melodic_mix, ndmin=2) - - # pad melodic_mix_arr with rows of zeros corresponding to number non steadystate volumes - if skip_vols > 0: - zeros = np.zeros([skip_vols, melodic_mix_arr.shape[1]]) - melodic_mix_arr = np.vstack([zeros, melodic_mix_arr]) - - # save melodic_mix_arr - np.savetxt(melodic_mix_out, melodic_mix_arr, delimiter='\t') - - # process the metadata so that the IC column entries match the BIDS name of - # the regressor - aroma_metadata = pd.read_csv(aroma_metadata, sep='\t') - aroma_metadata['IC'] = ['aroma_motion_{}'.format(name) for name in aroma_metadata['IC']] - aroma_metadata.columns = [re.sub(r'[ |\-|\/]', '_', c) for c in aroma_metadata.columns] - - # Add variance statistics to metadata - aroma_icstats = pd.read_csv(aroma_icstats, header=None, sep=' ')[[0, 1]] / 100 - aroma_icstats.columns = ['model_variance_explained', 'total_variance_explained'] - aroma_metadata = pd.concat([aroma_metadata, aroma_icstats], axis=1) - - aroma_metadata.to_csv(aroma_metadata_out, sep='\t', index=False) - - # Return dummy list of ones if no noise components were found - if motion_ic_indices.size == 0: - LOGGER.warning('No noise components were classified') - return None, motion_ics_out, melodic_mix_out, aroma_metadata_out - - # the "good" ics, (e.g., not motion related) - good_ic_arr = np.delete(melodic_mix_arr, motion_ic_indices, 1).T - - # return dummy lists of zeros if no signal components were found - if good_ic_arr.size == 0: - LOGGER.warning('No signal components were classified') - return None, motion_ics_out, melodic_mix_out, aroma_metadata_out - - # transpose melodic_mix_arr so x refers to the correct dimension - aggr_confounds = np.asarray([melodic_mix_arr.T[x] for x in motion_ic_indices]) - - # add one to motion_ic_indices to match melodic report. - aroma_confounds = os.path.join(newpath, "AROMAAggrCompAROMAConfounds.tsv") - pd.DataFrame( - aggr_confounds.T, columns=['aroma_motion_%02d' % (x + 1) for x in motion_ic_indices] - ).to_csv(aroma_confounds, sep="\t", index=None) - - return aroma_confounds, motion_ics_out, melodic_mix_out, aroma_metadata_out - - class _FMRISummaryInputSpec(BaseInterfaceInputSpec): in_nifti = File(exists=True, mandatory=True, desc="input BOLD (4D NIfTI file)") in_cifti = File(exists=True, desc="input BOLD (CIFTI dense timeseries)") diff --git a/fmriprep/tests/test_config.py b/fmriprep/tests/test_config.py index 237f3ed8d..895598c3d 100644 --- a/fmriprep/tests/test_config.py +++ b/fmriprep/tests/test_config.py @@ -70,28 +70,28 @@ def test_config_spaces(): config.init_spaces() spaces = config.workflow.spaces - assert "MNI152NLin6Asym:res-2" not in [str(s) for s in spaces.get_standard(full_spec=True)] + assert "MNI152NLin6Asym:res-1" not in [str(s) for s in spaces.get_standard(full_spec=True)] - assert "MNI152NLin6Asym_res-2" not in [ + assert "MNI152NLin6Asym_res-1" not in [ format_reference((s.fullname, s.spec)) for s in spaces.references if s.standard and s.dim == 3 ] - config.workflow.use_aroma = True + config.workflow.cifti_output = True config.init_spaces() spaces = config.workflow.spaces - assert "MNI152NLin6Asym:res-2" in [str(s) for s in spaces.get_standard(full_spec=True)] + assert "MNI152NLin6Asym:res-1" in [str(s) for s in spaces.get_standard(full_spec=True)] - assert "MNI152NLin6Asym_res-2" in [ + assert "MNI152NLin6Asym_res-1" in [ format_reference((s.fullname, s.spec)) for s in spaces.references if s.standard and s.dim == 3 ] config.execution.output_spaces = None - config.workflow.use_aroma = False + config.workflow.cifti_output = False config.init_spaces() spaces = config.workflow.spaces diff --git a/fmriprep/utils/bids.py b/fmriprep/utils/bids.py index cc677384a..1a86686b9 100644 --- a/fmriprep/utils/bids.py +++ b/fmriprep/utils/bids.py @@ -38,7 +38,6 @@ def write_bidsignore(deriv_dir): "*_boldref.nii.gz", "*_bold.func.gii", "*_mixing.tsv", - "*_AROMAnoiseICs.csv", "*_timeseries.tsv", ) ignore_file = Path(deriv_dir) / ".bidsignore" diff --git a/fmriprep/workflows/bold/__init__.py b/fmriprep/workflows/bold/__init__.py index 473ac0f64..c7b1ce70a 100644 --- a/fmriprep/workflows/bold/__init__.py +++ b/fmriprep/workflows/bold/__init__.py @@ -17,7 +17,7 @@ """ from .base import init_func_preproc_wf -from .confounds import init_bold_confs_wf, init_ica_aroma_wf +from .confounds import init_bold_confs_wf from .hmc import init_bold_hmc_wf from .registration import init_bold_reg_wf, init_bold_t1_trans_wf from .resampling import ( @@ -39,5 +39,4 @@ 'init_bold_t1_trans_wf', 'init_bold_t2s_wf', 'init_func_preproc_wf', - 'init_ica_aroma_wf', ] diff --git a/fmriprep/workflows/bold/base.py b/fmriprep/workflows/bold/base.py index eaa704d0c..7448cc910 100644 --- a/fmriprep/workflows/bold/base.py +++ b/fmriprep/workflows/bold/base.py @@ -162,12 +162,6 @@ def init_func_preproc_wf(bold_file, has_fieldmap=False): Estimated T2\\* map in template space confounds TSV of confounds - aroma_noise_ics - Noise components identified by ICA-AROMA - melodic_mix - FSL MELODIC mixing matrix - nonaggr_denoised_file - BOLD series, in native space, with non-agressive AROMA denoising applied confounds_metadata Confounds metadata dictionary @@ -182,7 +176,6 @@ def init_func_preproc_wf(bold_file, has_fieldmap=False): * :py:func:`~fmriprep.workflows.bold.registration.init_bold_t1_trans_wf` * :py:func:`~fmriprep.workflows.bold.registration.init_bold_reg_wf` * :py:func:`~fmriprep.workflows.bold.confounds.init_bold_confs_wf` - * :py:func:`~fmriprep.workflows.bold.confounds.init_ica_aroma_wf` * :py:func:`~fmriprep.workflows.bold.resampling.init_bold_std_trans_wf` * :py:func:`~fmriprep.workflows.bold.resampling.init_bold_preproc_trans_wf` * :py:func:`~fmriprep.workflows.bold.resampling.init_bold_surf_wf` @@ -399,9 +392,6 @@ def init_func_preproc_wf(bold_file, has_fieldmap=False): "t2star_t1", "t2star_std", "confounds", - "aroma_noise_ics", - "melodic_mix", - "nonaggr_denoised_file", "confounds_metadata", ] ), @@ -452,7 +442,6 @@ def init_func_preproc_wf(bold_file, has_fieldmap=False): multiecho=multiecho, output_dir=fmriprep_dir, spaces=spaces, - use_aroma=config.workflow.use_aroma, ) func_derivatives_wf.inputs.inputnode.all_source_files = bold_file func_derivatives_wf.inputs.inputnode.cifti_density = config.workflow.cifti_output @@ -474,9 +463,6 @@ def init_func_preproc_wf(bold_file, has_fieldmap=False): ("bold_echos_native", "inputnode.bold_echos_native"), ("confounds", "inputnode.confounds"), ("surfaces", "inputnode.surf_files"), - ("aroma_noise_ics", "inputnode.aroma_noise_ics"), - ("melodic_mix", "inputnode.melodic_mix"), - ("nonaggr_denoised_file", "inputnode.nonaggr_denoised_file"), ("bold_cifti", "inputnode.bold_cifti"), ("cifti_metadata", "inputnode.cifti_metadata"), ("t2star_bold", "inputnode.t2star_bold"), @@ -796,7 +782,6 @@ def init_func_preproc_wf(bold_file, has_fieldmap=False): if spaces.get_spaces(nonstandard=False, dim=(3,)): # Apply transforms in 1 shot - # Only use uncompressed output if AROMA is to be run bold_std_trans_wf = init_bold_std_trans_wf( freesurfer=freesurfer, mem_gb=mem_gb["resampled"], @@ -879,72 +864,6 @@ def init_func_preproc_wf(bold_file, has_fieldmap=False): ]) # fmt:on - if config.workflow.use_aroma: # ICA-AROMA workflow - from .confounds import init_ica_aroma_wf - - ica_aroma_wf = init_ica_aroma_wf( - mem_gb=mem_gb["resampled"], - metadata=metadata, - omp_nthreads=omp_nthreads, - err_on_aroma_warn=config.workflow.aroma_err_on_warn, - aroma_melodic_dim=config.workflow.aroma_melodic_dim, - name="ica_aroma_wf", - ) - - join = pe.Node( - niu.Function(output_names=["out_file"], function=_to_join), - name="aroma_confounds", - ) - - mrg_conf_metadata = pe.Node( - niu.Merge(2), - name="merge_confound_metadata", - run_without_submitting=True, - ) - mrg_conf_metadata2 = pe.Node( - DictMerge(), - name="merge_confound_metadata2", - run_without_submitting=True, - ) - # fmt:off - workflow.disconnect([ - (bold_confounds_wf, outputnode, [ - ("outputnode.confounds_file", "confounds"), - ]), - (bold_confounds_wf, outputnode, [ - ("outputnode.confounds_metadata", "confounds_metadata"), - ]), - ]) - workflow.connect([ - (inputnode, ica_aroma_wf, [("bold_file", "inputnode.name_source")]), - (bold_hmc_wf, ica_aroma_wf, [ - ("outputnode.movpar_file", "inputnode.movpar_file"), - ]), - (initial_boldref_wf, ica_aroma_wf, [ - ("outputnode.skip_vols", "inputnode.skip_vols"), - ]), - (bold_confounds_wf, join, [("outputnode.confounds_file", "in_file")]), - (bold_confounds_wf, mrg_conf_metadata, [ - ("outputnode.confounds_metadata", "in1"), - ]), - (ica_aroma_wf, join, [("outputnode.aroma_confounds", "join_file")]), - (ica_aroma_wf, mrg_conf_metadata, [("outputnode.aroma_metadata", "in2")]), - (mrg_conf_metadata, mrg_conf_metadata2, [("out", "in_dicts")]), - (ica_aroma_wf, outputnode, [ - ("outputnode.aroma_noise_ics", "aroma_noise_ics"), - ("outputnode.melodic_mix", "melodic_mix"), - ("outputnode.nonaggr_denoised_file", "nonaggr_denoised_file"), - ]), - (join, outputnode, [("out_file", "confounds")]), - (mrg_conf_metadata2, outputnode, [("out_dict", "confounds_metadata")]), - (bold_std_trans_wf, ica_aroma_wf, [ - ("outputnode.bold_std", "inputnode.bold_std"), - ("outputnode.bold_mask_std", "inputnode.bold_mask_std"), - ("outputnode.spatial_reference", "inputnode.spatial_reference"), - ]), - ]) - # fmt:on - # SURFACES ################################################################################## # Freesurfer if freesurfer and freesurfer_spaces: diff --git a/fmriprep/workflows/bold/confounds.py b/fmriprep/workflows/bold/confounds.py index 5636437b9..14b169313 100644 --- a/fmriprep/workflows/bold/confounds.py +++ b/fmriprep/workflows/bold/confounds.py @@ -25,7 +25,6 @@ ^^^^^^^^^^^^^^^^^^^^^^^^ .. autofunction:: init_bold_confs_wf -.. autofunction:: init_ica_aroma_wf """ from os import getenv @@ -44,7 +43,6 @@ FilterDropped, FMRISummary, GatherConfounds, - ICAConfounds, RenameACompCor, ) @@ -745,321 +743,6 @@ def init_carpetplot_wf( return workflow -def init_ica_aroma_wf( - mem_gb: float, - metadata: dict, - omp_nthreads: int, - aroma_melodic_dim: int = -200, - err_on_aroma_warn: bool = False, - name: str = "ica_aroma_wf", - susan_fwhm: float = 6.0, -): - """ - Build a workflow that runs `ICA-AROMA`_. - - This workflow wraps `ICA-AROMA`_ to identify and remove motion-related - independent components from a BOLD time series. - - The following steps are performed: - - #. Remove non-steady state volumes from the bold series. - #. Smooth data using FSL `susan`, with a kernel width FWHM=6.0mm. - #. Run FSL `melodic` outside of ICA-AROMA to generate the report - #. Run ICA-AROMA - #. Aggregate identified motion components (aggressive) to TSV - #. Return ``classified_motion_ICs`` and ``melodic_mix`` for user to complete - non-aggressive denoising in T1w space - #. Calculate ICA-AROMA-identified noise components - (columns named ``AROMAAggrCompXX``) - - Additionally, non-aggressive denoising is performed on the BOLD series - resampled into MNI space. - - There is a current discussion on whether other confounds should be extracted - before or after denoising `here - `__. - - .. _ICA-AROMA: https://github.com/maartenmennes/ICA-AROMA - - Workflow Graph - .. workflow:: - :graph2use: orig - :simple_form: yes - - from fmriprep.workflows.bold.confounds import init_ica_aroma_wf - wf = init_ica_aroma_wf( - mem_gb=3, - metadata={"RepetitionTime": 1.0}, - omp_nthreads=1) - - Parameters - ---------- - metadata : :obj:`dict` - BIDS metadata for BOLD file - mem_gb : :obj:`float` - Size of BOLD file in GB - omp_nthreads : :obj:`int` - Maximum number of threads an individual process may use - name : :obj:`str` - Name of workflow (default: ``bold_tpl_trans_wf``) - susan_fwhm : :obj:`float` - Kernel width (FWHM in mm) for the smoothing step with - FSL ``susan`` (default: 6.0mm) - err_on_aroma_warn : :obj:`bool` - Do not fail on ICA-AROMA errors - aroma_melodic_dim : :obj:`int` - Set the dimensionality of the MELODIC ICA decomposition. - Negative numbers set a maximum on automatic dimensionality estimation. - Positive numbers set an exact number of components to extract. - (default: -200, i.e., estimate <=200 components) - - Inputs - ------ - itk_bold_to_t1 - Affine transform from ``ref_bold_brain`` to T1 space (ITK format) - anat2std_xfm - ANTs-compatible affine-and-warp transform file - name_source - BOLD series NIfTI file - Used to recover original information lost during processing - skip_vols - number of non steady state volumes - bold_split - Individual 3D BOLD volumes, not motion corrected - bold_mask - BOLD series mask in template space - hmc_xforms - List of affine transforms aligning each volume to ``ref_image`` in ITK format - movpar_file - SPM-formatted motion parameters file - - Outputs - ------- - aroma_confounds - TSV of confounds identified as noise by ICA-AROMA - aroma_noise_ics - CSV of noise components identified by ICA-AROMA - melodic_mix - FSL MELODIC mixing matrix - nonaggr_denoised_file - BOLD series with non-aggressive ICA-AROMA denoising applied - - """ - from niworkflows.engine.workflows import LiterateWorkflow as Workflow - from niworkflows.interfaces.reportlets.segmentation import ICA_AROMARPT - from niworkflows.interfaces.utility import TSV2JSON, KeySelect - - workflow = Workflow(name=name) - workflow.__postdesc__ = """\ -Automatic removal of motion artifacts using independent component analysis -[ICA-AROMA, @aroma] was performed on the *preprocessed BOLD on MNI space* -time-series after removal of non-steady state volumes and spatial smoothing -with an isotropic, Gaussian kernel of 6mm FWHM (full-width half-maximum). -Corresponding "non-aggresively" denoised runs were produced after such -smoothing. -Additionally, the "aggressive" noise-regressors were collected and placed -in the corresponding confounds file. -""" - - inputnode = pe.Node( - niu.IdentityInterface( - fields=[ - "bold_std", - "bold_mask_std", - "movpar_file", - "name_source", - "skip_vols", - "spatial_reference", - ] - ), - name="inputnode", - ) - - outputnode = pe.Node( - niu.IdentityInterface( - fields=[ - "aroma_confounds", - "aroma_noise_ics", - "melodic_mix", - "nonaggr_denoised_file", - "aroma_metadata", - ] - ), - name="outputnode", - ) - - # extract out to BOLD base - select_std = pe.Node( - KeySelect(fields=["bold_mask_std", "bold_std"]), - name="select_std", - run_without_submitting=True, - ) - select_std.inputs.key = "MNI152NLin6Asym_res-2" - - rm_non_steady_state = pe.Node( - niu.Function(function=_remove_volumes, output_names=["bold_cut"]), name="rm_nonsteady" - ) - - calc_median_val = pe.Node(fsl.ImageStats(op_string="-k %s -p 50"), name="calc_median_val") - calc_bold_mean = pe.Node(fsl.MeanImage(), name="calc_bold_mean") - - def _getusans_func(image, thresh): - return [tuple([image, thresh])] - - getusans = pe.Node( - niu.Function(function=_getusans_func, output_names=["usans"]), name="getusans", mem_gb=0.01 - ) - - smooth = pe.Node( - fsl.SUSAN( - fwhm=susan_fwhm, output_type="NIFTI" if config.execution.low_mem else "NIFTI_GZ" - ), - name="smooth", - ) - - # melodic node - melodic = pe.Node( - fsl.MELODIC( - no_bet=True, - tr_sec=float(metadata["RepetitionTime"]), - mm_thresh=0.5, - out_stats=True, - dim=aroma_melodic_dim, - ), - name="melodic", - ) - - # ica_aroma node - ica_aroma = pe.Node( - ICA_AROMARPT( - denoise_type="nonaggr", generate_report=True, TR=metadata["RepetitionTime"], args="-np" - ), - name="ica_aroma", - ) - - add_non_steady_state = pe.Node( - niu.Function(function=_add_volumes, output_names=["bold_add"]), name="add_nonsteady" - ) - - # extract the confound ICs from the results - ica_aroma_confound_extraction = pe.Node( - ICAConfounds(err_on_aroma_warn=err_on_aroma_warn), name="ica_aroma_confound_extraction" - ) - - ica_aroma_metadata_fmt = pe.Node( - TSV2JSON( - index_column="IC", - output=None, - enforce_case=True, - additional_metadata={ - "Method": {"Name": "ICA-AROMA", "Version": getenv("AROMA_VERSION", "n/a")} - }, - ), - name="ica_aroma_metadata_fmt", - ) - - ds_report_ica_aroma = pe.Node( - DerivativesDataSink(desc="aroma", datatype="figures", dismiss_entities=("echo",)), - name="ds_report_ica_aroma", - run_without_submitting=True, - mem_gb=DEFAULT_MEMORY_MIN_GB, - ) - - def _getbtthresh(medianval): - return 0.75 * medianval - - # fmt:off - workflow.connect([ - (inputnode, select_std, [("spatial_reference", "keys"), - ("bold_std", "bold_std"), - ("bold_mask_std", "bold_mask_std")]), - (inputnode, ica_aroma, [("movpar_file", "motion_parameters")]), - (inputnode, rm_non_steady_state, [ - ("skip_vols", "skip_vols")]), - (select_std, rm_non_steady_state, [ - ("bold_std", "bold_file")]), - (select_std, calc_median_val, [ - ("bold_mask_std", "mask_file")]), - (rm_non_steady_state, calc_median_val, [ - ("bold_cut", "in_file")]), - (rm_non_steady_state, calc_bold_mean, [ - ("bold_cut", "in_file")]), - (calc_bold_mean, getusans, [("out_file", "image")]), - (calc_median_val, getusans, [("out_stat", "thresh")]), - # Connect input nodes to complete smoothing - (rm_non_steady_state, smooth, [ - ("bold_cut", "in_file")]), - (getusans, smooth, [("usans", "usans")]), - (calc_median_val, smooth, [(("out_stat", _getbtthresh), "brightness_threshold")]), - # connect smooth to melodic - (smooth, melodic, [("smoothed_file", "in_files")]), - (select_std, melodic, [ - ("bold_mask_std", "mask")]), - # connect nodes to ICA-AROMA - (smooth, ica_aroma, [("smoothed_file", "in_file")]), - (select_std, ica_aroma, [ - ("bold_mask_std", "report_mask"), - ("bold_mask_std", "mask")]), - (melodic, ica_aroma, [("out_dir", "melodic_dir")]), - # generate tsvs from ICA-AROMA - (ica_aroma, ica_aroma_confound_extraction, [("out_dir", "in_directory")]), - (inputnode, ica_aroma_confound_extraction, [ - ("skip_vols", "skip_vols")]), - (ica_aroma_confound_extraction, ica_aroma_metadata_fmt, [ - ("aroma_metadata", "in_file")]), - # output for processing and reporting - (ica_aroma_confound_extraction, outputnode, [("aroma_confounds", "aroma_confounds"), - ("aroma_noise_ics", "aroma_noise_ics"), - ("melodic_mix", "melodic_mix")]), - (ica_aroma_metadata_fmt, outputnode, [("output", "aroma_metadata")]), - (ica_aroma, add_non_steady_state, [ - ("nonaggr_denoised_file", "bold_cut_file")]), - (select_std, add_non_steady_state, [ - ("bold_std", "bold_file")]), - (inputnode, add_non_steady_state, [ - ("skip_vols", "skip_vols")]), - (add_non_steady_state, outputnode, [("bold_add", "nonaggr_denoised_file")]), - (ica_aroma, ds_report_ica_aroma, [("out_report", "in_file")]), - ]) - # fmt:on - return workflow - - -def _remove_volumes(bold_file, skip_vols): - """Remove skip_vols from bold_file.""" - import nibabel as nb - from nipype.utils.filemanip import fname_presuffix - - if skip_vols == 0: - return bold_file - - out = fname_presuffix(bold_file, suffix="_cut") - bold_img = nb.load(bold_file) - bold_img.__class__( - bold_img.dataobj[..., skip_vols:], bold_img.affine, bold_img.header - ).to_filename(out) - return out - - -def _add_volumes(bold_file, bold_cut_file, skip_vols): - """Prepend skip_vols from bold_file onto bold_cut_file.""" - import nibabel as nb - import numpy as np - from nipype.utils.filemanip import fname_presuffix - - if skip_vols == 0: - return bold_cut_file - - bold_img = nb.load(bold_file) - bold_cut_img = nb.load(bold_cut_file) - - bold_data = np.concatenate((bold_img.dataobj[..., :skip_vols], bold_cut_img.dataobj), axis=3) - - out = fname_presuffix(bold_cut_file, suffix="_addnonsteady") - bold_img.__class__(bold_data, bold_img.affine, bold_img.header).to_filename(out) - return out - - def _binary_union(mask1, mask2): """Generate the union of two masks.""" from pathlib import Path diff --git a/fmriprep/workflows/bold/outputs.py b/fmriprep/workflows/bold/outputs.py index 0e573d7b6..a5a4ec638 100644 --- a/fmriprep/workflows/bold/outputs.py +++ b/fmriprep/workflows/bold/outputs.py @@ -148,7 +148,6 @@ def init_func_derivatives_wf( multiecho: bool, output_dir: str, spaces: SpatialReferences, - use_aroma: bool, name='func_derivatives_wf', ): """ @@ -181,8 +180,6 @@ def init_func_derivatives_wf( the TemplateFlow root directory. Each ``Reference`` may also contain a spec, which is a dictionary with template specifications (e.g., a specification of ``{'resolution': 2}`` would lead to resampling on a 2mm resolution of the space). - use_aroma : :obj:`bool` - Whether ``--use-aroma`` flag was set. name : :obj:`str` This workflow's identifier (default: ``func_derivatives_wf``). @@ -210,7 +207,6 @@ def init_func_derivatives_wf( inputnode = pe.Node( niu.IdentityInterface( fields=[ - 'aroma_noise_ics', 'bold_aparc_std', 'bold_aparc_t1', 'bold_aseg_std', @@ -231,8 +227,6 @@ def init_func_derivatives_wf( 'confounds', 'confounds_metadata', 'goodvoxels_ribbon', - 'melodic_mix', - 'nonaggr_denoised_file', 'source_file', 'all_source_files', 'surf_files', @@ -540,50 +534,6 @@ def init_func_derivatives_wf( ]) # fmt:on - if use_aroma: - ds_aroma_noise_ics = pe.Node( - DerivativesDataSink( - base_directory=output_dir, suffix='AROMAnoiseICs', dismiss_entities=("echo",) - ), - name="ds_aroma_noise_ics", - run_without_submitting=True, - mem_gb=DEFAULT_MEMORY_MIN_GB, - ) - ds_melodic_mix = pe.Node( - DerivativesDataSink( - base_directory=output_dir, - desc='MELODIC', - suffix='mixing', - dismiss_entities=("echo",), - ), - name="ds_melodic_mix", - run_without_submitting=True, - mem_gb=DEFAULT_MEMORY_MIN_GB, - ) - ds_aroma_std = pe.Node( - DerivativesDataSink( - base_directory=output_dir, - space='MNI152NLin6Asym', - desc='smoothAROMAnonaggr', - compress=True, - TaskName=metadata.get('TaskName'), - **timing_parameters, - ), - name='ds_aroma_std', - run_without_submitting=True, - mem_gb=DEFAULT_MEMORY_MIN_GB, - ) - # fmt:off - workflow.connect([ - (inputnode, ds_aroma_noise_ics, [('source_file', 'source_file'), - ('aroma_noise_ics', 'in_file')]), - (inputnode, ds_melodic_mix, [('source_file', 'source_file'), - ('melodic_mix', 'in_file')]), - (inputnode, ds_aroma_std, [('source_file', 'source_file'), - ('nonaggr_denoised_file', 'in_file')]), - ]) - # fmt:on - if getattr(spaces, '_cached') is None: return workflow diff --git a/fmriprep/workflows/bold/tests/test_confounds.py b/fmriprep/workflows/bold/tests/test_confounds.py deleted file mode 100644 index cd179ee48..000000000 --- a/fmriprep/workflows/bold/tests/test_confounds.py +++ /dev/null @@ -1,70 +0,0 @@ -# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -# -# Copyright 2023 The NiPreps Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# We support and encourage derived works from this project, please read -# about our expectations at -# -# https://www.nipreps.org/community/licensing/ -# -''' Testing module for fmriprep.workflows.bold.confounds ''' -import os - -import nibabel as nib -import pytest - -from ..confounds import _add_volumes, _remove_volumes - -skip_pytest = pytest.mark.skipif( - not os.getenv('FMRIPREP_REGRESSION_SOURCE') or not os.getenv('FMRIPREP_REGRESSION_TARGETS'), - reason='FMRIPREP_REGRESSION_{SOURCE,TARGETS} env vars not set', -) - - -@skip_pytest -def test_remove_volumes(): - bold_file = os.path.join( - os.getenv('FMRIPREP_REGRESSION_SOURCE'), 'ds001362/sub-01_task-taskname_run-01_bold.nii.gz' - ) - n_volumes = nib.load(bold_file).shape[3] - skip_vols = 3 - - expected_volumes = n_volumes - skip_vols - - cut_file = _remove_volumes(bold_file, skip_vols) - out_volumes = nib.load(cut_file).shape[3] - # cleanup output file - os.remove(cut_file) - - assert out_volumes == expected_volumes - - -@skip_pytest -def test_add_volumes(): - bold_file = os.path.join( - os.getenv('FMRIPREP_REGRESSION_SOURCE'), 'ds001362/sub-01_task-taskname_run-01_bold.nii.gz' - ) - n_volumes = nib.load(bold_file).shape[3] - add_vols = 3 - - expected_volumes = n_volumes + add_vols - - add_file = _add_volumes(bold_file, bold_file, add_vols) - out_volumes = nib.load(add_file).shape[3] - # cleanup output file - os.remove(add_file) - - assert out_volumes == expected_volumes