Repository: nilearn/nilearn

Issue:
[ENH] Throw error when extra kwargs are given to `glm.first_level.first_level_from_bids`
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Describe your proposed enhancement in detail.

`nilearn.glm.first_level.first_level_from_bids` takes kwargs, and passes kwargs that have the `confounds_` prefix to another function. However, the remaining kwargs are not checked and nothing happens if for example the confound kwargs are not given correctly (e.g. with typo `confound_` instead of `confounds_`).

I think it would be nice/useful to throw a runtime error or something when that happens (first mentioned in PR #4352). Would be happy to work on this too.

### Benefits to the change

Better debugging when invalid/unknown kwargs are given.

### Pseudocode for the new behavior, if applicable

_No response_


Discussion:
@michellewang 
I assigned the issue to you so that "no one else" (😉) gets started on it.

Resolving patch:
diff --git a/nilearn/glm/first_level/first_level.py b/nilearn/glm/first_level/first_level.py
--- a/nilearn/glm/first_level/first_level.py
+++ b/nilearn/glm/first_level/first_level.py
@@ -1292,7 +1292,15 @@ def first_level_from_bids(
 
     dataset_path = Path(dataset_path).absolute()
 
-    kwargs_load_confounds = _check_kwargs_load_confounds(**kwargs)
+    kwargs_load_confounds, remaining_kwargs = _check_kwargs_load_confounds(
+        **kwargs
+    )
+
+    if len(remaining_kwargs) > 0:
+        raise RuntimeError(
+            "Unknown keyword arguments. Keyword arguments should start with "
+            f"`confounds_` prefix: {remaining_kwargs}"
+        )
 
     if drift_model is not None and kwargs_load_confounds is not None:
         if "high_pass" in kwargs_load_confounds.get("strategy"):
@@ -1903,18 +1911,18 @@ def _check_kwargs_load_confounds(**kwargs):
     }
 
     if kwargs.get("confounds_strategy") is None:
-        return None
-
-    kwargs_load_confounds = {
-        key: (
-            defaults[key]
-            if f"confounds_{key}" not in kwargs
-            else kwargs[f"confounds_{key}"]
-        )
-        for key in defaults
-    }
+        return None, kwargs
+
+    remaining_kwargs = kwargs.copy()
+    kwargs_load_confounds = {}
+    for key in defaults:
+        confounds_key = f"confounds_{key}"
+        if confounds_key in kwargs:
+            kwargs_load_confounds[key] = remaining_kwargs.pop(confounds_key)
+        else:
+            kwargs_load_confounds[key] = defaults[key]
 
-    return kwargs_load_confounds
+    return kwargs_load_confounds, remaining_kwargs
 
 
 def _make_bids_files_filter(


Test patch:
diff --git a/nilearn/glm/tests/test_first_level.py b/nilearn/glm/tests/test_first_level.py
--- a/nilearn/glm/tests/test_first_level.py
+++ b/nilearn/glm/tests/test_first_level.py
@@ -1958,6 +1958,23 @@ def test_first_level_from_bids_no_subject(tmp_path):
         )
 
 
+def test_first_level_from_bids_unused_kwargs(tmp_path):
+    """Check that unused kwargs are properly handled."""
+    bids_path = create_fake_bids_dataset(
+        base_dir=tmp_path, n_sub=1, n_ses=1, tasks=["main"], n_runs=[2]
+    )
+    with pytest.raises(RuntimeError, match="Unknown keyword arguments"):
+        # wrong kwarg name `confound_strategy` (wrong)
+        # instead of `confounds_strategy` (correct)
+        first_level_from_bids(
+            dataset_path=bids_path,
+            task_label="main",
+            space_label="MNI",
+            slice_time_ref=None,
+            confound_strategy="motion",
+        )
+
+
 def test_check_run_tables_errors():
     # check high level wrapper keeps behavior
     with pytest.raises(ValueError, match="len.* does not match len.*"):