Coverage for nilearn/interfaces/fmriprep/load_confounds.py: 12%
74 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-18 13:00 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-18 13:00 +0200
1"""Flexible method to load confounds generated by fMRIprep."""
3import warnings
5import pandas as pd
7from nilearn._utils.logger import find_stack_level
8from nilearn.interfaces.fmriprep import load_confounds_components as components
9from nilearn.interfaces.fmriprep.load_confounds_utils import (
10 MissingConfoundError,
11 get_confounds_file,
12 get_json,
13 load_confounds_file_as_dataframe,
14 load_confounds_json,
15 prepare_output,
16 sanitize_confounds,
17)
19# Global variables listing the admissible types of noise components
20all_confounds = [
21 "motion",
22 "high_pass",
23 "wm_csf",
24 "global_signal",
25 "compcor",
26 "ica_aroma",
27 "scrub",
28 "non_steady_state",
29]
31# extra parameters needed for each noise component
32component_parameters = {
33 "motion": ["motion"],
34 "wm_csf": ["wm_csf"],
35 "global_signal": ["global_signal"],
36 "compcor": ["meta_json", "compcor", "n_compcor"],
37 "ica_aroma": ["ica_aroma"],
38 "scrub": ["scrub", "fd_threshold", "std_dvars_threshold"],
39}
42def _check_strategy(strategy):
43 """Ensure the denoising strategies combinations are valid.
45 Parameters
46 ----------
47 strategy : :obj:`tuple` or :obj:`list` of :obj:`str`.
48 See :func:`nilearn.interfaces.fmriprep.load_confounds` for details.
50 Raises
51 ------
52 ValueError
53 If any of the confounds specified in the strategy are not supported,
54 or the combination of the strategies are not valid.
55 """
56 if (not isinstance(strategy, tuple)) and (not isinstance(strategy, list)):
57 raise ValueError(
58 "strategy needs to be a tuple or list of strings"
59 f" A {type(strategy)} was provided instead."
60 )
62 if len(strategy) == 0:
63 warnings.warn(
64 "strategy is empty, confounds will return None.",
65 stacklevel=find_stack_level(),
66 )
68 for conf in strategy:
69 if conf == "non_steady_state":
70 warnings.warn(
71 "Non-steady state volumes are always detected. It "
72 "doesn't need to be supplied as part of the "
73 "strategy. Supplying non_steady_state in strategy "
74 "will not have additional effect.",
75 stacklevel=find_stack_level(),
76 )
77 if conf not in all_confounds:
78 raise ValueError(f"{conf} is not a supported type of confounds.")
80 # high pass filtering must be present if using fmriprep compcor outputs
81 if ("compcor" in strategy) and ("high_pass" not in strategy):
82 raise ValueError(
83 "When using compcor, `high_pass` must be included in "
84 f"strategy. Current strategy: '{strategy}'"
85 )
88def _check_error(missing):
89 """Consolidate a single error message across multiple missing confounds."""
90 if missing["confounds"] or missing["keywords"]:
91 error_msg = (
92 "The following keywords or parameters are missing: "
93 f" {missing['confounds']}"
94 f" {missing['keywords']}"
95 ". You may want to try a different denoising strategy."
96 )
97 raise ValueError(error_msg)
100def load_confounds(
101 img_files,
102 strategy=("motion", "high_pass", "wm_csf"),
103 motion="full",
104 scrub=5,
105 fd_threshold=0.2,
106 std_dvars_threshold=3,
107 wm_csf="basic",
108 global_signal="basic",
109 compcor="anat_combined",
110 n_compcor="all",
111 ica_aroma="full",
112 demean=True,
113):
114 """
115 Use confounds from :term:`fMRIPrep`.
117 To enable easy confound variables loading from :term:`fMRIPrep` outputs,
118 `load_confounds` provides an interface that groups subsets of confound
119 variables into noise components and their parameters. It is possible to
120 fine-tune a subset of noise components and their parameters through this
121 function.
123 The implementation will only support :term:`fMRIPrep` functional derivative
124 directory from the 1.2.x series. The `compcor` noise component requires
125 1.4.x series or above.
127 .. versionadded:: 0.9.0
129 Parameters
130 ----------
131 img_files : :obj:`str` or :obj:`list` of :obj:`str`
132 Path of processed nii.gz/dtseries.nii/func.gii file reside in a
133 :term:`fMRIPrep` generated functional derivative directory (i.e.The
134 associated confound files should be in the same directory as the image
135 file). As long as the image file, confound related tsv and json are in
136 the same directory with BIDS-complied names, `load_confounds` can
137 retrieve the relevant files correctly.
139 - `nii.gz` or `dtseries.nii`: path to files, optionally as a list.
140 - `func.gii`: list of a pair of paths to files, optionally as a list
141 of lists.
143 strategy : :obj:`tuple` or :obj:`list` of :obj:`str`, \
144 default=("motion", "high_pass", "wm_csf")
145 The type of noise components to include.
147 - "motion": head motion estimates. Associated parameter: `motion`
148 - "wm_csf" confounds derived from white matter and cerebrospinal fluid.
149 Associated parameter: `wm_csf`
150 - "global_signal" confounds derived from the global signal.
151 Associated parameter: `global_signal`
152 - "compcor" confounds derived from CompCor (:footcite:t:`Behzadi2007`).
153 When using this noise component, "high_pass" must also be applied.
154 Associated parameter: `compcor`, `n_compcor`
155 - "ica_aroma" confounds derived
156 from ICA-AROMA (:footcite:t:`Pruim2015`).
157 Associated parameter: `ica_aroma`
158 - "scrub" regressors for :footcite:t:`Power2014` scrubbing approach.
159 Associated parameter: `scrub`, `fd_threshold`, `std_dvars_threshold`
161 For each component above, associated parameters will be applied if
162 specified. If associated parameters are not specified, any values
163 supplied to the parameters are ignored.
164 For example, `strategy=('motion', 'global_signal')` will allow users
165 to supply input to associated parameter `motion` and `global_signal`;
166 if users pass `wm_csf` parameter, it will not be applied as it is not
167 part of the `strategy`.
169 There are two additional noise components with no optional parameters.
171 - "non_steady_state" denotes volumes collected before
172 the :term:`fMRI` scanner has reached a stable state.
173 - "high_pass" adds discrete cosines transformation
174 basis regressors to handle low-frequency signal drifts.
176 Non-steady-state volumes will always be checked. There's no need to
177 supply this component to the strategy.
179 motion : :obj:`str`, default="full"
180 Type of confounds extracted from head motion estimates.
182 - "basic" translation/rotation (6 parameters)
183 - "power2" translation/rotation + quadratic terms (12 parameters)
184 - "derivatives" translation/rotation + derivatives (12 parameters)
185 - "full" translation/rotation + derivatives + quadratic terms + power2d
186 derivatives (24 parameters)
188 wm_csf : :obj:`str`, default="basic"
189 Type of confounds extracted from masks of white matter and
190 cerebrospinal fluids.
192 - "basic" the averages in each mask (2 parameters)
193 - "power2" averages and quadratic terms (4 parameters)
194 - "derivatives" averages and derivatives (4 parameters)
195 - "full" averages + derivatives + quadratic terms + power2d derivatives
196 (8 parameters)
198 global_signal : :obj:`str`, default="basic"
199 Type of confounds extracted from the global signal.
201 - "basic" just the global signal (1 parameter)
202 - "power2" global signal and quadratic term (2 parameters)
203 - "derivatives" global signal and derivative (2 parameters)
204 - "full" global signal + derivatives + quadratic terms + power2d
205 derivatives (4 parameters)
207 scrub : :obj:`int`, default=5
208 After accounting for time frames with excessive motion, further remove
209 segments shorter than the given number. The default value is referred
210 as full scrubbing in :footcite:t:`Power2014`. When the value is 0,
211 remove time frames based on excessive framewise displacement and
212 DVARS only.
214 fd_threshold : :obj:`float`, default=0.2
216 .. deprecated:: 0.10.3
217 The default value will be changed to 0.5 in 0.13.0
219 Framewise displacement threshold for scrub in mm.
221 std_dvars_threshold : :obj:`float`, default=3
223 .. deprecated:: 0.10.3
224 The default value will be changed to 1.5 in 0.13.0
226 Standardized DVARS threshold for scrub.
227 The default threshold matching :term:`fMRIPrep`.
228 DVARs is defined as root mean squared intensity difference of volume N
229 to volume N+1 :footcite:t:`Power2012`.
230 D referring to temporal derivative of timecourses,
231 VARS referring to root mean squared variance over voxels.
233 compcor : :obj:`str`, default="anat_combined"
235 .. warning::
236 Require :term:`fMRIPrep` >= v:1.4.0.
238 Type of confounds extracted from a component based noise correction
239 method :footcite:t:`Behzadi2007`.
241 - "anat_combined" noise components calculated using a white matter and
242 CSF combined anatomical mask
243 - "anat_separated" noise components calculated using white matter mask
244 and CSF mask compcor separately; two sets of scores are concatenated
245 - "temporal" noise components calculated using temporal compcor
246 - "temporal_anat_combined" components of "temporal" and "anat_combined"
247 - "temporal_anat_separated" components of "temporal" and
248 "anat_separated"
250 n_compcor : :obj:`str` or :obj:`int`, default="all"
251 The number of noise components to be extracted.
252 For acompcor_combined=False, and/or compcor="full", this is the number
253 of components per mask.
254 "all": select all components (50% variance explained by
255 :term:`fMRIPrep` defaults)
257 ica_aroma : :obj:`str`, default="full"
259 - "full": use :term:`fMRIPrep` output
260 `~desc-smoothAROMAnonaggr_bold.nii.gz`.
261 - "basic": use noise independent components only.
263 demean : :obj:`bool`, default=True
264 If True, the confounds are standardized to a zero mean (over time).
265 When using :class:`nilearn.maskers.NiftiMasker` with default
266 parameters, the recommended option is True.
267 When using :func:`nilearn.signal.clean` with default parameters, the
268 recommended option is False.
269 When `sample_mask` is not None, the mean is calculated on retained
270 volumes.
272 Returns
273 -------
274 confounds : :class:`pandas.DataFrame`, or :obj:`list` of \
275 :class:`pandas.DataFrame`
276 A reduced version of :term:`fMRIPrep` confounds based on selected
277 strategy and flags.
278 The columns contains the labels of the regressors.
280 sample_mask : None, :class:`numpy.ndarray` or, :obj:`list` of \
281 :class:`numpy.ndarray` or None
282 When no volumns require removal, the value is None.
283 Otherwise, shape: (number of scans - number of volumes removed, )
284 The index of the niimgs along time/fourth dimension for valid volumes
285 for subsequent analysis.
286 This attribute should be passed to parameter `sample_mask` of
287 :class:`nilearn.maskers.NiftiMasker` or
288 :func:`nilearn.signal.clean`.
289 Volumns are removed if flagged as following:
291 - Non-steady-state volumes (if present)
292 - Motion outliers detected by scrubbing
294 Notes
295 -----
296 The noise components implemented in this class are adapted from
297 :footcite:t:`Ciric2017`. Band-pass filter is replaced by high-pass filter.
298 Low-pass filters can be implemented, e.g., through `NifitMaskers`.
299 Other aspects of the preprocessing listed
300 in :footcite:t:`Ciric2017` are controlled
301 through :term:`fMRIPrep`, e.g. distortion correction.
303 See Also
304 --------
305 :func:`nilearn.interfaces.fmriprep.load_confounds_strategy`
307 References
308 ----------
309 .. footbibliography::
311 """
312 _check_strategy(strategy)
313 if "scrub" in strategy and fd_threshold == 0.2:
314 fd_threshold_default = (
315 "The default parameter for fd_threshold is currently 0.2 "
316 "which is inconsistent with the fMRIPrep default of 0.5. "
317 "In release 0.13.0, "
318 "the default strategy will be replaced by 0.5."
319 )
320 warnings.warn(
321 category=DeprecationWarning,
322 message=fd_threshold_default,
323 stacklevel=find_stack_level(),
324 )
325 if "scrub" in strategy and std_dvars_threshold == 3:
326 std_dvars_threshold_default = (
327 "The default parameter for std_dvars_threshold is currently 3 "
328 "which is inconsistent with the fMRIPrep default of 1.5. "
329 "In release 0.13.0, "
330 "the default strategy will be replaced by 1.5."
331 )
332 warnings.warn(
333 category=DeprecationWarning,
334 message=std_dvars_threshold_default,
335 stacklevel=find_stack_level(),
336 )
337 # load confounds per image provided
338 img_files, flag_single = sanitize_confounds(img_files)
339 confounds_out = []
340 sample_mask_out = []
341 for file in img_files:
342 sample_mask, conf = _load_confounds_for_single_image_file(
343 file,
344 strategy,
345 demean,
346 motion=motion,
347 scrub=scrub,
348 fd_threshold=fd_threshold,
349 std_dvars_threshold=std_dvars_threshold,
350 wm_csf=wm_csf,
351 global_signal=global_signal,
352 compcor=compcor,
353 n_compcor=n_compcor,
354 ica_aroma=ica_aroma,
355 )
356 confounds_out.append(conf)
357 sample_mask_out.append(sample_mask)
359 # If a single input was provided,
360 # send back a single output instead of a list
361 if flag_single:
362 confounds_out = confounds_out[0]
363 sample_mask_out = sample_mask_out[0]
365 # If no strategy was provided, return None for confounds
366 if len(strategy) == 0:
367 confounds_out = None
369 return confounds_out, sample_mask_out
372def _load_confounds_for_single_image_file(
373 image_file, strategy, demean, **kwargs
374):
375 """Load confounds for a single image file.
377 Parameters
378 ----------
379 image_file : :obj:`str`
380 Path to processed image file.
382 strategy : :obj:`tuple` or :obj:`list` of :obj:`str`.
383 See :func:`nilearn.interfaces.fmriprep.load_confounds` for details.
385 demean : :obj:`bool`, default=True
386 See :func:`nilearn.interfaces.fmriprep.load_confounds` for details.
388 kwargs : :obj:`dict`
389 Extra relevant parameters for the given `strategy`.
390 See :func:`nilearn.interfaces.fmriprep.load_confounds` for details.
392 Returns
393 -------
394 sample_mask : None, :class:`numpy.ndarray` or, :obj:`list` of \
395 :class:`numpy.ndarray` or None
396 See :func:`nilearn.interfaces.fmriprep.load_confounds` for details.
398 confounds : :class:`pandas.DataFrame`, or :obj:`list` of \
399 :class:`pandas.DataFrame`
400 See :func:`nilearn.interfaces.fmriprep.load_confounds` for details.
401 """
402 # Check for ica_aroma in strategy, this will change the required image_file
403 flag_full_aroma = ("ica_aroma" in strategy) and (
404 kwargs.get("ica_aroma") == "full"
405 )
407 confounds_file = get_confounds_file(
408 image_file, flag_full_aroma=flag_full_aroma
409 )
410 confounds_json_file = get_json(confounds_file)
412 return _load_single_confounds_file(
413 confounds_file=confounds_file,
414 strategy=strategy,
415 demean=demean,
416 confounds_json_file=confounds_json_file,
417 **kwargs,
418 )
421def _load_single_confounds_file(
422 confounds_file, strategy, demean=True, confounds_json_file=None, **kwargs
423):
424 """Load and extract specified confounds from the confounds file.
426 Parameters
427 ----------
428 confounds_file : :obj:`str`
429 Path to confounds file.
431 strategy : :obj:`tuple` or :obj:`list` of :obj:`str`.
432 See :func:`nilearn.interfaces.fmriprep.load_confounds` for details.
434 demean : :obj:`bool`, default=True
435 See :func:`nilearn.interfaces.fmriprep.load_confounds` for details.
437 confounds_json_file : :obj:`str`, default=None
438 Path to confounds json file.
440 kwargs : :obj:`dict`
441 Extra relevant parameters for the given `strategy`.
442 See :func:`nilearn.interfaces.fmriprep.load_confounds` for details.
444 Returns
445 -------
446 confounds : :class:`pandas.DataFrame`
447 See :func:`nilearn.interfaces.fmriprep.load_confounds` for details.
449 Raises
450 ------
451 ValueError
452 If any of the confounds specified in the strategy are not found in the
453 confounds file or confounds json file.
454 """
455 flag_acompcor = ("compcor" in strategy) and (
456 "anat" in kwargs.get("compcor")
457 )
458 # Convert tsv file to pandas dataframe
459 confounds_all = load_confounds_file_as_dataframe(confounds_file)
461 if confounds_json_file is None:
462 confounds_json_file = get_json(confounds_file)
464 # Read the associated json file
465 meta_json = load_confounds_json(
466 confounds_json_file, flag_acompcor=flag_acompcor
467 )
469 missing = {"confounds": [], "keywords": []}
470 # always check non steady state volumes are loaded
471 confounds_select, missing = _load_noise_component(
472 confounds_all,
473 "non_steady_state",
474 missing,
475 meta_json=meta_json,
476 **kwargs,
477 )
478 for component in strategy:
479 loaded_confounds, missing = _load_noise_component(
480 confounds_all, component, missing, meta_json=meta_json, **kwargs
481 )
482 confounds_select = pd.concat(
483 [confounds_select, loaded_confounds], axis=1
484 )
486 _check_error(missing) # raise any missing
487 return prepare_output(confounds_select, demean)
490def _load_noise_component(confounds_raw, component, missing, **kargs):
491 """Load confound of a single noise component.
493 Parameters
494 ----------
495 confounds_raw : :class:`pandas.DataFrame`
496 The confounds loaded from the confounds file.
498 component : :obj:`str`
499 The noise component to be loaded. The item from the strategy list.
501 missing : :obj:`dict`
502 A dictionary of missing confounds and noise component keywords.
504 kargs : :obj:`dict`
505 Extra relevant parameters for the given `component`.
506 See :func:`nilearn.interfaces.fmriprep.load_confounds` for details.
508 Returns
509 -------
510 loaded_confounds : :class:`pandas.DataFrame`
511 The confounds loaded from the confounds file for the given component.
513 missing : :obj:`dict`
514 A dictionary of missing confounds and noise component keywords.
516 Raises
517 ------
518 MissingConfoundError
519 If any of the confounds specified in the strategy are not found in the
520 confounds file or confounds json file.
521 """
522 try:
523 need_params = component_parameters.get(component)
524 if need_params:
525 params = {param: kargs.get(param) for param in need_params}
526 loaded_confounds = getattr(components, f"_load_{component}")(
527 confounds_raw, **params
528 )
529 else:
530 loaded_confounds = getattr(components, f"_load_{component}")(
531 confounds_raw
532 )
533 except MissingConfoundError as exception:
534 missing["confounds"] += exception.params
535 missing["keywords"] += exception.keywords
536 loaded_confounds = pd.DataFrame()
537 return loaded_confounds, missing