def test_sample_mask(): """Test load method and sample mask.""" # create a version with srub_mask not applied; # This is not recommanded conf = lc.Confounds(strategy=["motion", "scrub"], scrub="full", fd_thresh=0.15) reg, mask = conf.load(file_confounds) # the current test data has 6 time points marked as motion outliers, # and one nonsteady state (overlap with the first motion outlier) # 2 time points removed due to the "full" srubbing strategy assert reg.shape[0] - len(mask) == 8 # nilearn requires unmasked confound regressors assert reg.shape[0] == 30 # non steady state will always be removed conf = lc.Confounds(strategy=["motion"]) reg, mask = conf.load(file_confounds) assert reg.shape[0] - len(mask) == 1 # When no non-steady state volumes are present conf = lc.Confounds(strategy=["motion"]) reg, mask = conf.load(file_no_none_steady) assert mask is None
def test_not_found_exception(): conf = lc.Confounds(strategy=["high_pass", "motion", "global"], global_signal="full", motion="full") missing_params = ["trans_y", "trans_x_derivative1", "rot_z_power2"] missing_keywords = ["cosine"] file_missing_confounds = os.path.join( path_data, "missing_space-MNI152NLin2009cAsym_desc-preproc_bold.nii.gz") with pytest.raises(ValueError) as exc_info: conf.load(file_missing_confounds) assert f"{missing_params}" in exc_info.value.args[0] assert f"{missing_keywords}" in exc_info.value.args[0] # loading anat compcor should also raise an error, because the json file is # missing for that example dataset with pytest.raises(ValueError): conf = lc.Confounds(strategy=["compcor"], compcor="anat") conf.load(file_missing_confounds) # catch invalid compcor option with pytest.raises(KeyError): conf = lc.Confounds(strategy=["compcor"], compcor="blah") conf.load(file_confounds) # catch invalid compcor option with pytest.raises(ValueError): conf = lc.Confounds(strategy=["compcor"], compcor="full", acompcor_combined=None) conf.load(file_confounds)
def test_sanitize_strategy(): """Check that flawed strategy options generate meaningful error messages.""" with pytest.raises(ValueError): lc.Confounds(strategy="string") with pytest.raises(ValueError): lc.Confounds(strategy=["error"]) with pytest.raises(ValueError): lc.Confounds(strategy=[0])
def test_sanitize_strategy(): """Check that flawed strategy options generate meaningful error messages.""" with pytest.raises(ValueError): lc.Confounds(strategy="string") with pytest.raises(ValueError): lc.Confounds(strategy=["error"]) with pytest.raises(ValueError): lc.Confounds(strategy=[0]) conf = lc.Confounds(strategy=["motion"]) assert "non_steady_state" in conf.strategy
def test_n_motion(): conf = lc.Confounds(strategy=["motion"], motion="full", n_motion=0.2) conf.load(file_confounds) assert "motion_pca_1" in conf.confounds_.columns assert "motion_pca_2" not in conf.confounds_.columns conf = lc.Confounds(strategy=["motion"], motion="full", n_motion=0.95) conf.load(file_confounds) assert "motion_pca_6" in conf.confounds_.columns with pytest.raises(ValueError): conf = lc.Confounds(strategy=["motion"], motion="full", n_motion=50) conf.load(file_confounds)
def test_n_compcor(): conf = lc.Confounds(strategy=["compcor"], compcor="anat", n_compcor=2) conf.load(file_confounds) assert "a_comp_cor_00" in conf.confounds_.columns assert "a_comp_cor_01" in conf.confounds_.columns assert "a_comp_cor_02" not in conf.confounds_.columns
def test_confounds2df(): """Check auto-detect of confonds from an fMRI nii image.""" conf = lc.Confounds() file_confounds_nii = os.path.join( path_data, "test_space-MNI152NLin2009cAsym_desc-preproc_bold.nii.gz") conf.load(file_confounds_nii) assert "trans_x" in conf.confounds_.columns
def test_nilearn_regress(): """Try regressing out all motion types in nilearn.""" # Regress full motion confounds, _ = lc.Confounds(strategy=["motion"], motion="full").load(file_confounds) sample_mask = None # not testing sample mask here _regression(confounds, sample_mask) # Regress high_pass confounds, _ = lc.Confounds(strategy=["high_pass"]).load(file_confounds) _regression(confounds, sample_mask) # Regress wm_csf confounds, _ = lc.Confounds(strategy=["wm_csf"], wm_csf="full").load(file_confounds) _regression(confounds, sample_mask) # Regress global confounds, _ = lc.Confounds(strategy=["global"], global_signal="full").load(file_confounds) _regression(confounds, sample_mask) # Regress AnatCompCor confounds, _ = lc.Confounds(strategy=["compcor"], compcor="anat").load(file_confounds) _regression(confounds, sample_mask) # Regress TempCompCor confounds, _ = lc.Confounds(strategy=["compcor"], compcor="temp").load(file_confounds) _regression(confounds, sample_mask) # Regress ICA-AROMA confounds, _ = lc.Confounds(strategy=["ica_aroma"], ica_aroma="basic").load(file_confounds) _regression(confounds, sample_mask)
def test_read_file(): """Check that loading missing or incomplete files produce error messages.""" conf = lc.Confounds() with pytest.raises(FileNotFoundError): conf.load(" ") with pytest.raises(ValueError): df = pd.read_csv(file_confounds, sep="\t") df = df.drop("trans_x", axis=1) conf.load(df)
def test_nilearn_regress(): """Try regressing out all motion types in nilearn.""" # Regress full motion confounds = lc.Confounds(strategy=["motion"], motion="full").load(file_confounds) _regression(confounds) # Regress high_pass confounds = lc.Confounds(strategy=["high_pass"]).load(file_confounds) _regression(confounds) # Regress wm_csf confounds = lc.Confounds(strategy=["wm_csf"], wm_csf="full").load(file_confounds) _regression(confounds) # Regress global confounds = lc.Confounds(strategy=["global"], global_signal="full").load(file_confounds) _regression(confounds) # Regress AnatCompCor confounds = lc.Confounds(strategy=["compcor"], compcor="anat").load(file_confounds) _regression(confounds) # Regress TempCompCor confounds = lc.Confounds(strategy=["compcor"], compcor="temp").load(file_confounds) _regression(confounds)
def test_ica_aroma(): """Test ICA AROMA related file input.""" aroma_nii = os.path.join( path_data, "test_space-MNI152NLin2009cAsym_desc-smoothAROMAnonaggr_bold.nii.gz") # Agressive strategy conf = lc.Confounds(strategy=["ica_aroma"], ica_aroma="basic") conf.load(file_confounds) for col_name in conf.confounds_.columns: # only aroma and non-steady state columns will be present assert re.match("(?:aroma_motion_+|non_steady_state+)", col_name) # Non-agressive strategy conf = lc.Confounds(strategy=["ica_aroma"], ica_aroma="full") conf.load(aroma_nii) assert conf.confounds_.size == 0 # invalid combination of strategy and option with pytest.raises(ValueError) as exc_info: conf = lc.Confounds(strategy=["ica_aroma"], ica_aroma=None) conf.load(file_confounds) assert "ICA-AROMA strategy" in exc_info.value.args[0]
def test_not_found_exception(): conf = lc.Confounds(strategy=["high_pass", "motion", "global"], global_signal="full", motion="full") missing_params = ["trans_y", "trans_x_derivative1", "rot_z_power2"] missing_keywords = ["cosine"] file_missing_confounds = os.path.join( path_data, "missing_space-MNI152NLin2009cAsym_desc-preproc_bold.nii.gz") with pytest.raises(ValueError) as exc_info: conf.load(file_missing_confounds) assert f"{missing_params}" in exc_info.value.args[0] assert f"{missing_keywords}" in exc_info.value.args[0] # loading anat compcor should also raise an error, because the json file is # missing for that example dataset with pytest.raises(ValueError): conf = lc.Confounds(strategy=["compcor"], compcor="anat") conf.load(file_missing_confounds) # catch invalid compcor option with pytest.raises(KeyError): conf = lc.Confounds(strategy=["compcor"], compcor="blah") conf.load(file_confounds) # catch invalid compcor option with pytest.raises(ValueError): conf = lc.Confounds(strategy=["compcor"], compcor="full", acompcor_combined=None) conf.load(file_confounds) # Aggressive ICA-AROMA strategy requires # default nifti and noise ICs in confound file # correct nifti but missing noise regressor with pytest.raises(ValueError) as exc_info: conf = lc.Confounds(strategy=["ica_aroma"], ica_aroma="basic") conf.load(file_missing_confounds) assert "aroma" in exc_info.value.args[0] # Aggressive ICA-AROMA strategy requires # default nifti aroma_nii = os.path.join( path_data, "test_space-MNI152NLin2009cAsym_desc-smoothAROMAnonaggr_bold.nii.gz") with pytest.raises(ValueError) as exc_info: conf.load(aroma_nii) assert "Invalid file type" in exc_info.value.args[0] # non aggressive ICA-AROMA strategy requires # desc-smoothAROMAnonaggr nifti file with pytest.raises(ValueError) as exc_info: conf = lc.Confounds(strategy=["ica_aroma"], ica_aroma="full") conf.load(file_missing_confounds) assert "desc-smoothAROMAnonaggr_bold" in exc_info.value.args[0]
def _simu_img(demean=True): """Simulate an nifti image based on confound file with some parts confounds and some parts noise.""" # set the size of the image matrix nx = 5 ny = 5 # the actual number of slices will actually be double of that # as we will stack slices with confounds on top of slices with noise nz = 2 # Load a simple 6 parameters motion models as confounds confounds, _ = lc.Confounds(strategy=["motion"], motion="basic", demean=demean).load(file_confounds) X = confounds.values # the first row is non-steady state, replace it with the imput from the second row non_steady = X[0, :] X[0, :] = X[1, :] # repeat X in length (axis = 0) three times to increase the degree of freedom X = np.tile(X, (3, 1)) # put non-steady state volume back at the first sample X[0, :] = non_steady # the number of time points is based on the example confound file nt = X.shape[0] # initialize an empty 4D volume vol = np.zeros([nx, ny, 2 * nz, nt]) vol_conf = np.zeros([nx, ny, 2 * nz]) vol_rand = np.zeros([nx, ny, 2 * nz]) # create a random mixture of confounds # standardized to zero mean and unit variance beta = np.random.rand(nx * ny * nz, X.shape[1]) tseries_conf = scale(np.matmul(beta, X.transpose()), axis=1) # fill the first half of the 4D data with the mixture vol[:, :, 0:nz, :] = tseries_conf.reshape(nx, ny, nz, nt) vol_conf[:, :, 0:nz] = 1 # create random noise in the second half of the 4D data tseries_rand = scale(np.random.randn(nx * ny * nz, nt), axis=1) vol[:, :, range(nz, 2 * nz), :] = tseries_rand.reshape(nx, ny, nz, nt) vol_rand[:, :, range(nz, 2 * nz)] = 1 # Shift the mean to non-zero vol = vol + 100 # create an nifti image with the data, and corresponding mask img = Nifti1Image(vol, np.eye(4)) mask_conf = Nifti1Image(vol_conf, np.eye(4)) mask_rand = Nifti1Image(vol_rand, np.eye(4)) return img, mask_conf, mask_rand, X
def test_motion(): conf_basic = lc.Confounds(strategy=["motion"], motion="basic") conf_basic.load(file_confounds) conf_derivatives = lc.Confounds(strategy=["motion"], motion="derivatives") conf_derivatives.load(file_confounds) conf_power2 = lc.Confounds(strategy=["motion"], motion="power2") conf_power2.load(file_confounds) conf_full = lc.Confounds(strategy=["motion"], motion="full") conf_full.load(file_confounds) params = ["trans_x", "trans_y", "trans_z", "rot_x", "rot_y", "rot_z"] for param in params: # Basic 6 params motion model assert f"{param}" in conf_basic.confounds_.columns assert f"{param}_derivative1" not in conf_basic.confounds_.columns assert f"{param}_power2" not in conf_basic.confounds_.columns assert f"{param}_derivative1_power2" not in conf_basic.confounds_.columns # Use a 6 params + derivatives motion model assert f"{param}" in conf_derivatives.confounds_.columns assert f"{param}_derivative1" in conf_derivatives.confounds_.columns assert f"{param}_power2" not in conf_derivatives.confounds_.columns assert f"{param}_derivative1_power2" not in conf_derivatives.confounds_.columns # Use a 6 params + power2 motion model assert f"{param}" in conf_power2.confounds_.columns assert f"{param}_derivative1" not in conf_power2.confounds_.columns assert f"{param}_power2" in conf_power2.confounds_.columns assert f"{param}_derivative1_power2" not in conf_power2.confounds_.columns # Use a 6 params + derivatives + power2 + power2d derivatives motion model assert f"{param}" in conf_full.confounds_.columns assert f"{param}_derivative1" in conf_full.confounds_.columns assert f"{param}_power2" in conf_full.confounds_.columns assert f"{param}_derivative1_power2" in conf_full.confounds_.columns
def test_invalid_filetype(): """Invalid file types/associated files for load method.""" # invalid fmriprep version: contain confound files before and after v20.2.0 conf = lc.Confounds() invalid_ver = os.path.join( path_data, "invalid_space-MNI152NLin2009cAsym_desc-preproc_bold.nii.gz") with pytest.raises(ValueError): conf.load(invalid_ver) # nifti with no associated confound file no_confound = os.path.join( path_data, "noconfound_space-MNI152NLin2009cAsym_desc-preproc_bold.nii.gz") with pytest.raises(ValueError): conf.load(no_confound)
def test_load_non_nifti(): """Test non-nifti and invalid file type as input.""" conf = lc.Confounds() # tsv file - unsupported input tsv = os.path.join(path_data, "test_desc-confounds_regressors.tsv") with pytest.raises(ValueError): conf.load(tsv) # cifti file should be supported cifti = os.path.join(path_data, "test_space-fsLR_den-91k_bold.dtseries.nii") conf.load(cifti) assert conf.confounds_.size != 0 # gifti support gifti = [ os.path.join(path_data, f"test_space-fsaverage5_hemi-{hemi}_bold.func.gii") for hemi in ["L", "R"] ] conf.load(gifti) assert conf.confounds_.size != 0
def test_ica_aroma(): conf = lc.Confounds(strategy=["ica_aroma"]) conf.load(file_confounds) for col_name in conf.columns_: assert re.match("aroma_motion_+", col_name)