Coverage for nilearn/interfaces/fmriprep/tests/test_load_confounds_scrub.py: 0%
55 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-16 12:32 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-16 12:32 +0200
1import numpy as np
2import pandas as pd
3import pytest
4from pandas.testing import assert_frame_equal
6from nilearn.interfaces.fmriprep.load_confounds_scrub import (
7 _get_outlier_cols,
8 extract_outlier_regressors,
9 optimize_scrub,
10)
13@pytest.mark.parametrize(
14 "original_motion_outliers_index,expected_optimal",
15 [
16 (
17 [3, 11, 13, 80, 97],
18 np.array([0, 1, 2, 3, 11, 12, 13, 80, 97, 98, 99]),
19 ),
20 (
21 [11, 13, 16, 44, 50, 80], # middle volumes
22 np.array([11, 12, 13, 14, 15, 16, 44, 50, 80]),
23 ),
24 ([4], np.array([0, 1, 2, 3, 4])), # head volumes
25 ([96], np.array([96, 97, 98, 99])), # tail volumes
26 ([5], np.array([5])),
27 ],
28) # no optimization needed
29def test_optimize_scrub(original_motion_outliers_index, expected_optimal):
30 """Check the segment removal is acting correctly."""
31 # simulated labels with 100 time frames and remove any segment under
32 # 5 volumes
33 optimised_index = optimize_scrub(original_motion_outliers_index, 100, 5)
34 assert np.array_equal(optimised_index, expected_optimal)
37def test_get_outlier_cols():
38 """Check the non-steady state columns are detached."""
39 col_names = ["confound_regressor"]
40 non_steady_state = [f"non_steady_state_outlier{i:02d}" for i in range(3)]
41 col_names += non_steady_state
42 col_names = pd.Index(col_names)
43 outlier_cols, confounds_cols = _get_outlier_cols(col_names)
44 assert confounds_cols == ["confound_regressor"]
45 assert outlier_cols == non_steady_state
48def test_extract_outlier_regressors(rng):
49 """Check outlier regressors of different types."""
50 # Create a fake confound dataframe
51 n_scans = 50
52 fake_confounds = pd.DataFrame(
53 rng.random((n_scans, 1)), columns=["confound_regressor"]
54 )
56 # scrubbed volume one-hot, overlap with non-steady-state
57 idx_scrubbed = [2, 4, 34, 44]
58 scrub_vol = pd.DataFrame(
59 np.eye(n_scans)[:, idx_scrubbed],
60 columns=[f"motion_outlier{i:02d}" for i in range(len(idx_scrubbed))],
61 )
62 # First three volumes are non-steady-state
63 non_steady_vol = pd.DataFrame(
64 np.eye(n_scans)[:, :3],
65 columns=[f"non_steady_state_outlier{i:02d}" for i in range(3)],
66 )
68 # non-steady only
69 non_steady_conf = pd.concat([fake_confounds, non_steady_vol], axis=1)
70 sample_mask, confounds, outliers = extract_outlier_regressors(
71 non_steady_conf
72 )
73 assert np.array_equal(sample_mask, np.arange(n_scans)[3:]) is True
74 assert_frame_equal(outliers, non_steady_vol)
75 assert_frame_equal(confounds, fake_confounds)
77 # scrub only
78 srub_conf = pd.concat([fake_confounds, scrub_vol], axis=1)
79 make_mask = np.delete(np.arange(n_scans), idx_scrubbed)
80 sample_mask, confounds, outliers = extract_outlier_regressors(srub_conf)
81 assert np.array_equal(sample_mask, make_mask) is True
82 assert_frame_equal(outliers, scrub_vol)
83 assert_frame_equal(confounds, fake_confounds)
85 # scrub and non-steady state
86 all_conf = pd.concat([fake_confounds, non_steady_vol, scrub_vol], axis=1)
87 make_mask = np.delete(np.arange(n_scans), idx_scrubbed)[2:]
88 make_outliers = pd.concat([non_steady_vol, scrub_vol], axis=1)
89 make_outliers = make_outliers.reindex(
90 sorted(make_outliers.columns), axis=1
91 )
92 make_outliers = make_outliers.drop(columns="non_steady_state_outlier02")
94 sample_mask, confounds, outliers = extract_outlier_regressors(all_conf)
95 assert len(sample_mask) == 44
96 assert np.array_equal(sample_mask, make_mask) is True
97 assert_frame_equal(outliers, make_outliers)
98 assert_frame_equal(confounds, fake_confounds)
101@pytest.mark.parametrize(
102 "outlier_type",
103 ["motion_outlier", "non_steady_state_outlier"],
104)
105def test_warning_no_volumes_left(outlier_type):
106 """Check warning is thrown when all volumes in a run are scrubbed."""
107 rng = np.random.default_rng()
108 n_scans = 10
109 fake_confounds = pd.DataFrame(
110 rng.random((n_scans, 1)), columns=["confound_regressor"]
111 )
113 # scrubbed volume one-hot, overlap with non-steady-state
114 idx_scrubbed = np.arange(n_scans)
115 scrub_vol = pd.DataFrame(
116 np.eye(n_scans)[:, idx_scrubbed],
117 columns=[f"{outlier_type}{i:02d}" for i in range(len(idx_scrubbed))],
118 )
120 srub_conf = pd.concat([fake_confounds, scrub_vol], axis=1)
122 with pytest.warns(
123 RuntimeWarning,
124 match="All volumes were marked as motion outliers.",
125 ):
126 sample_mask, _, _ = extract_outlier_regressors(srub_conf)
127 assert sample_mask.size == 0