junifer 0.0.5.dev242__py3-none-any.whl → 0.0.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- junifer/__init__.py +2 -31
- junifer/__init__.pyi +37 -0
- junifer/_version.py +9 -4
- junifer/api/__init__.py +3 -5
- junifer/api/__init__.pyi +4 -0
- junifer/api/decorators.py +14 -19
- junifer/api/functions.py +165 -109
- junifer/api/py.typed +0 -0
- junifer/api/queue_context/__init__.py +2 -4
- junifer/api/queue_context/__init__.pyi +5 -0
- junifer/api/queue_context/gnu_parallel_local_adapter.py +22 -6
- junifer/api/queue_context/htcondor_adapter.py +23 -6
- junifer/api/queue_context/py.typed +0 -0
- junifer/api/queue_context/tests/test_gnu_parallel_local_adapter.py +3 -3
- junifer/api/queue_context/tests/test_htcondor_adapter.py +3 -3
- junifer/api/tests/test_functions.py +168 -74
- junifer/cli/__init__.py +24 -0
- junifer/cli/__init__.pyi +3 -0
- junifer/{api → cli}/cli.py +141 -125
- junifer/cli/parser.py +235 -0
- junifer/cli/py.typed +0 -0
- junifer/{api → cli}/tests/test_cli.py +8 -8
- junifer/{api/tests/test_api_utils.py → cli/tests/test_cli_utils.py} +5 -4
- junifer/{api → cli}/tests/test_parser.py +2 -2
- junifer/{api → cli}/utils.py +6 -16
- junifer/configs/juseless/__init__.py +2 -2
- junifer/configs/juseless/__init__.pyi +3 -0
- junifer/configs/juseless/datagrabbers/__init__.py +2 -12
- junifer/configs/juseless/datagrabbers/__init__.pyi +13 -0
- junifer/configs/juseless/datagrabbers/ixi_vbm.py +2 -2
- junifer/configs/juseless/datagrabbers/py.typed +0 -0
- junifer/configs/juseless/datagrabbers/tests/test_ucla.py +2 -2
- junifer/configs/juseless/datagrabbers/ucla.py +4 -4
- junifer/configs/juseless/py.typed +0 -0
- junifer/conftest.py +25 -0
- junifer/data/__init__.py +2 -42
- junifer/data/__init__.pyi +29 -0
- junifer/data/_dispatch.py +248 -0
- junifer/data/coordinates/__init__.py +9 -0
- junifer/data/coordinates/__init__.pyi +5 -0
- junifer/data/coordinates/_ants_coordinates_warper.py +104 -0
- junifer/data/coordinates/_coordinates.py +385 -0
- junifer/data/coordinates/_fsl_coordinates_warper.py +81 -0
- junifer/data/{tests → coordinates/tests}/test_coordinates.py +26 -33
- junifer/data/masks/__init__.py +9 -0
- junifer/data/masks/__init__.pyi +6 -0
- junifer/data/masks/_ants_mask_warper.py +177 -0
- junifer/data/masks/_fsl_mask_warper.py +106 -0
- junifer/data/masks/_masks.py +802 -0
- junifer/data/{tests → masks/tests}/test_masks.py +67 -63
- junifer/data/parcellations/__init__.py +9 -0
- junifer/data/parcellations/__init__.pyi +6 -0
- junifer/data/parcellations/_ants_parcellation_warper.py +166 -0
- junifer/data/parcellations/_fsl_parcellation_warper.py +89 -0
- junifer/data/parcellations/_parcellations.py +1388 -0
- junifer/data/{tests → parcellations/tests}/test_parcellations.py +165 -295
- junifer/data/pipeline_data_registry_base.py +76 -0
- junifer/data/py.typed +0 -0
- junifer/data/template_spaces.py +44 -79
- junifer/data/tests/test_data_utils.py +1 -2
- junifer/data/tests/test_template_spaces.py +8 -4
- junifer/data/utils.py +109 -4
- junifer/datagrabber/__init__.py +2 -26
- junifer/datagrabber/__init__.pyi +27 -0
- junifer/datagrabber/aomic/__init__.py +2 -4
- junifer/datagrabber/aomic/__init__.pyi +5 -0
- junifer/datagrabber/aomic/id1000.py +81 -52
- junifer/datagrabber/aomic/piop1.py +83 -55
- junifer/datagrabber/aomic/piop2.py +85 -56
- junifer/datagrabber/aomic/py.typed +0 -0
- junifer/datagrabber/aomic/tests/test_id1000.py +19 -12
- junifer/datagrabber/aomic/tests/test_piop1.py +52 -18
- junifer/datagrabber/aomic/tests/test_piop2.py +50 -17
- junifer/datagrabber/base.py +22 -18
- junifer/datagrabber/datalad_base.py +71 -34
- junifer/datagrabber/dmcc13_benchmark.py +31 -18
- junifer/datagrabber/hcp1200/__init__.py +2 -3
- junifer/datagrabber/hcp1200/__init__.pyi +4 -0
- junifer/datagrabber/hcp1200/datalad_hcp1200.py +3 -3
- junifer/datagrabber/hcp1200/hcp1200.py +26 -15
- junifer/datagrabber/hcp1200/py.typed +0 -0
- junifer/datagrabber/hcp1200/tests/test_hcp1200.py +8 -2
- junifer/datagrabber/multiple.py +14 -9
- junifer/datagrabber/pattern.py +132 -96
- junifer/datagrabber/pattern_validation_mixin.py +206 -94
- junifer/datagrabber/py.typed +0 -0
- junifer/datagrabber/tests/test_datalad_base.py +27 -12
- junifer/datagrabber/tests/test_dmcc13_benchmark.py +28 -11
- junifer/datagrabber/tests/test_multiple.py +48 -2
- junifer/datagrabber/tests/test_pattern_datalad.py +1 -1
- junifer/datagrabber/tests/test_pattern_validation_mixin.py +6 -6
- junifer/datareader/__init__.py +2 -2
- junifer/datareader/__init__.pyi +3 -0
- junifer/datareader/default.py +6 -6
- junifer/datareader/py.typed +0 -0
- junifer/external/nilearn/__init__.py +2 -3
- junifer/external/nilearn/__init__.pyi +4 -0
- junifer/external/nilearn/junifer_connectivity_measure.py +25 -17
- junifer/external/nilearn/junifer_nifti_spheres_masker.py +4 -4
- junifer/external/nilearn/py.typed +0 -0
- junifer/external/nilearn/tests/test_junifer_connectivity_measure.py +17 -16
- junifer/external/nilearn/tests/test_junifer_nifti_spheres_masker.py +2 -3
- junifer/markers/__init__.py +2 -38
- junifer/markers/__init__.pyi +37 -0
- junifer/markers/base.py +11 -14
- junifer/markers/brainprint.py +12 -14
- junifer/markers/complexity/__init__.py +2 -18
- junifer/markers/complexity/__init__.pyi +17 -0
- junifer/markers/complexity/complexity_base.py +9 -11
- junifer/markers/complexity/hurst_exponent.py +7 -7
- junifer/markers/complexity/multiscale_entropy_auc.py +7 -7
- junifer/markers/complexity/perm_entropy.py +7 -7
- junifer/markers/complexity/py.typed +0 -0
- junifer/markers/complexity/range_entropy.py +7 -7
- junifer/markers/complexity/range_entropy_auc.py +7 -7
- junifer/markers/complexity/sample_entropy.py +7 -7
- junifer/markers/complexity/tests/test_complexity_base.py +1 -1
- junifer/markers/complexity/tests/test_hurst_exponent.py +5 -5
- junifer/markers/complexity/tests/test_multiscale_entropy_auc.py +5 -5
- junifer/markers/complexity/tests/test_perm_entropy.py +5 -5
- junifer/markers/complexity/tests/test_range_entropy.py +5 -5
- junifer/markers/complexity/tests/test_range_entropy_auc.py +5 -5
- junifer/markers/complexity/tests/test_sample_entropy.py +5 -5
- junifer/markers/complexity/tests/test_weighted_perm_entropy.py +5 -5
- junifer/markers/complexity/weighted_perm_entropy.py +7 -7
- junifer/markers/ets_rss.py +12 -11
- junifer/markers/falff/__init__.py +2 -3
- junifer/markers/falff/__init__.pyi +4 -0
- junifer/markers/falff/_afni_falff.py +38 -45
- junifer/markers/falff/_junifer_falff.py +16 -19
- junifer/markers/falff/falff_base.py +7 -11
- junifer/markers/falff/falff_parcels.py +9 -9
- junifer/markers/falff/falff_spheres.py +8 -8
- junifer/markers/falff/py.typed +0 -0
- junifer/markers/falff/tests/test_falff_spheres.py +3 -1
- junifer/markers/functional_connectivity/__init__.py +2 -12
- junifer/markers/functional_connectivity/__init__.pyi +13 -0
- junifer/markers/functional_connectivity/crossparcellation_functional_connectivity.py +9 -8
- junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +8 -8
- junifer/markers/functional_connectivity/edge_functional_connectivity_spheres.py +7 -7
- junifer/markers/functional_connectivity/functional_connectivity_base.py +13 -12
- junifer/markers/functional_connectivity/functional_connectivity_parcels.py +8 -8
- junifer/markers/functional_connectivity/functional_connectivity_spheres.py +7 -7
- junifer/markers/functional_connectivity/py.typed +0 -0
- junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_parcels.py +1 -2
- junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_spheres.py +1 -2
- junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +6 -6
- junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +5 -5
- junifer/markers/parcel_aggregation.py +22 -17
- junifer/markers/py.typed +0 -0
- junifer/markers/reho/__init__.py +2 -3
- junifer/markers/reho/__init__.pyi +4 -0
- junifer/markers/reho/_afni_reho.py +29 -35
- junifer/markers/reho/_junifer_reho.py +13 -14
- junifer/markers/reho/py.typed +0 -0
- junifer/markers/reho/reho_base.py +7 -11
- junifer/markers/reho/reho_parcels.py +10 -10
- junifer/markers/reho/reho_spheres.py +9 -9
- junifer/markers/sphere_aggregation.py +22 -17
- junifer/markers/temporal_snr/__init__.py +2 -3
- junifer/markers/temporal_snr/__init__.pyi +4 -0
- junifer/markers/temporal_snr/py.typed +0 -0
- junifer/markers/temporal_snr/temporal_snr_base.py +11 -10
- junifer/markers/temporal_snr/temporal_snr_parcels.py +8 -8
- junifer/markers/temporal_snr/temporal_snr_spheres.py +7 -7
- junifer/markers/tests/test_ets_rss.py +3 -3
- junifer/markers/tests/test_parcel_aggregation.py +24 -24
- junifer/markers/tests/test_sphere_aggregation.py +6 -6
- junifer/markers/utils.py +3 -3
- junifer/onthefly/__init__.py +2 -1
- junifer/onthefly/_brainprint.py +138 -0
- junifer/onthefly/read_transform.py +5 -8
- junifer/pipeline/__init__.py +2 -10
- junifer/pipeline/__init__.pyi +13 -0
- junifer/{markers/collection.py → pipeline/marker_collection.py} +8 -14
- junifer/pipeline/pipeline_component_registry.py +294 -0
- junifer/pipeline/pipeline_step_mixin.py +15 -11
- junifer/pipeline/py.typed +0 -0
- junifer/{markers/tests/test_collection.py → pipeline/tests/test_marker_collection.py} +2 -3
- junifer/pipeline/tests/test_pipeline_component_registry.py +200 -0
- junifer/pipeline/tests/test_pipeline_step_mixin.py +36 -37
- junifer/pipeline/tests/test_update_meta_mixin.py +4 -4
- junifer/pipeline/tests/test_workdir_manager.py +43 -0
- junifer/pipeline/update_meta_mixin.py +21 -17
- junifer/pipeline/utils.py +6 -6
- junifer/pipeline/workdir_manager.py +19 -5
- junifer/preprocess/__init__.py +2 -10
- junifer/preprocess/__init__.pyi +11 -0
- junifer/preprocess/base.py +10 -10
- junifer/preprocess/confounds/__init__.py +2 -2
- junifer/preprocess/confounds/__init__.pyi +3 -0
- junifer/preprocess/confounds/fmriprep_confound_remover.py +243 -64
- junifer/preprocess/confounds/py.typed +0 -0
- junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py +121 -14
- junifer/preprocess/py.typed +0 -0
- junifer/preprocess/smoothing/__init__.py +2 -2
- junifer/preprocess/smoothing/__init__.pyi +3 -0
- junifer/preprocess/smoothing/_afni_smoothing.py +40 -40
- junifer/preprocess/smoothing/_fsl_smoothing.py +22 -32
- junifer/preprocess/smoothing/_nilearn_smoothing.py +35 -14
- junifer/preprocess/smoothing/py.typed +0 -0
- junifer/preprocess/smoothing/smoothing.py +11 -13
- junifer/preprocess/warping/__init__.py +2 -2
- junifer/preprocess/warping/__init__.pyi +3 -0
- junifer/preprocess/warping/_ants_warper.py +136 -32
- junifer/preprocess/warping/_fsl_warper.py +73 -22
- junifer/preprocess/warping/py.typed +0 -0
- junifer/preprocess/warping/space_warper.py +39 -11
- junifer/preprocess/warping/tests/test_space_warper.py +5 -9
- junifer/py.typed +0 -0
- junifer/stats.py +5 -5
- junifer/storage/__init__.py +2 -10
- junifer/storage/__init__.pyi +11 -0
- junifer/storage/base.py +47 -13
- junifer/storage/hdf5.py +95 -33
- junifer/storage/pandas_base.py +12 -11
- junifer/storage/py.typed +0 -0
- junifer/storage/sqlite.py +11 -11
- junifer/storage/tests/test_hdf5.py +86 -4
- junifer/storage/tests/test_sqlite.py +2 -2
- junifer/storage/tests/test_storage_base.py +5 -2
- junifer/storage/tests/test_utils.py +33 -7
- junifer/storage/utils.py +95 -9
- junifer/testing/__init__.py +2 -3
- junifer/testing/__init__.pyi +4 -0
- junifer/testing/datagrabbers.py +10 -11
- junifer/testing/py.typed +0 -0
- junifer/testing/registry.py +4 -7
- junifer/testing/tests/test_testing_registry.py +9 -17
- junifer/tests/test_stats.py +2 -2
- junifer/typing/__init__.py +9 -0
- junifer/typing/__init__.pyi +31 -0
- junifer/typing/_typing.py +68 -0
- junifer/utils/__init__.py +2 -12
- junifer/utils/__init__.pyi +18 -0
- junifer/utils/_config.py +110 -0
- junifer/utils/_yaml.py +16 -0
- junifer/utils/helpers.py +6 -6
- junifer/utils/logging.py +117 -8
- junifer/utils/py.typed +0 -0
- junifer/{pipeline → utils}/singleton.py +19 -14
- junifer/utils/tests/test_config.py +59 -0
- {junifer-0.0.5.dev242.dist-info → junifer-0.0.6.dist-info}/METADATA +43 -38
- junifer-0.0.6.dist-info/RECORD +350 -0
- {junifer-0.0.5.dev242.dist-info → junifer-0.0.6.dist-info}/WHEEL +1 -1
- junifer-0.0.6.dist-info/entry_points.txt +2 -0
- junifer/api/parser.py +0 -118
- junifer/data/coordinates.py +0 -408
- junifer/data/masks.py +0 -670
- junifer/data/parcellations.py +0 -1828
- junifer/pipeline/registry.py +0 -177
- junifer/pipeline/tests/test_registry.py +0 -150
- junifer-0.0.5.dev242.dist-info/RECORD +0 -275
- junifer-0.0.5.dev242.dist-info/entry_points.txt +0 -2
- /junifer/{api → cli}/tests/data/gmd_mean.yaml +0 -0
- /junifer/{api → cli}/tests/data/gmd_mean_htcondor.yaml +0 -0
- /junifer/{api → cli}/tests/data/partly_cloudy_agg_mean_tian.yml +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/AutobiographicalMemory_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/CogAC_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/CogAR_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/DMNBuckner_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/Dosenbach2010_MNI_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/Empathy_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/Motor_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/MultiTask_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/PhysioStress_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/Power2011_MNI_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/Power2013_MNI_VOIs.tsv +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/Rew_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/Somatosensory_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/ToM_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/VigAtt_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/WM_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/eMDN_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/eSAD_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/extDMN_VOIs.txt +0 -0
- {junifer-0.0.5.dev242.dist-info → junifer-0.0.6.dist-info/licenses}/AUTHORS.rst +0 -0
- {junifer-0.0.5.dev242.dist-info → junifer-0.0.6.dist-info/licenses}/LICENSE.md +0 -0
- {junifer-0.0.5.dev242.dist-info → junifer-0.0.6.dist-info}/top_level.txt +0 -0
@@ -8,21 +8,22 @@
|
|
8
8
|
from typing import (
|
9
9
|
Any,
|
10
10
|
ClassVar,
|
11
|
-
Dict,
|
12
|
-
List,
|
13
11
|
Optional,
|
14
|
-
Set,
|
15
|
-
Tuple,
|
16
12
|
Union,
|
17
13
|
)
|
18
14
|
|
15
|
+
import nibabel as nib
|
19
16
|
import numpy as np
|
20
17
|
import pandas as pd
|
21
18
|
from nilearn import image as nimg
|
22
19
|
from nilearn._utils.niimg_conversions import check_niimg_4d
|
20
|
+
from nilearn.interfaces.fmriprep.load_confounds_components import _load_scrub
|
21
|
+
from nilearn.interfaces.fmriprep.load_confounds_utils import prepare_output
|
23
22
|
|
24
23
|
from ...api.decorators import register_preprocessor
|
25
|
-
from ...data import
|
24
|
+
from ...data import get_data
|
25
|
+
from ...pipeline import WorkDirManager
|
26
|
+
from ...typing import Dependencies
|
26
27
|
from ...utils import logger, raise_error
|
27
28
|
from ..base import BasePreprocessor
|
28
29
|
|
@@ -93,6 +94,9 @@ FMRIPREP_VALID_NAMES = [
|
|
93
94
|
for t_list in x.values()
|
94
95
|
for elem in t_list
|
95
96
|
]
|
97
|
+
# NOTE: Check with @fraimondo about the spike mapping intent
|
98
|
+
# Add spike_name to FMRIPREP_VALID_NAMES
|
99
|
+
FMRIPREP_VALID_NAMES.append("framewise_displacement")
|
96
100
|
|
97
101
|
|
98
102
|
@register_preprocessor
|
@@ -108,13 +112,15 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
|
|
108
112
|
----------
|
109
113
|
strategy : dict, optional
|
110
114
|
The strategy to use for each component. If None, will use the *full*
|
111
|
-
strategy for all components
|
115
|
+
strategy for all components except ``"scrubbing"`` which will be set
|
116
|
+
to False (default None).
|
112
117
|
The keys of the dictionary should correspond to names of noise
|
113
118
|
components to include:
|
114
119
|
|
115
120
|
* ``motion``
|
116
121
|
* ``wm_csf``
|
117
122
|
* ``global_signal``
|
123
|
+
* ``scrubbing``
|
118
124
|
|
119
125
|
The values of dictionary should correspond to types of confounds
|
120
126
|
extracted from each signal:
|
@@ -124,10 +130,29 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
|
|
124
130
|
* ``derivatives`` : signal + derivatives
|
125
131
|
* ``full`` : signal + deriv. + quadratic terms + power2 deriv.
|
126
132
|
|
133
|
+
except ``scrubbing`` which needs to be bool.
|
127
134
|
spike : float, optional
|
128
135
|
If None, no spike regressor is added. If spike is a float, it will
|
129
136
|
add a spike regressor for every point at which framewise displacement
|
130
137
|
exceeds the specified float (default None).
|
138
|
+
scrub : int, optional
|
139
|
+
After accounting for time frames with excessive motion, further remove
|
140
|
+
segments shorter than the given number. When the value is 0, remove
|
141
|
+
time frames based on excessive framewise displacement and DVARS only.
|
142
|
+
If None and no ``"scrubbing"`` in ``strategy``, no scrubbing is
|
143
|
+
performed, else the default value is 0. The default value is referred
|
144
|
+
as full scrubbing (default None).
|
145
|
+
fd_threshold : float, optional
|
146
|
+
Framewise displacement threshold for scrub in mm. If None no
|
147
|
+
``"scrubbing"`` in ``strategy``, no scrubbing is performed, else the
|
148
|
+
default value is 0.5 (default None).
|
149
|
+
std_dvars_threshold : float, optional
|
150
|
+
Standardized DVARS threshold for scrub. DVARs is defined as root mean
|
151
|
+
squared intensity difference of volume N to volume N+1. D referring to
|
152
|
+
temporal derivative of timecourses, VARS referring to root mean squared
|
153
|
+
variance over voxels. If None and no ``"scrubbing"`` in ``strategy``,
|
154
|
+
no scrubbing is performed, else the default value is 1.5
|
155
|
+
(default None).
|
131
156
|
detrend : bool, optional
|
132
157
|
If True, detrending will be applied on timeseries, before confound
|
133
158
|
removal (default True).
|
@@ -149,18 +174,21 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
|
|
149
174
|
|
150
175
|
"""
|
151
176
|
|
152
|
-
_DEPENDENCIES: ClassVar[
|
177
|
+
_DEPENDENCIES: ClassVar[Dependencies] = {"numpy", "nilearn"}
|
153
178
|
|
154
179
|
def __init__(
|
155
180
|
self,
|
156
|
-
strategy: Optional[
|
181
|
+
strategy: Optional[dict[str, Union[str, bool]]] = None,
|
157
182
|
spike: Optional[float] = None,
|
183
|
+
scrub: Optional[int] = None,
|
184
|
+
fd_threshold: Optional[float] = None,
|
185
|
+
std_dvars_threshold: Optional[float] = None,
|
158
186
|
detrend: bool = True,
|
159
187
|
standardize: bool = True,
|
160
188
|
low_pass: Optional[float] = None,
|
161
189
|
high_pass: Optional[float] = None,
|
162
190
|
t_r: Optional[float] = None,
|
163
|
-
masks: Union[str,
|
191
|
+
masks: Union[str, dict, list[Union[dict, str]], None] = None,
|
164
192
|
) -> None:
|
165
193
|
"""Initialize the class."""
|
166
194
|
if strategy is None:
|
@@ -168,9 +196,13 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
|
|
168
196
|
"motion": "full",
|
169
197
|
"wm_csf": "full",
|
170
198
|
"global_signal": "full",
|
199
|
+
"scrubbing": False,
|
171
200
|
}
|
172
201
|
self.strategy = strategy
|
173
202
|
self.spike = spike
|
203
|
+
self.scrub = scrub
|
204
|
+
self.fd_threshold = fd_threshold
|
205
|
+
self.std_dvars_threshold = std_dvars_threshold
|
174
206
|
self.detrend = detrend
|
175
207
|
self.standardize = standardize
|
176
208
|
self.low_pass = low_pass
|
@@ -178,13 +210,22 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
|
|
178
210
|
self.t_r = t_r
|
179
211
|
self.masks = masks
|
180
212
|
|
181
|
-
self._valid_components = [
|
213
|
+
self._valid_components = [
|
214
|
+
"motion",
|
215
|
+
"wm_csf",
|
216
|
+
"global_signal",
|
217
|
+
"scrubbing",
|
218
|
+
]
|
182
219
|
self._valid_confounds = ["basic", "power2", "derivatives", "full"]
|
183
220
|
|
184
221
|
if any(not isinstance(k, str) for k in strategy.keys()):
|
185
222
|
raise_error("Strategy keys must be strings", ValueError)
|
186
223
|
|
187
|
-
if any(
|
224
|
+
if any(
|
225
|
+
not isinstance(v, str)
|
226
|
+
for k, v in strategy.items()
|
227
|
+
if k != "scrubbing"
|
228
|
+
):
|
188
229
|
raise_error("Strategy values must be strings", ValueError)
|
189
230
|
|
190
231
|
if any(x not in self._valid_components for x in strategy.keys()):
|
@@ -197,7 +238,11 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
|
|
197
238
|
klass=ValueError,
|
198
239
|
)
|
199
240
|
|
200
|
-
if any(
|
241
|
+
if any(
|
242
|
+
v not in self._valid_confounds
|
243
|
+
for k, v in strategy.items()
|
244
|
+
if k != "scrubbing"
|
245
|
+
):
|
201
246
|
raise_error(
|
202
247
|
msg=f"Invalid confound types {list(strategy.values())}. "
|
203
248
|
f"Valid confound types are {self._valid_confounds}.\n"
|
@@ -208,7 +253,7 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
|
|
208
253
|
)
|
209
254
|
super().__init__(on="BOLD", required_data_types=["BOLD"])
|
210
255
|
|
211
|
-
def get_valid_inputs(self) ->
|
256
|
+
def get_valid_inputs(self) -> list[str]:
|
212
257
|
"""Get valid data types for input.
|
213
258
|
|
214
259
|
Returns
|
@@ -237,7 +282,7 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
|
|
237
282
|
# Does not add any new keys
|
238
283
|
return input_type
|
239
284
|
|
240
|
-
def _map_adhoc_to_fmriprep(self, input:
|
285
|
+
def _map_adhoc_to_fmriprep(self, input: dict[str, Any]) -> None:
|
241
286
|
"""Map the adhoc format to the fmpriprep format spec.
|
242
287
|
|
243
288
|
Based on the spec, map the column names to match the fmriprep format.
|
@@ -267,8 +312,8 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
|
|
267
312
|
confounds_df.rename(columns=confounds_mapping, inplace=True)
|
268
313
|
|
269
314
|
def _process_fmriprep_spec(
|
270
|
-
self, input:
|
271
|
-
) ->
|
315
|
+
self, input: dict[str, Any]
|
316
|
+
) -> tuple[list[str], dict[str, str], dict[str, str], str]:
|
272
317
|
"""Process the fmpriprep format spec from the specified file.
|
273
318
|
|
274
319
|
Based on the strategy, find the relevant column names in the dataframe,
|
@@ -300,7 +345,7 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
|
|
300
345
|
|
301
346
|
Raises
|
302
347
|
------
|
303
|
-
|
348
|
+
RuntimeError
|
304
349
|
If invalid confounds file is found.
|
305
350
|
|
306
351
|
"""
|
@@ -313,50 +358,62 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
|
|
313
358
|
derivatives_to_compute = {} # the dictionary of missing derivatives
|
314
359
|
|
315
360
|
for t_kind, t_strategy in self.strategy.items():
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
to_select.append(
|
345
|
-
if
|
346
|
-
|
361
|
+
if t_kind != "scrubbing":
|
362
|
+
t_basics = FMRIPREP_BASICS[t_kind]
|
363
|
+
|
364
|
+
if any(x not in available_vars for x in t_basics):
|
365
|
+
missing = [x for x in t_basics if x not in available_vars]
|
366
|
+
raise_error(
|
367
|
+
msg=(
|
368
|
+
"Invalid confounds file. Missing basic confounds: "
|
369
|
+
f"{missing}. "
|
370
|
+
"Check if this file is really an fmriprep "
|
371
|
+
"confounds file. You can also modify the confound "
|
372
|
+
"removal strategy."
|
373
|
+
),
|
374
|
+
klass=RuntimeError,
|
375
|
+
)
|
376
|
+
|
377
|
+
to_select.extend(t_basics)
|
378
|
+
|
379
|
+
if t_strategy in ["power2", "full"]:
|
380
|
+
for x in t_basics:
|
381
|
+
x_2 = f"{x}_power2"
|
382
|
+
to_select.append(x_2)
|
383
|
+
if x_2 not in available_vars:
|
384
|
+
squares_to_compute[x_2] = x
|
385
|
+
|
386
|
+
if t_strategy in ["derivatives", "full"]:
|
387
|
+
for x in t_basics:
|
388
|
+
x_derivative = f"{x}_derivative1"
|
389
|
+
to_select.append(x_derivative)
|
390
|
+
if x_derivative not in available_vars:
|
391
|
+
derivatives_to_compute[x_derivative] = x
|
392
|
+
if t_strategy == "full":
|
393
|
+
x_derivative_2 = f"{x_derivative}_power2"
|
394
|
+
to_select.append(x_derivative_2)
|
395
|
+
if x_derivative_2 not in available_vars:
|
396
|
+
squares_to_compute[x_derivative_2] = (
|
397
|
+
x_derivative
|
398
|
+
)
|
399
|
+
# Add spike
|
347
400
|
spike_name = "framewise_displacement"
|
348
401
|
if self.spike is not None:
|
349
402
|
if spike_name not in available_vars:
|
350
403
|
raise_error(
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
404
|
+
msg=(
|
405
|
+
"Invalid confounds file. Missing "
|
406
|
+
"framewise_displacement (spike) confound. "
|
407
|
+
"Check if this file is really an fmriprep confounds "
|
408
|
+
"file. You can also deactivate spike "
|
409
|
+
"(set spike = None)."
|
410
|
+
),
|
411
|
+
klass=RuntimeError,
|
355
412
|
)
|
356
413
|
out = to_select, squares_to_compute, derivatives_to_compute, spike_name
|
357
414
|
return out
|
358
415
|
|
359
|
-
def _pick_confounds(self, input:
|
416
|
+
def _pick_confounds(self, input: dict[str, Any]) -> pd.DataFrame:
|
360
417
|
"""Select relevant confounds from the specified file.
|
361
418
|
|
362
419
|
Parameters
|
@@ -375,14 +432,12 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
|
|
375
432
|
if confounds_format == "adhoc":
|
376
433
|
self._map_adhoc_to_fmriprep(input)
|
377
434
|
|
378
|
-
processed_spec = self._process_fmriprep_spec(input)
|
379
|
-
|
380
435
|
(
|
381
436
|
to_select,
|
382
437
|
squares_to_compute,
|
383
438
|
derivatives_to_compute,
|
384
439
|
spike_name,
|
385
|
-
) =
|
440
|
+
) = self._process_fmriprep_spec(input)
|
386
441
|
# Copy the confounds
|
387
442
|
out_df = input["data"].copy()
|
388
443
|
|
@@ -413,9 +468,70 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
|
|
413
468
|
|
414
469
|
return out_df
|
415
470
|
|
471
|
+
def _get_scrub_regressors(
|
472
|
+
self, confounds_df: pd.DataFrame
|
473
|
+
) -> pd.DataFrame:
|
474
|
+
"""Get motion outline regressors.
|
475
|
+
|
476
|
+
Parameters
|
477
|
+
----------
|
478
|
+
confounds_df : pandas.DataFrame
|
479
|
+
pandas.DataFrame with all confounds.
|
480
|
+
|
481
|
+
Returns
|
482
|
+
-------
|
483
|
+
pandas.DataFrame
|
484
|
+
pandas.DataFrame of motion outline regressors.
|
485
|
+
|
486
|
+
Raises
|
487
|
+
------
|
488
|
+
RuntimeError
|
489
|
+
If ``std_dvars`` and / or ``framewise_displacement`` is not found
|
490
|
+
in ``confounds_df``.
|
491
|
+
|
492
|
+
"""
|
493
|
+
# Check columns
|
494
|
+
if (
|
495
|
+
self.std_dvars_threshold is not None
|
496
|
+
and "std_dvars" not in confounds_df.columns
|
497
|
+
):
|
498
|
+
raise_error(
|
499
|
+
msg=(
|
500
|
+
"Invalid confounds file. Missing std_dvars "
|
501
|
+
"(standardized DVARS) confound. "
|
502
|
+
"Check if this file is really an fMRIPrep confounds file. "
|
503
|
+
),
|
504
|
+
klass=RuntimeError,
|
505
|
+
)
|
506
|
+
if (
|
507
|
+
self.fd_threshold is not None
|
508
|
+
and "framewise_displacement" not in confounds_df.columns
|
509
|
+
):
|
510
|
+
raise_error(
|
511
|
+
msg=(
|
512
|
+
"Invalid confounds file. Missing framewise_displacement "
|
513
|
+
"confound. "
|
514
|
+
"Check if this file is really an fMRIPrep confounds file. "
|
515
|
+
),
|
516
|
+
klass=RuntimeError,
|
517
|
+
)
|
518
|
+
# Use function from nilearn to not reinvent the wheel
|
519
|
+
return _load_scrub(
|
520
|
+
confounds_raw=confounds_df,
|
521
|
+
scrub=self.scrub if self.scrub is not None else 0,
|
522
|
+
fd_threshold=(
|
523
|
+
self.fd_threshold if self.fd_threshold is not None else 0.5
|
524
|
+
),
|
525
|
+
std_dvars_threshold=(
|
526
|
+
self.std_dvars_threshold
|
527
|
+
if self.std_dvars_threshold is not None
|
528
|
+
else 1.5
|
529
|
+
),
|
530
|
+
)
|
531
|
+
|
416
532
|
def _validate_data(
|
417
533
|
self,
|
418
|
-
input:
|
534
|
+
input: dict[str, Any],
|
419
535
|
) -> None:
|
420
536
|
"""Validate input data.
|
421
537
|
|
@@ -503,9 +619,9 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
|
|
503
619
|
|
504
620
|
def preprocess(
|
505
621
|
self,
|
506
|
-
input:
|
507
|
-
extra_input: Optional[
|
508
|
-
) ->
|
622
|
+
input: dict[str, Any],
|
623
|
+
extra_input: Optional[dict[str, Any]] = None,
|
624
|
+
) -> tuple[dict[str, Any], Optional[dict[str, dict[str, Any]]]]:
|
509
625
|
"""Preprocess.
|
510
626
|
|
511
627
|
Parameters
|
@@ -539,31 +655,78 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
|
|
539
655
|
logger.info(
|
540
656
|
f"Read t_r from NIfTI header: {t_r}",
|
541
657
|
)
|
658
|
+
|
659
|
+
# Create element-specific tempdir for storing generated data
|
660
|
+
# and / or mask
|
661
|
+
element_tempdir = WorkDirManager().get_element_tempdir(
|
662
|
+
prefix="fmriprep_confound_remover"
|
663
|
+
)
|
664
|
+
|
542
665
|
# Set mask data
|
543
666
|
mask_img = None
|
544
667
|
if self.masks is not None:
|
668
|
+
# Generate mask
|
545
669
|
logger.debug(f"Masking with {self.masks}")
|
546
|
-
mask_img =
|
547
|
-
|
670
|
+
mask_img = get_data(
|
671
|
+
kind="mask",
|
672
|
+
names=self.masks,
|
673
|
+
target_data=input,
|
674
|
+
extra_input=extra_input,
|
548
675
|
)
|
549
|
-
#
|
676
|
+
# Save generated mask for use later
|
677
|
+
generated_mask_img_path = element_tempdir / "generated_mask.nii.gz"
|
678
|
+
nib.save(mask_img, generated_mask_img_path)
|
679
|
+
|
680
|
+
# Save BOLD mask and link it to the BOLD data type dict;
|
550
681
|
# this allows to use "inherit" down the pipeline
|
551
682
|
logger.debug("Setting `BOLD.mask`")
|
552
683
|
input.update(
|
553
684
|
{
|
554
685
|
"mask": {
|
686
|
+
# Update path to sync with "data"
|
687
|
+
"path": generated_mask_img_path,
|
688
|
+
# Update data
|
555
689
|
"data": mask_img,
|
690
|
+
# Should be in the same space as target data
|
556
691
|
"space": input["space"],
|
557
692
|
}
|
558
693
|
}
|
559
694
|
)
|
695
|
+
|
696
|
+
signal_clean_kwargs = {}
|
697
|
+
# Set up scrubbing mask if needed
|
698
|
+
if self.strategy.get("scrubbing", False):
|
699
|
+
motion_outline_regressors = self._get_scrub_regressors(
|
700
|
+
input["confounds"]["data"]
|
701
|
+
)
|
702
|
+
# Add regressors to confounds
|
703
|
+
confounds_df = pd.concat(
|
704
|
+
[confounds_df, motion_outline_regressors], axis=1
|
705
|
+
)
|
706
|
+
# Get sample mask
|
707
|
+
sample_mask, confounds_df = prepare_output(
|
708
|
+
confounds=confounds_df, demean=False
|
709
|
+
)
|
710
|
+
signal_clean_kwargs.update(
|
711
|
+
{
|
712
|
+
"clean__sample_mask": sample_mask,
|
713
|
+
}
|
714
|
+
)
|
560
715
|
# Clean image
|
561
716
|
logger.info("Cleaning image using nilearn")
|
717
|
+
logger.debug(f"\tstrategy: {self.strategy}")
|
718
|
+
logger.debug(f"\tspike: {self.spike}")
|
719
|
+
logger.debug(f"\tscrub: {self.scrub}")
|
720
|
+
logger.debug(f"\tfd_threshold: {self.fd_threshold}")
|
721
|
+
logger.debug(f"\tstd_dvars_threshold: {self.std_dvars_threshold}")
|
562
722
|
logger.debug(f"\tdetrend: {self.detrend}")
|
563
723
|
logger.debug(f"\tstandardize: {self.standardize}")
|
564
724
|
logger.debug(f"\tlow_pass: {self.low_pass}")
|
565
725
|
logger.debug(f"\thigh_pass: {self.high_pass}")
|
566
|
-
|
726
|
+
logger.debug(f"\tt_r: {self.t_r}")
|
727
|
+
|
728
|
+
# Deconfound data
|
729
|
+
cleaned_img = nimg.clean_img(
|
567
730
|
imgs=bold_img,
|
568
731
|
detrend=self.detrend,
|
569
732
|
standardize=self.standardize,
|
@@ -572,6 +735,22 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
|
|
572
735
|
high_pass=self.high_pass,
|
573
736
|
t_r=t_r,
|
574
737
|
mask_img=mask_img,
|
738
|
+
**signal_clean_kwargs,
|
739
|
+
)
|
740
|
+
# Fix t_r as nilearn messes it up
|
741
|
+
cleaned_img.header["pixdim"][4] = t_r
|
742
|
+
# Save deconfounded data
|
743
|
+
deconfounded_img_path = element_tempdir / "deconfounded_data.nii.gz"
|
744
|
+
nib.save(cleaned_img, deconfounded_img_path)
|
745
|
+
|
746
|
+
logger.debug("Updating `BOLD`")
|
747
|
+
input.update(
|
748
|
+
{
|
749
|
+
# Update path to sync with "data"
|
750
|
+
"path": deconfounded_img_path,
|
751
|
+
# Update data
|
752
|
+
"data": cleaned_img,
|
753
|
+
}
|
575
754
|
)
|
576
755
|
|
577
756
|
return input, None
|
File without changes
|