Repository: nilearn/nilearn

Issue:
Problem with flm_objects as input to non_parametric_inference() 
<!--Provide a brief description of the bug.-->
The non_parametric_inference() function uses the _check_second_level_input() function whose "flm_object" argument is set to False. This raises an error when trying to pass first-level model objects as second level input to the function. The function takes no "flm_object" arguments, so there is no way to change this parameter to True, thus allowing first-level model objects. 

<!--Please fill in the following information, to the best of your ability.-->
Nilearn version: 0.10.0

### Expected behavior
If the "flm_object" argument is necessary in the _check_second_level_input() function, the non_parametetric_inference() function should take an argument that allows the user to change this parameter from False to True. 

### Actual behavior
It is not possible to change the "flm_object" argument from False to True, and thus not possible to pass first-level model objects to the second_level_input argument. 

### Steps and code to reproduce bug

1) Import modules 

```python
from nilearn.datasets import fetch_language_localizer_demo_dataset
from nilearn.glm.first_level import first_level_from_bids
from nilearn.glm.second_level import non_parametric_inference
```

2) Extract first-level model objects from BIDS data 

```python
data_dir, _ = fetch_language_localizer_demo_dataset()
task_label = 'languagelocalizer'
models, models_run_imgs, models_events, models_confounds = \
    first_level_from_bids(
        data_dir, task_label,
        img_filters=[('desc', 'preproc')])
```

3) Fit first-level models 

```python
model_and_args = zip(models, models_run_imgs, models_events, models_confounds)
for m_idx, (model, imgs, events, confounds) in enumerate(model_and_args):
     model.fit(imgs, events, confounds)
     model.compute_contrast('language-string')
```

4) Run non-parametric permutation using first-level models as second-level input 

```python
design_matrix = pd.DataFrame(
    [1] * len(second_level_input),
    columns=['intercept'],
)

n_perm=1000

out_dict = non_parametric_inference(
    second_level_input=models,
    design_matrix=design_matrix,
    first_level_contrast='language-string',
    model_intercept=True,
    n_perm=n_perm,  
    two_sided_test=False,
    smoothing_fwhm=8.0,
    n_jobs=1,
)
```



Discussion:
I agree that this is a bug. PR welcome !

Resolving patch:
diff --git a/nilearn/glm/second_level/second_level.py b/nilearn/glm/second_level/second_level.py
--- a/nilearn/glm/second_level/second_level.py
+++ b/nilearn/glm/second_level/second_level.py
@@ -30,79 +30,116 @@
 from nilearn.glm._base import BaseGLM
 
 
-def _check_second_level_input(second_level_input, design_matrix,
-                              confounds=None, flm_object=True, df_object=True):
+def _check_second_level_input(second_level_input,
+                              design_matrix,
+                              confounds=None):
     """Checking second_level_input type"""
-    # Check parameters
-    # check first level input
+    input_type = _check_input_type(second_level_input)
+    _check_input_as_type(
+        second_level_input,
+        input_type,
+        confounds is None,
+        design_matrix is None
+    )
+
+
+def _check_input_type(second_level_input):
+    """Determines the type of input provided."""
+    if isinstance(second_level_input, pd.DataFrame):
+        return "df_object"
+    if isinstance(second_level_input, (str, Nifti1Image)):
+        return "nii_object"
     if isinstance(second_level_input, list):
-        if len(second_level_input) < 2:
-            raise ValueError('A second level model requires a list with at'
-                             ' least two first level models or niimgs')
-        # Check FirstLevelModel objects case
-        if isinstance(second_level_input[0], FirstLevelModel):
-            if not flm_object:
-                raise ValueError("Contradictory arguments: flm_object is set "
-                                 "to False yet second_level_input is a list "
-                                 "of FirstLevelModel objects")
-            models_input = enumerate(second_level_input)
-            for model_idx, first_level in models_input:
-                if (first_level.labels_ is None
-                        or first_level.results_ is None):
-                    raise ValueError(
-                        'Model %s at index %i has not been fit yet'
-                        '' % (first_level.subject_label, model_idx))
-                if not isinstance(first_level, FirstLevelModel):
-                    raise ValueError(' object at idx %d is %s instead of'
-                                     ' FirstLevelModel object' %
-                                     (model_idx, type(first_level)))
-                if confounds is not None:
-                    if first_level.subject_label is None:
-                        raise ValueError(
-                            'In case confounds are provided, first level '
-                            'objects need to provide the attribute '
-                            'subject_label to match rows appropriately.'
-                            'Model at idx %d does not provide it. '
-                            'To set it, you can do '
-                            'first_level.subject_label = "01"'
-                            '' % (model_idx))
-        # Check niimgs case
-        elif isinstance(second_level_input[0], (str, Nifti1Image)):
-            if design_matrix is None:
-                raise ValueError('List of niimgs as second_level_input'
-                                 ' require a design matrix to be provided')
-            for model_idx, niimg in enumerate(second_level_input):
-                if not isinstance(niimg, (str, Nifti1Image)):
-                    raise ValueError(' object at idx %d is %s instead of'
-                                     ' Niimg-like object' %
-                                     (model_idx, type(niimg)))
-    # Check pandas dataframe case
-    elif df_object and isinstance(second_level_input, pd.DataFrame):
-        for col in ['subject_label', 'map_name', 'effects_map_path']:
-            if col not in second_level_input.columns:
-                raise ValueError('second_level_input DataFrame must have'
-                                 ' columns subject_label, map_name and'
-                                 ' effects_map_path')
-        # Make sure subject_label contain strings
-        if not all([isinstance(_, str) for _ in
-                    second_level_input['subject_label'].tolist()]):
-            raise ValueError('subject_label column must contain only strings')
-    elif isinstance(second_level_input, (str, Nifti1Image)):
-        if design_matrix is None:
-            raise ValueError('List of niimgs as second_level_input'
-                             ' require a design matrix to be provided')
-        second_level_input = check_niimg(niimg=second_level_input,
-                                         ensure_ndim=4)
+        return _check_input_type_when_list(second_level_input)
+    raise TypeError("second_level_input must be "
+                    "either a pandas DataFrame, "
+                    "a Niimg-like object, "
+                    "a list of Niimg-like object or "
+                    "a list of FirstLevelModel objects. "
+                    f"Got {_return_type(second_level_input)} instead")
+
+
+def _return_type(second_level_input):
+    if isinstance(second_level_input, list):
+        return [type(x) for x in second_level_input]
     else:
-        if flm_object and df_object:
-            raise ValueError('second_level_input must be a list of'
-                             ' `FirstLevelModel` objects, a pandas DataFrame'
-                             ' or a list Niimg-like objects. Instead %s '
-                             'was provided' % type(second_level_input))
-        else:
-            raise ValueError('second_level_input must be'
-                             ' a list Niimg-like objects. Instead %s '
-                             'was provided' % type(second_level_input))
+        return type(second_level_input)
+
+
+def _check_input_type_when_list(second_level_input):
+    """Determines the type of input provided when it is a list."""
+    if len(second_level_input) < 2:
+        raise TypeError('A second level model requires a list with at'
+                            ' least two first level models or niimgs')
+    _check_all_elements_of_same_type(second_level_input)   
+    if all(isinstance(x, (str, Nifti1Image)) for x in second_level_input):
+        return "nii_object"
+    if all(isinstance(x, FirstLevelModel) for x in second_level_input):
+        return "flm_object"
+    raise TypeError("second_level_input must be "
+                    "either a pandas DataFrame, "
+                    "a Niimg-like object, "
+                    "a list of Niimg-like object or "
+                    "a list of FirstLevelModel objects. "
+                    f"Got {_return_type(second_level_input)} instead")
+
+
+def _check_all_elements_of_same_type(data):
+    for idx, input in enumerate(data):
+        if not isinstance(input, type(data[0])):  
+             raise TypeError(
+                 f'Elements of second_level_input must be of the same type.'
+                 f' Got object type {type(input)} at idx {idx}.')
+
+
+def _check_input_as_type(second_level_input,
+                         input_type,
+                         none_confounds,
+                         none_design_matrix):
+    if input_type == "flm_object":
+        _check_input_as_first_level_model(second_level_input, none_confounds)
+    elif input_type == "nii_object":
+         _check_input_as_nifti_images(second_level_input, none_design_matrix)
+    else:
+         _check_input_as_dataframe(second_level_input)
+
+
+def _check_input_as_first_level_model(second_level_input,
+                                      none_confounds):
+    for model_idx, first_level in enumerate(second_level_input):
+        if (first_level.labels_ is None or first_level.results_ is None):
+             raise ValueError(
+                 'Model %s at index %i has not been fit yet'
+                 '' % (first_level.subject_label, model_idx))
+        if not none_confounds and first_level.subject_label is None:
+                raise ValueError(
+                    'In case confounds are provided, first level '
+                    'objects need to provide the attribute '
+                    'subject_label to match rows appropriately.'
+                    f'Model at idx {model_idx} does not provide it. '
+                    'To set it, you can do '
+                    'first_level.subject_label = "01"')
+
+
+def _check_input_as_dataframe(second_level_input):
+    for col in ('subject_label', 'map_name', 'effects_map_path'):
+        if col not in second_level_input.columns:
+            raise ValueError('second_level_input DataFrame must have'
+                             ' columns subject_label, map_name and'
+                             ' effects_map_path')
+    if not all(isinstance(_, str) 
+               for _ in second_level_input['subject_label'].tolist()):
+        raise ValueError('subject_label column must contain only strings')
+
+
+def _check_input_as_nifti_images(second_level_input, none_design_matrix):
+    if isinstance(second_level_input, (str, Nifti1Image)):
+        second_level_input = [second_level_input]
+    for niimg in second_level_input:
+        check_niimg(niimg=niimg, ensure_ndim=4)
+    if none_design_matrix:
+        raise ValueError('List of niimgs as second_level_input'
+                         ' require a design matrix to be provided')
 
 
 def _check_confounds(confounds):
@@ -605,9 +642,9 @@ def non_parametric_inference(
     n_jobs=1,
     verbose=0,
     threshold=None,
-    tfce=False,
+    tfce=False
 ):
-    """Generate p-values corresponding to the contrasts provided
+    """Generate p-values corresponding to the contrasts provided \
     based on permutation testing.
 
     This function is a light wrapper around
@@ -616,19 +653,7 @@ def non_parametric_inference(
 
     Parameters
     ----------
-    second_level_input : :obj:`pandas.DataFrame` or :obj:`list` of Niimg-like \
-            objects
-        If a pandas DataFrame, then they have to contain subject_label,
-        map_name and effects_map_path. It can contain multiple maps that
-        would be selected during contrast estimation with the argument
-        first_level_contrast of the compute_contrast function. The
-        DataFrame will be sorted based on the subject_label column to avoid
-        order inconsistencies when extracting the maps. So the rows of the
-        automatically computed design matrix, if not provided, will
-        correspond to the sorted subject_label column.
-
-        If list of Niimg-like objects then this is taken literally as Y
-        for the model fit and design_matrix must be provided.
+    %(second_level_input)s
 
     confounds : :obj:`pandas.DataFrame` or None, optional
         Must contain a subject_label column. All other columns are
@@ -646,12 +671,7 @@ def non_parametric_inference(
         Ensure that the order of maps given by a ``second_level_input``
         list of Niimgs matches the order of the rows in the design matrix.
 
-    second_level_contrast : :obj:`str` or array of shape (n_col), optional
-        Where ``n_col`` is the number of columns of the design matrix.
-        The default (None) is accepted if the design matrix has a single
-        column, in which case the only possible contrast array((1)) is
-        applied; when the design matrix has multiple columns, an error is
-        raised.
+    %(second_level_contrast)s
 
     first_level_contrast : :obj:`str`, optional
         In case a pandas DataFrame was provided as second_level_input this
@@ -667,7 +687,9 @@ def non_parametric_inference(
         automatically by a :class:`~nilearn.maskers.MultiNiftiMasker` with
         default parameters. Automatic mask computation assumes first level
         imgs have already been masked.
+
     %(smoothing_fwhm)s
+
     model_intercept : :obj:`bool`, optional
         If ``True``, a constant column is added to the confounding variates
         unless the tested variate is already the intercept.
@@ -687,10 +709,13 @@ def non_parametric_inference(
           The null hypothesis is that the effect is zero or negative.
 
         Default=False.
+
     %(random_state)s
         Use this parameter to have the same permutations in each
         computing units.
+
     %(n_jobs)s
+
     %(verbose0)s
 
     threshold : None or :obj:`float`, optional
@@ -800,8 +825,7 @@ def non_parametric_inference(
     ----------
     .. footbibliography::
     """
-    _check_second_level_input(second_level_input, design_matrix,
-                              flm_object=False, df_object=True)
+    _check_second_level_input(second_level_input, design_matrix)
     _check_confounds(confounds)
     _check_design_matrix(design_matrix)
 


Test patch:
diff --git a/nilearn/glm/tests/test_second_level.py b/nilearn/glm/tests/test_second_level.py
--- a/nilearn/glm/tests/test_second_level.py
+++ b/nilearn/glm/tests/test_second_level.py
@@ -3,10 +3,12 @@
 """
 import os
 
+import pytest
+
 import numpy as np
 import pandas as pd
 
-import pytest
+from scipy import stats
 
 from nibabel import (load,
                      Nifti1Image,
@@ -24,12 +26,20 @@
 from nilearn.glm.first_level import (FirstLevelModel, run_glm)
 from nilearn.glm.second_level import (SecondLevelModel,
                                       non_parametric_inference)
-from scipy import stats
+
+try:
+    from nilearn.reporting import get_clusters_table
+except ImportError:
+    have_mpl = False
+else:
+    have_mpl = True
 
 # This directory path
 BASEDIR = os.path.dirname(os.path.abspath(__file__))
 FUNCFILE = os.path.join(BASEDIR, 'functional.nii.gz')
 
+N_PERM = 10
+
 
 @pytest.fixture
 def input_df():
@@ -38,6 +48,36 @@ def input_df():
                          'subject_label': ["foo", "bar", "baz"]})
 
 
+def test_non_parametric_inference_with_flm_objects():
+    """see https://github.com/nilearn/nilearn/issues/3579"""
+
+    with InTemporaryDirectory():        
+        shapes, rk = [(7, 8, 9, 15)], 3
+        mask, fmri_data, design_matrices = generate_fake_fmri_data_and_design(
+            shapes, rk
+        )
+
+        masker = NiftiMasker(mask)
+        masker.fit()
+        single_session_model = FirstLevelModel(mask_img=masker).fit(
+            fmri_data[0], design_matrices=design_matrices[0]
+        )
+        single_session_model.compute_contrast("x")
+
+        second_level_input = [single_session_model, single_session_model]
+
+        design_matrix = pd.DataFrame(
+            [1] * len(second_level_input),
+            columns=["intercept"],
+        )
+
+        non_parametric_inference(
+            second_level_input=second_level_input,
+            design_matrix=design_matrix,
+            first_level_contrast="x",
+            n_perm=N_PERM)      
+
+
 def test_process_second_level_input_as_dataframe(input_df):
     """Unit tests for function _process_second_level_input_as_dataframe()."""
     from nilearn.glm.second_level.second_level import _process_second_level_input_as_dataframe  # noqa
@@ -81,15 +121,16 @@ def test_process_second_level_input_as_firstlevelmodels():
 
 def test_check_second_level_input():
     from nilearn.glm.second_level.second_level import _check_second_level_input
-    with pytest.raises(ValueError,
+    with pytest.raises(TypeError,
                        match="A second level model requires a list with at "
                              "least two first level models or niimgs"):
         _check_second_level_input([FirstLevelModel()], pd.DataFrame())
     with pytest.raises(ValueError,
-                       match="Model sub_1 at index 0 has not been fit yet"):
-        _check_second_level_input([FirstLevelModel(
-            subject_label="sub_{}".format(i))
-            for i in range(1, 3)], pd.DataFrame())
+                           match="Model sub_1 at index 0 has not been fit yet"):
+        _check_second_level_input(
+            [FirstLevelModel(subject_label=f"sub_{i}") for i in range(1, 3)],
+            pd.DataFrame(),
+        )
     with InTemporaryDirectory():
         shapes, rk = [(7, 8, 9, 15)], 3
         mask, fmri_data, design_matrices = \
@@ -99,9 +140,8 @@ def test_check_second_level_input():
         obj = lambda: None
         obj.results_ = "foo"
         obj.labels_ = "bar"
-        with pytest.raises(ValueError,
-                           match=" object at idx 1 is <class 'function'> "
-                                 "instead of FirstLevelModel object"):
+        with pytest.raises(TypeError,
+                           match="Got object type <class 'function'> at idx 1"):
             _check_second_level_input(input_models + [obj], pd.DataFrame())
         with pytest.raises(ValueError,
                            match="In case confounds are provided, first level "
@@ -114,8 +154,8 @@ def test_check_second_level_input():
                                  "require a design matrix to be provided"):
             _check_second_level_input(fmri_data * 2, None)
         _check_second_level_input(fmri_data[0], pd.DataFrame())
-    with pytest.raises(ValueError,
-                       match=" object at idx 1 is <class 'int'> instead"):
+    with pytest.raises(TypeError,
+                       match="Got object type <class 'int'> at idx 1"):
         _check_second_level_input(["foo", 1], pd.DataFrame())
     with pytest.raises(ValueError,
                        match="second_level_input DataFrame must have columns "
@@ -132,19 +172,13 @@ def test_check_second_level_input():
     with pytest.raises(ValueError,
                        match="List of niimgs as second_level_input "
                              "require a design matrix to be provided"):
-        _check_second_level_input("foo", None)
-    with pytest.raises(ValueError,
-                       match="second_level_input must be a list of"):
+        _check_second_level_input(fmri_data[0], None)
+    with pytest.raises(TypeError,
+                       match="second_level_input must be"):
+        _check_second_level_input(1, None)
+    with pytest.raises(TypeError,
+                       match="second_level_input must be"):
         _check_second_level_input(1, None)
-    with pytest.raises(ValueError,
-                       match="second_level_input must be a list"):
-        _check_second_level_input(1, None, flm_object=False)
-    with pytest.raises(ValueError,
-                       match="Contradictory arguments: flm_object is set to "
-                             "False yet second_level_input is a list of "
-                             "FirstLevelModel objects"):
-        _check_second_level_input([FirstLevelModel()] * 3, None,
-                                  flm_object=False, df_object=False)
 
 
 def test_check_output_type():
@@ -292,7 +326,6 @@ def test_high_level_glm_with_paths():
 
 def test_high_level_non_parametric_inference_with_paths():
     with InTemporaryDirectory():
-        n_perm = 100
         shapes = ((7, 8, 9, 1),)
         mask, FUNCFILE, _ = write_fake_fmri_data_and_design(shapes)
         FUNCFILE = FUNCFILE[0]
@@ -309,7 +342,7 @@ def test_high_level_non_parametric_inference_with_paths():
             non_parametric_inference(
                 second_level_input, design_matrix=X, second_level_contrast=c1,
                 first_level_contrast=FUNCFILE, mask=mask,
-                n_perm=n_perm, verbose=1
+                n_perm=N_PERM, verbose=1
             ) for second_level_input in [Y, df_input]
         ]
         assert all(
@@ -319,7 +352,7 @@ def test_high_level_non_parametric_inference_with_paths():
             assert_array_equal(img.affine, load(mask).affine)
         neg_log_pvals_list = [get_data(i) for i in neg_log_pvals_imgs]
         for neg_log_pvals in neg_log_pvals_list:
-            assert np.all(neg_log_pvals <= - np.log10(1.0 / (n_perm + 1)))
+            assert np.all(neg_log_pvals <= - np.log10(1.0 / (N_PERM + 1)))
             assert np.all(0 <= neg_log_pvals)
 
         masker = NiftiMasker(mask, smoothing_fwhm=2.0)
@@ -329,7 +362,7 @@ def test_high_level_non_parametric_inference_with_paths():
             non_parametric_inference(Y, design_matrix=X,
                                      second_level_contrast=c1,
                                      smoothing_fwhm=3.0,
-                                     mask=masker, n_perm=n_perm)
+                                     mask=masker, n_perm=N_PERM)
         # Delete objects attached to files to avoid WindowsError when deleting
         # temporary directory
         del X, Y, FUNCFILE, func_img, neg_log_pvals_imgs
@@ -389,17 +422,17 @@ def test_fmri_inputs():
 
         # test wrong input errors
         # test first level model requirements
-        with pytest.raises(ValueError):
+        with pytest.raises(TypeError, match="second_level_input must be"):
             SecondLevelModel().fit(flm)
-        with pytest.raises(ValueError):
+        with pytest.raises(TypeError, match="at least two"):
             SecondLevelModel().fit([flm])
         # test dataframe requirements
-        with pytest.raises(ValueError):
+        with pytest.raises(TypeError, match="second_level_input must be"):
             SecondLevelModel().fit(niidf['subject_label'])
         # test niimgs requirements
-        with pytest.raises(ValueError):
+        with pytest.raises(ValueError, match="require a design matrix"):
             SecondLevelModel().fit(niimgs)
-        with pytest.raises(ValueError):
+        with pytest.raises(TypeError):
             SecondLevelModel().fit(niimgs + [[]],
                                    confounds)
         # test first_level_conditions, confounds, and design
@@ -421,27 +454,23 @@ def test_fmri_inputs_for_non_parametric_inference():
         p, q = 80, 10
         X = rng.standard_normal(size=(p, q))
         shapes = ((7, 8, 9, 10),)
-        mask, FUNCFILE, _ = write_fake_fmri_data_and_design(shapes)
-        FUNCFILE = FUNCFILE[0]
-        func_img = load(FUNCFILE)
+        _, func_file, _ = write_fake_fmri_data_and_design(shapes)
+        func_file = func_file[0]
+        func_img = load(func_file)
         T = func_img.shape[-1]
         des = pd.DataFrame(np.ones((T, 1)), columns=['a'])
         des_fname = 'design.csv'
         des.to_csv(des_fname)
 
         # prepare correct input first level models
-        flm = FirstLevelModel(subject_label='01').fit(FUNCFILE,
+        flm = FirstLevelModel(subject_label='01').fit(func_file,
                                                       design_matrices=des)
         # prepare correct input dataframe and lists
         shapes = ((7, 8, 9, 1),)
-        _, FUNCFILE, _ = write_fake_fmri_data_and_design(shapes)
-        FUNCFILE = FUNCFILE[0]
+        _, func_file, _ = write_fake_fmri_data_and_design(shapes)
+        func_file = func_file[0]
 
-        dfcols = ['subject_label', 'map_name', 'effects_map_path']
-        dfrows = [['01', 'a', FUNCFILE], ['02', 'a', FUNCFILE],
-                  ['03', 'a', FUNCFILE]]
-        niidf = pd.DataFrame(dfrows, columns=dfcols)
-        niimgs = [FUNCFILE, FUNCFILE, FUNCFILE]
+        niimgs = [func_file, func_file, func_file]
         niimg_4d = concat_imgs(niimgs)
         confounds = pd.DataFrame([['01', 1], ['02', 2], ['03', 3]],
                                  columns=['subject_label', 'conf1'])
@@ -459,32 +488,20 @@ def test_fmri_inputs_for_non_parametric_inference():
 
         # test wrong input errors
         # test first level model
-        with pytest.raises(ValueError):
+        with pytest.raises(TypeError, match="second_level_input must be"):
             non_parametric_inference(flm)
         # test list of less than two niimgs
-        with pytest.raises(ValueError):
-            non_parametric_inference([FUNCFILE])
+        with pytest.raises(TypeError, match="at least two"):
+            non_parametric_inference([func_file])
         # test niimgs requirements
-        with pytest.raises(ValueError):
+        with pytest.raises(ValueError, match="require a design matrix"):
             non_parametric_inference(niimgs)
-        with pytest.raises(ValueError):
+        with pytest.raises(TypeError):
             non_parametric_inference(niimgs + [[]], confounds)
-        with pytest.raises(ValueError):
-            non_parametric_inference([FUNCFILE])
         # test other objects
         with pytest.raises(ValueError):
             non_parametric_inference('random string object')
-        del X, FUNCFILE, func_img
-
-
-def _first_level_dataframe():
-    names = ['con_01', 'con_01', 'con_01']
-    subjects = ['01', '02', '03']
-    maps = ['', '', '']
-    dataframe = pd.DataFrame({'map_name': names,
-                              'subject_label': subjects,
-                              'effects_map_path': maps})
-    return dataframe
+        del X, func_file, func_img
 
 
 def test_second_level_glm_computation():
@@ -579,7 +596,7 @@ def test_non_parametric_inference_permutation_computation():
 
         neg_log_pvals_img = non_parametric_inference(Y, design_matrix=X,
                                                      model_intercept=False,
-                                                     mask=mask, n_perm=100)
+                                                     mask=mask, n_perm=N_PERM)
 
         assert get_data(neg_log_pvals_img).shape == shapes[0][:3]
         del func_img, FUNCFILE, neg_log_pvals_img, X, Y
@@ -597,7 +614,7 @@ def test_non_parametric_inference_tfce():
             design_matrix=X,
             model_intercept=False,
             mask=mask,
-            n_perm=10,
+            n_perm=N_PERM,
             tfce=True,
         )
         assert isinstance(out, dict)
@@ -628,7 +645,7 @@ def test_non_parametric_inference_cluster_level():
             design_matrix=X,
             model_intercept=False,
             mask=mask,
-            n_perm=10,
+            n_perm=N_PERM,
             threshold=0.001,
         )
         assert isinstance(out, dict)
@@ -643,13 +660,15 @@ def test_non_parametric_inference_cluster_level():
         del func_img, FUNCFILE, out, X, Y
 
 
+@pytest.mark.skipif(
+    not have_mpl, reason="Matplotlib not installed; required for this test"
+)
 def test_non_parametric_inference_cluster_level_with_covariates(
         random_state=0
 ):
     """Test non-parametric inference with cluster-level inference in
     the context of covariates."""
 
-    from nilearn.reporting import get_clusters_table
     rng = np.random.RandomState(random_state)
 
     with InTemporaryDirectory():
@@ -659,7 +678,7 @@ def test_non_parametric_inference_cluster_level_with_covariates(
         func_img = load(FUNCFILE)
 
         unc_pval = 0.01
-        n_subjects = 6
+        n_subjects = 2
 
         # Set up one sample t-test design with two random covariates
         cov1 = rng.random(n_subjects)
@@ -707,7 +726,7 @@ def test_non_parametric_inference_cluster_level_with_covariates(
                     mask=mask,
                     model_intercept=False,
                     second_level_contrast="intercept",
-                    n_perm=1 / unc_pval,
+                    n_perm=N_PERM,
                     threshold=unc_pval,
                 )
 
@@ -788,7 +807,7 @@ def test_non_parametric_inference_contrast_computation():
         FUNCFILE = FUNCFILE[0]
         func_img = load(FUNCFILE)
         # asking for contrast before model fit gives error
-        with pytest.raises(ValueError):
+        with pytest.raises(TypeError):
             non_parametric_inference(None, None, None, 'intercept', mask)
         # fit model
         Y = [func_img] * 4
@@ -796,7 +815,7 @@ def test_non_parametric_inference_contrast_computation():
         # formula should work without second-level contrast
         neg_log_pvals_img = non_parametric_inference(Y, design_matrix=X,
                                                      model_intercept=False,
-                                                     mask=mask, n_perm=100)
+                                                     mask=mask, n_perm=N_PERM)
 
         ncol = len(X.columns)
         c1, cnull = np.eye(ncol)[0, :], np.zeros(ncol)
@@ -804,13 +823,13 @@ def test_non_parametric_inference_contrast_computation():
         neg_log_pvals_img = non_parametric_inference(Y, design_matrix=X,
                                                      model_intercept=False,
                                                      second_level_contrast=c1,
-                                                     mask=mask, n_perm=100)
+                                                     mask=mask, n_perm=N_PERM)
         # formula should work passing variable name directly
         neg_log_pvals_img = \
             non_parametric_inference(Y, design_matrix=X,
                                      second_level_contrast='intercept',
                                      model_intercept=False,
-                                     mask=mask, n_perm=100)
+                                     mask=mask, n_perm=N_PERM)
 
         # passing null contrast should give back a value error
         with pytest.raises(ValueError):