We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Less restrictive of `load_confounds` file mathcing pattern
<!--Provide a brief description what you would like changes and why.-->
The `load_confounds` function does a lazy search based on the image file name to find other required files from a fMRIPrep derivative. Currently it is restricted to outputs that are mapped to a group template. This raise an issue when people wants to use files in subject's native space. In this case the `space-` entity would not be present. 

<!--Please fill in the following information, to the best of your ability.-->
### Benefits to the change
Load data preprocessed in native space with ease.

### Pseudocode for the new behavior, if applicable
It will not raise errors at this specific line here:
https://github.com/nilearn/nilearn/blob/9c29e3376243b643a72f78b8b98a56d12c2e99bb/nilearn/interfaces/fmriprep/load_confounds_utils.py#L92
No changes needed for the user facing side.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `nilearn/interfaces/fmriprep/load_confounds_utils.py`
Content:
```
1 """Helper functions for the manipulation of fmriprep output confounds."""
2 import numpy as np
3 import pandas as pd
4 from sklearn.preprocessing import scale
5 import os
6 import json
7 import re
8 
9 from .load_confounds_scrub import _extract_outlier_regressors
10 from nilearn._utils.fmriprep_confounds import (
11     _flag_single_gifti, _is_camel_case
12 )
13 
14 
15 img_file_patterns = {
16     "aroma": "_desc-smoothAROMAnonaggr_bold",
17     "nii.gz": "_space-.*_desc-preproc_bold.nii.gz",
18     "dtseries.nii": "_space-.*_bold.dtseries.nii",
19     "func.gii": "_space-.*_hemi-[LR]_bold.func.gii",
20 }
21 
22 img_file_error = {
23     "aroma": (
24         "Input must be ~desc-smoothAROMAnonaggr_bold for full ICA-AROMA"
25         " strategy."
26     ),
27     "nii.gz": "Invalid file type for the selected method.",
28     "dtseries.nii": "Invalid file type for the selected method.",
29     "func.gii": "need fMRIprep output with extension func.gii",
30 }
31 
32 
33 def _check_params(confounds_raw, params):
34     """Check that specified parameters can be found in the confounds."""
35     not_found_params = [
36         par for par in params if par not in confounds_raw.columns
37     ]
38     if not_found_params:
39         raise MissingConfound(params=not_found_params)
40     return None
41 
42 
43 def _find_confounds(confounds_raw, keywords):
44     """Find confounds that contain certain keywords."""
45     list_confounds, missing_keys = [], []
46     for key in keywords:
47         key_found = [col for col in confounds_raw.columns if key in col]
48         if key_found:
49             list_confounds.extend(key_found)
50         elif key != "non_steady_state":
51             missing_keys.append(key)
52     if missing_keys:
53         raise MissingConfound(keywords=missing_keys)
54     return list_confounds
55 
56 
57 def _sanitize_confounds(img_files):
58     """Make sure the inputs are in the correct format."""
59     # we want to support loading a single set of confounds, instead of a list
60     # so we hack it
61     if len(img_files) == 1:
62         return img_files, True
63     # gifti has to be passed as pair
64     if isinstance(img_files, list) and len(img_files) == 2:
65         flag_single = _flag_single_gifti(img_files)
66     else:  # single file
67         flag_single = isinstance(img_files, str)
68     if flag_single:
69         img_files = [img_files]
70     return img_files, flag_single
71 
72 
73 def _add_suffix(params, model):
74     """Add derivative suffixes to a list of parameters."""
75     params_full = params.copy()
76     suffix = {
77         "basic": {},
78         "derivatives": {"derivative1"},
79         "power2": {"power2"},
80         "full": {"derivative1", "power2", "derivative1_power2"},
81     }
82     for par in params:
83         for suff in suffix[model]:
84             params_full.append(f"{par}_{suff}")
85     return params_full
86 
87 
88 def _get_file_name(nii_file):
89     """Construct the raw confound file name from processed functional data."""
90     if isinstance(nii_file, list):  # catch gifti
91         nii_file = nii_file[0]
92     suffix = "_space-" + nii_file.split("space-")[1]
93     # fmriprep has changed the file suffix between v20.1.1 and v20.2.0 with
94     # respect to BEP 012.
95     # cf. https://neurostars.org/t/naming-change-confounds-regressors-to-confounds-timeseries/17637 # noqa
96     # Check file with new naming scheme exists or replace,
97     # for backward compatibility.
98     confounds_raw_candidates = [
99         nii_file.replace(suffix, "_desc-confounds_timeseries.tsv"),
100         nii_file.replace(suffix, "_desc-confounds_regressors.tsv"),
101     ]
102 
103     confounds_raw = [
104         cr for cr in confounds_raw_candidates if os.path.exists(cr)
105     ]
106 
107     if not confounds_raw:
108         raise ValueError(
109             ("Could not find associated confound file. "
110              "The functional derivatives should exist under the same parent "
111              "directory."
112              )
113         )
114     elif len(confounds_raw) != 1:
115         raise ValueError("Found more than one confound file.")
116     else:
117         return confounds_raw[0]
118 
119 
120 def _get_confounds_file(image_file, flag_full_aroma):
121     _check_images(image_file, flag_full_aroma)
122     confounds_raw_path = _get_file_name(image_file)
123     return confounds_raw_path
124 
125 
126 def _get_json(confounds_raw_path):
127     """return json data companion file to the confounds tsv file."""
128     # Load JSON file
129     return confounds_raw_path.replace("tsv", "json")
130 
131 
132 def _load_confounds_json(confounds_json, flag_acompcor):
133     """Load json data companion to the confounds tsv file."""
134     try:
135         with open(confounds_json, "rb") as f:
136             confounds_json = json.load(f)
137     except OSError:
138         if flag_acompcor:
139             raise ValueError(
140                 (
141                     f"Could not find associated json file {confounds_json}."
142                     "This is necessary for anatomical CompCor."
143                     "The CompCor component is only supported for fMRIprep "
144                     "version >= 1.4.0."
145                 )
146             )
147     return confounds_json
148 
149 
150 def _load_confounds_file_as_dataframe(confounds_raw_path):
151     """Load raw confounds as a pandas DataFrame."""
152     confounds_raw = pd.read_csv(
153         confounds_raw_path, delimiter="\t", encoding="utf-8"
154     )
155 
156     # check if the version of fMRIprep (>=1.2.0) is supported based on
157     # header format. 1.0.x and 1.1.x series uses camel case
158     if any(_is_camel_case(col_name) for col_name in confounds_raw.columns):
159         raise ValueError(
160             "The confound file contains header in camel case."
161             "This is likely the output from 1.0.x and 1.1.x "
162             "series. We only support fmriprep outputs >= 1.2.0."
163             f"{confounds_raw.columns}"
164         )
165 
166     # even old version with no header will have the first row as header
167     try:
168         too_old = float(confounds_raw.columns[0])
169     except ValueError:
170         too_old = False
171 
172     if too_old:
173         bad_file = pd.read_csv(
174             confounds_raw_path, delimiter="\t", encoding="utf-8", header=None
175         )
176         raise ValueError(
177             "The confound file contains no header."
178             "Is this an old version fMRIprep output?"
179             f"{bad_file.head()}"
180         )
181     return confounds_raw
182 
183 
184 def _ext_validator(image_file, ext):
185     """Check image is valid based on extension."""
186     try:
187         valid_img = all(
188             bool(re.search(img_file_patterns[ext], img)) for img in image_file
189         )
190         error_message = img_file_error[ext]
191     except KeyError:
192         valid_img = False
193         error_message = "Unsupported input."
194     return valid_img, error_message
195 
196 
197 def _check_images(image_file, flag_full_aroma):
198     """Validate input file and ICA AROMA related file."""
199     if len(image_file) == 2:  # must be gifti
200         valid_img, error_message = _ext_validator(image_file, "func.gii")
201     elif flag_full_aroma:
202         valid_img, error_message = _ext_validator([image_file], "aroma")
203     else:
204         ext = ".".join(image_file.split(".")[-2:])
205         valid_img, error_message = _ext_validator([image_file], ext)
206     if not valid_img:
207         raise ValueError(error_message)
208 
209 
210 def _prepare_output(confounds, demean):
211     """Demean and create sample mask for the selected confounds."""
212     sample_mask, confounds, outliers = _extract_outlier_regressors(confounds)
213     if confounds.size != 0:  # ica_aroma = "full" generate empty output
214         # Derivatives have NaN on the first row
215         # Replace them by estimates at second time point,
216         # otherwise nilearn will crash.
217         mask_nan = np.isnan(confounds.values[0, :])
218         confounds.iloc[0, mask_nan] = confounds.iloc[1, mask_nan]
219         if demean:
220             confounds = _demean_confounds(confounds, sample_mask)
221     return sample_mask, confounds
222 
223 
224 def _demean_confounds(confounds, sample_mask):
225     """Demean the confounds. The mean is calculated on non-outlier values."""
226     confound_cols = confounds.columns
227     if sample_mask is None:
228         confounds = scale(confounds, axis=0, with_std=False)
229     else:  # calculate the mean without outliers.
230         confounds_mean = confounds.iloc[sample_mask, :].mean(axis=0)
231         confounds -= confounds_mean
232     return pd.DataFrame(confounds, columns=confound_cols)
233 
234 
235 class MissingConfound(Exception):
236     """
237     Exception raised when failing to find params in the confounds.
238 
239     Parameters
240     ----------
241     params : list of missing params
242         Default values are empty lists.
243     keywords: list of missing keywords
244         Default values are empty lists.
245     """
246 
247     def __init__(self, params=None, keywords=None):
248         """Set missing parameters and keywords."""
249         self.params = params or []
250         self.keywords = keywords or []
251
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/nilearn/interfaces/fmriprep/load_confounds_utils.py b/nilearn/interfaces/fmriprep/load_confounds_utils.py
--- a/nilearn/interfaces/fmriprep/load_confounds_utils.py
+++ b/nilearn/interfaces/fmriprep/load_confounds_utils.py
@@ -10,26 +10,25 @@
 from nilearn._utils.fmriprep_confounds import (
     _flag_single_gifti, _is_camel_case
 )
+from nilearn.interfaces.bids import parse_bids_filename
 
 
 img_file_patterns = {
     "aroma": "_desc-smoothAROMAnonaggr_bold",
-    "nii.gz": "_space-.*_desc-preproc_bold.nii.gz",
-    "dtseries.nii": "_space-.*_bold.dtseries.nii",
-    "func.gii": "_space-.*_hemi-[LR]_bold.func.gii",
+    "nii.gz": "(_space-.*)?_desc-preproc_bold.nii.gz",
+    "dtseries.nii": "(_space-.*)?_bold.dtseries.nii",
+    "func.gii": "(_space-.*)?_hemi-[LR]_bold.func.gii",
 }
 
 img_file_error = {
     "aroma": (
-        "Input must be ~desc-smoothAROMAnonaggr_bold for full ICA-AROMA"
+        "Input must be desc-smoothAROMAnonaggr_bold for full ICA-AROMA"
         " strategy."
-    ),
-    "nii.gz": "Invalid file type for the selected method.",
+    ),    "nii.gz": "Invalid file type for the selected method.",
     "dtseries.nii": "Invalid file type for the selected method.",
     "func.gii": "need fMRIprep output with extension func.gii",
 }
 
-
 def _check_params(confounds_raw, params):
     """Check that specified parameters can be found in the confounds."""
     not_found_params = [
@@ -89,15 +88,28 @@
     """Construct the raw confound file name from processed functional data."""
     if isinstance(nii_file, list):  # catch gifti
         nii_file = nii_file[0]
-    suffix = "_space-" + nii_file.split("space-")[1]
+    entities = parse_bids_filename(nii_file)
+    subject_label = f"sub-{entities['sub']}"
+    if "ses" in entities:
+        subject_label = f"{subject_label}_ses-{entities['ses']}"
+    specifiers = f"task-{entities['task']}"
+    if "run" in entities:
+        specifiers = f"{specifiers}_run-{entities['run']}"
+    img_filename = nii_file.split(os.sep)[-1]
     # fmriprep has changed the file suffix between v20.1.1 and v20.2.0 with
     # respect to BEP 012.
     # cf. https://neurostars.org/t/naming-change-confounds-regressors-to-confounds-timeseries/17637 # noqa
     # Check file with new naming scheme exists or replace,
     # for backward compatibility.
     confounds_raw_candidates = [
-        nii_file.replace(suffix, "_desc-confounds_timeseries.tsv"),
-        nii_file.replace(suffix, "_desc-confounds_regressors.tsv"),
+        nii_file.replace(
+            img_filename,
+            f"{subject_label}_{specifiers}_desc-confounds_timeseries.tsv"
+        ),
+        nii_file.replace(
+            img_filename,
+            f"{subject_label}_{specifiers}_desc-confounds_regressors.tsv"
+        ),
     ]
 
     confounds_raw = [

{"golden_diff": "diff --git a/nilearn/interfaces/fmriprep/load_confounds_utils.py b/nilearn/interfaces/fmriprep/load_confounds_utils.py\n--- a/nilearn/interfaces/fmriprep/load_confounds_utils.py\n+++ b/nilearn/interfaces/fmriprep/load_confounds_utils.py\n@@ -10,26 +10,25 @@\n from nilearn._utils.fmriprep_confounds import (\n     _flag_single_gifti, _is_camel_case\n )\n+from nilearn.interfaces.bids import parse_bids_filename\n \n \n img_file_patterns = {\n     \"aroma\": \"_desc-smoothAROMAnonaggr_bold\",\n-    \"nii.gz\": \"_space-.*_desc-preproc_bold.nii.gz\",\n-    \"dtseries.nii\": \"_space-.*_bold.dtseries.nii\",\n-    \"func.gii\": \"_space-.*_hemi-[LR]_bold.func.gii\",\n+    \"nii.gz\": \"(_space-.*)?_desc-preproc_bold.nii.gz\",\n+    \"dtseries.nii\": \"(_space-.*)?_bold.dtseries.nii\",\n+    \"func.gii\": \"(_space-.*)?_hemi-[LR]_bold.func.gii\",\n }\n \n img_file_error = {\n     \"aroma\": (\n-        \"Input must be ~desc-smoothAROMAnonaggr_bold for full ICA-AROMA\"\n+        \"Input must be desc-smoothAROMAnonaggr_bold for full ICA-AROMA\"\n         \" strategy.\"\n-    ),\n-    \"nii.gz\": \"Invalid file type for the selected method.\",\n+    ),    \"nii.gz\": \"Invalid file type for the selected method.\",\n     \"dtseries.nii\": \"Invalid file type for the selected method.\",\n     \"func.gii\": \"need fMRIprep output with extension func.gii\",\n }\n \n-\n def _check_params(confounds_raw, params):\n     \"\"\"Check that specified parameters can be found in the confounds.\"\"\"\n     not_found_params = [\n@@ -89,15 +88,28 @@\n     \"\"\"Construct the raw confound file name from processed functional data.\"\"\"\n     if isinstance(nii_file, list):  # catch gifti\n         nii_file = nii_file[0]\n-    suffix = \"_space-\" + nii_file.split(\"space-\")[1]\n+    entities = parse_bids_filename(nii_file)\n+    subject_label = f\"sub-{entities['sub']}\"\n+    if \"ses\" in entities:\n+        subject_label = f\"{subject_label}_ses-{entities['ses']}\"\n+    specifiers = f\"task-{entities['task']}\"\n+    if \"run\" in entities:\n+        specifiers = f\"{specifiers}_run-{entities['run']}\"\n+    img_filename = nii_file.split(os.sep)[-1]\n     # fmriprep has changed the file suffix between v20.1.1 and v20.2.0 with\n     # respect to BEP 012.\n     # cf. https://neurostars.org/t/naming-change-confounds-regressors-to-confounds-timeseries/17637 # noqa\n     # Check file with new naming scheme exists or replace,\n     # for backward compatibility.\n     confounds_raw_candidates = [\n-        nii_file.replace(suffix, \"_desc-confounds_timeseries.tsv\"),\n-        nii_file.replace(suffix, \"_desc-confounds_regressors.tsv\"),\n+        nii_file.replace(\n+            img_filename,\n+            f\"{subject_label}_{specifiers}_desc-confounds_timeseries.tsv\"\n+        ),\n+        nii_file.replace(\n+            img_filename,\n+            f\"{subject_label}_{specifiers}_desc-confounds_regressors.tsv\"\n+        ),\n     ]\n \n     confounds_raw = [\n", "issue": "Less restrictive of `load_confounds` file mathcing pattern\n<!--Provide a brief description what you would like changes and why.-->\r\nThe `load_confounds` function does a lazy search based on the image file name to find other required files from a fMRIPrep derivative. Currently it is restricted to outputs that are mapped to a group template. This raise an issue when people wants to use files in subject's native space. In this case the `space-` entity would not be present. \r\n\r\n<!--Please fill in the following information, to the best of your ability.-->\r\n### Benefits to the change\r\nLoad data preprocessed in native space with ease.\r\n\r\n### Pseudocode for the new behavior, if applicable\r\nIt will not raise errors at this specific line here:\r\nhttps://github.com/nilearn/nilearn/blob/9c29e3376243b643a72f78b8b98a56d12c2e99bb/nilearn/interfaces/fmriprep/load_confounds_utils.py#L92\r\nNo changes needed for the user facing side.\n", "before_files": [{"content": "\"\"\"Helper functions for the manipulation of fmriprep output confounds.\"\"\"\nimport numpy as np\nimport pandas as pd\nfrom sklearn.preprocessing import scale\nimport os\nimport json\nimport re\n\nfrom .load_confounds_scrub import _extract_outlier_regressors\nfrom nilearn._utils.fmriprep_confounds import (\n    _flag_single_gifti, _is_camel_case\n)\n\n\nimg_file_patterns = {\n    \"aroma\": \"_desc-smoothAROMAnonaggr_bold\",\n    \"nii.gz\": \"_space-.*_desc-preproc_bold.nii.gz\",\n    \"dtseries.nii\": \"_space-.*_bold.dtseries.nii\",\n    \"func.gii\": \"_space-.*_hemi-[LR]_bold.func.gii\",\n}\n\nimg_file_error = {\n    \"aroma\": (\n        \"Input must be ~desc-smoothAROMAnonaggr_bold for full ICA-AROMA\"\n        \" strategy.\"\n    ),\n    \"nii.gz\": \"Invalid file type for the selected method.\",\n    \"dtseries.nii\": \"Invalid file type for the selected method.\",\n    \"func.gii\": \"need fMRIprep output with extension func.gii\",\n}\n\n\ndef _check_params(confounds_raw, params):\n    \"\"\"Check that specified parameters can be found in the confounds.\"\"\"\n    not_found_params = [\n        par for par in params if par not in confounds_raw.columns\n    ]\n    if not_found_params:\n        raise MissingConfound(params=not_found_params)\n    return None\n\n\ndef _find_confounds(confounds_raw, keywords):\n    \"\"\"Find confounds that contain certain keywords.\"\"\"\n    list_confounds, missing_keys = [], []\n    for key in keywords:\n        key_found = [col for col in confounds_raw.columns if key in col]\n        if key_found:\n            list_confounds.extend(key_found)\n        elif key != \"non_steady_state\":\n            missing_keys.append(key)\n    if missing_keys:\n        raise MissingConfound(keywords=missing_keys)\n    return list_confounds\n\n\ndef _sanitize_confounds(img_files):\n    \"\"\"Make sure the inputs are in the correct format.\"\"\"\n    # we want to support loading a single set of confounds, instead of a list\n    # so we hack it\n    if len(img_files) == 1:\n        return img_files, True\n    # gifti has to be passed as pair\n    if isinstance(img_files, list) and len(img_files) == 2:\n        flag_single = _flag_single_gifti(img_files)\n    else:  # single file\n        flag_single = isinstance(img_files, str)\n    if flag_single:\n        img_files = [img_files]\n    return img_files, flag_single\n\n\ndef _add_suffix(params, model):\n    \"\"\"Add derivative suffixes to a list of parameters.\"\"\"\n    params_full = params.copy()\n    suffix = {\n        \"basic\": {},\n        \"derivatives\": {\"derivative1\"},\n        \"power2\": {\"power2\"},\n        \"full\": {\"derivative1\", \"power2\", \"derivative1_power2\"},\n    }\n    for par in params:\n        for suff in suffix[model]:\n            params_full.append(f\"{par}_{suff}\")\n    return params_full\n\n\ndef _get_file_name(nii_file):\n    \"\"\"Construct the raw confound file name from processed functional data.\"\"\"\n    if isinstance(nii_file, list):  # catch gifti\n        nii_file = nii_file[0]\n    suffix = \"_space-\" + nii_file.split(\"space-\")[1]\n    # fmriprep has changed the file suffix between v20.1.1 and v20.2.0 with\n    # respect to BEP 012.\n    # cf. https://neurostars.org/t/naming-change-confounds-regressors-to-confounds-timeseries/17637 # noqa\n    # Check file with new naming scheme exists or replace,\n    # for backward compatibility.\n    confounds_raw_candidates = [\n        nii_file.replace(suffix, \"_desc-confounds_timeseries.tsv\"),\n        nii_file.replace(suffix, \"_desc-confounds_regressors.tsv\"),\n    ]\n\n    confounds_raw = [\n        cr for cr in confounds_raw_candidates if os.path.exists(cr)\n    ]\n\n    if not confounds_raw:\n        raise ValueError(\n            (\"Could not find associated confound file. \"\n             \"The functional derivatives should exist under the same parent \"\n             \"directory.\"\n             )\n        )\n    elif len(confounds_raw) != 1:\n        raise ValueError(\"Found more than one confound file.\")\n    else:\n        return confounds_raw[0]\n\n\ndef _get_confounds_file(image_file, flag_full_aroma):\n    _check_images(image_file, flag_full_aroma)\n    confounds_raw_path = _get_file_name(image_file)\n    return confounds_raw_path\n\n\ndef _get_json(confounds_raw_path):\n    \"\"\"return json data companion file to the confounds tsv file.\"\"\"\n    # Load JSON file\n    return confounds_raw_path.replace(\"tsv\", \"json\")\n\n\ndef _load_confounds_json(confounds_json, flag_acompcor):\n    \"\"\"Load json data companion to the confounds tsv file.\"\"\"\n    try:\n        with open(confounds_json, \"rb\") as f:\n            confounds_json = json.load(f)\n    except OSError:\n        if flag_acompcor:\n            raise ValueError(\n                (\n                    f\"Could not find associated json file {confounds_json}.\"\n                    \"This is necessary for anatomical CompCor.\"\n                    \"The CompCor component is only supported for fMRIprep \"\n                    \"version >= 1.4.0.\"\n                )\n            )\n    return confounds_json\n\n\ndef _load_confounds_file_as_dataframe(confounds_raw_path):\n    \"\"\"Load raw confounds as a pandas DataFrame.\"\"\"\n    confounds_raw = pd.read_csv(\n        confounds_raw_path, delimiter=\"\\t\", encoding=\"utf-8\"\n    )\n\n    # check if the version of fMRIprep (>=1.2.0) is supported based on\n    # header format. 1.0.x and 1.1.x series uses camel case\n    if any(_is_camel_case(col_name) for col_name in confounds_raw.columns):\n        raise ValueError(\n            \"The confound file contains header in camel case.\"\n            \"This is likely the output from 1.0.x and 1.1.x \"\n            \"series. We only support fmriprep outputs >= 1.2.0.\"\n            f\"{confounds_raw.columns}\"\n        )\n\n    # even old version with no header will have the first row as header\n    try:\n        too_old = float(confounds_raw.columns[0])\n    except ValueError:\n        too_old = False\n\n    if too_old:\n        bad_file = pd.read_csv(\n            confounds_raw_path, delimiter=\"\\t\", encoding=\"utf-8\", header=None\n        )\n        raise ValueError(\n            \"The confound file contains no header.\"\n            \"Is this an old version fMRIprep output?\"\n            f\"{bad_file.head()}\"\n        )\n    return confounds_raw\n\n\ndef _ext_validator(image_file, ext):\n    \"\"\"Check image is valid based on extension.\"\"\"\n    try:\n        valid_img = all(\n            bool(re.search(img_file_patterns[ext], img)) for img in image_file\n        )\n        error_message = img_file_error[ext]\n    except KeyError:\n        valid_img = False\n        error_message = \"Unsupported input.\"\n    return valid_img, error_message\n\n\ndef _check_images(image_file, flag_full_aroma):\n    \"\"\"Validate input file and ICA AROMA related file.\"\"\"\n    if len(image_file) == 2:  # must be gifti\n        valid_img, error_message = _ext_validator(image_file, \"func.gii\")\n    elif flag_full_aroma:\n        valid_img, error_message = _ext_validator([image_file], \"aroma\")\n    else:\n        ext = \".\".join(image_file.split(\".\")[-2:])\n        valid_img, error_message = _ext_validator([image_file], ext)\n    if not valid_img:\n        raise ValueError(error_message)\n\n\ndef _prepare_output(confounds, demean):\n    \"\"\"Demean and create sample mask for the selected confounds.\"\"\"\n    sample_mask, confounds, outliers = _extract_outlier_regressors(confounds)\n    if confounds.size != 0:  # ica_aroma = \"full\" generate empty output\n        # Derivatives have NaN on the first row\n        # Replace them by estimates at second time point,\n        # otherwise nilearn will crash.\n        mask_nan = np.isnan(confounds.values[0, :])\n        confounds.iloc[0, mask_nan] = confounds.iloc[1, mask_nan]\n        if demean:\n            confounds = _demean_confounds(confounds, sample_mask)\n    return sample_mask, confounds\n\n\ndef _demean_confounds(confounds, sample_mask):\n    \"\"\"Demean the confounds. The mean is calculated on non-outlier values.\"\"\"\n    confound_cols = confounds.columns\n    if sample_mask is None:\n        confounds = scale(confounds, axis=0, with_std=False)\n    else:  # calculate the mean without outliers.\n        confounds_mean = confounds.iloc[sample_mask, :].mean(axis=0)\n        confounds -= confounds_mean\n    return pd.DataFrame(confounds, columns=confound_cols)\n\n\nclass MissingConfound(Exception):\n    \"\"\"\n    Exception raised when failing to find params in the confounds.\n\n    Parameters\n    ----------\n    params : list of missing params\n        Default values are empty lists.\n    keywords: list of missing keywords\n        Default values are empty lists.\n    \"\"\"\n\n    def __init__(self, params=None, keywords=None):\n        \"\"\"Set missing parameters and keywords.\"\"\"\n        self.params = params or []\n        self.keywords = keywords or []\n", "path": "nilearn/interfaces/fmriprep/load_confounds_utils.py"}], "after_files": [{"content": "\"\"\"Helper functions for the manipulation of fmriprep output confounds.\"\"\"\nimport numpy as np\nimport pandas as pd\nfrom sklearn.preprocessing import scale\nimport os\nimport json\nimport re\n\nfrom .load_confounds_scrub import _extract_outlier_regressors\nfrom nilearn._utils.fmriprep_confounds import (\n    _flag_single_gifti, _is_camel_case\n)\nfrom nilearn.interfaces.bids import parse_bids_filename\n\n\nimg_file_patterns = {\n    \"aroma\": \"_desc-smoothAROMAnonaggr_bold\",\n    \"nii.gz\": \"(_space-.*)?_desc-preproc_bold.nii.gz\",\n    \"dtseries.nii\": \"(_space-.*)?_bold.dtseries.nii\",\n    \"func.gii\": \"(_space-.*)?_hemi-[LR]_bold.func.gii\",\n}\n\nimg_file_error = {\n    \"aroma\": (\n        \"Input must be desc-smoothAROMAnonaggr_bold for full ICA-AROMA\"\n        \" strategy.\"\n    ),    \"nii.gz\": \"Invalid file type for the selected method.\",\n    \"dtseries.nii\": \"Invalid file type for the selected method.\",\n    \"func.gii\": \"need fMRIprep output with extension func.gii\",\n}\n\ndef _check_params(confounds_raw, params):\n    \"\"\"Check that specified parameters can be found in the confounds.\"\"\"\n    not_found_params = [\n        par for par in params if par not in confounds_raw.columns\n    ]\n    if not_found_params:\n        raise MissingConfound(params=not_found_params)\n    return None\n\n\ndef _find_confounds(confounds_raw, keywords):\n    \"\"\"Find confounds that contain certain keywords.\"\"\"\n    list_confounds, missing_keys = [], []\n    for key in keywords:\n        key_found = [col for col in confounds_raw.columns if key in col]\n        if key_found:\n            list_confounds.extend(key_found)\n        elif key != \"non_steady_state\":\n            missing_keys.append(key)\n    if missing_keys:\n        raise MissingConfound(keywords=missing_keys)\n    return list_confounds\n\n\ndef _sanitize_confounds(img_files):\n    \"\"\"Make sure the inputs are in the correct format.\"\"\"\n    # we want to support loading a single set of confounds, instead of a list\n    # so we hack it\n    if len(img_files) == 1:\n        return img_files, True\n    # gifti has to be passed as pair\n    if isinstance(img_files, list) and len(img_files) == 2:\n        flag_single = _flag_single_gifti(img_files)\n    else:  # single file\n        flag_single = isinstance(img_files, str)\n    if flag_single:\n        img_files = [img_files]\n    return img_files, flag_single\n\n\ndef _add_suffix(params, model):\n    \"\"\"Add derivative suffixes to a list of parameters.\"\"\"\n    params_full = params.copy()\n    suffix = {\n        \"basic\": {},\n        \"derivatives\": {\"derivative1\"},\n        \"power2\": {\"power2\"},\n        \"full\": {\"derivative1\", \"power2\", \"derivative1_power2\"},\n    }\n    for par in params:\n        for suff in suffix[model]:\n            params_full.append(f\"{par}_{suff}\")\n    return params_full\n\n\ndef _get_file_name(nii_file):\n    \"\"\"Construct the raw confound file name from processed functional data.\"\"\"\n    if isinstance(nii_file, list):  # catch gifti\n        nii_file = nii_file[0]\n    entities = parse_bids_filename(nii_file)\n    subject_label = f\"sub-{entities['sub']}\"\n    if \"ses\" in entities:\n        subject_label = f\"{subject_label}_ses-{entities['ses']}\"\n    specifiers = f\"task-{entities['task']}\"\n    if \"run\" in entities:\n        specifiers = f\"{specifiers}_run-{entities['run']}\"\n    img_filename = nii_file.split(os.sep)[-1]\n    # fmriprep has changed the file suffix between v20.1.1 and v20.2.0 with\n    # respect to BEP 012.\n    # cf. https://neurostars.org/t/naming-change-confounds-regressors-to-confounds-timeseries/17637 # noqa\n    # Check file with new naming scheme exists or replace,\n    # for backward compatibility.\n    confounds_raw_candidates = [\n        nii_file.replace(\n            img_filename,\n            f\"{subject_label}_{specifiers}_desc-confounds_timeseries.tsv\"\n        ),\n        nii_file.replace(\n            img_filename,\n            f\"{subject_label}_{specifiers}_desc-confounds_regressors.tsv\"\n        ),\n    ]\n\n    confounds_raw = [\n        cr for cr in confounds_raw_candidates if os.path.exists(cr)\n    ]\n\n    if not confounds_raw:\n        raise ValueError(\n            (\"Could not find associated confound file. \"\n             \"The functional derivatives should exist under the same parent \"\n             \"directory.\"\n             )\n        )\n    elif len(confounds_raw) != 1:\n        raise ValueError(\"Found more than one confound file.\")\n    else:\n        return confounds_raw[0]\n\n\ndef _get_confounds_file(image_file, flag_full_aroma):\n    _check_images(image_file, flag_full_aroma)\n    confounds_raw_path = _get_file_name(image_file)\n    return confounds_raw_path\n\n\ndef _get_json(confounds_raw_path):\n    \"\"\"return json data companion file to the confounds tsv file.\"\"\"\n    # Load JSON file\n    return confounds_raw_path.replace(\"tsv\", \"json\")\n\n\ndef _load_confounds_json(confounds_json, flag_acompcor):\n    \"\"\"Load json data companion to the confounds tsv file.\"\"\"\n    try:\n        with open(confounds_json, \"rb\") as f:\n            confounds_json = json.load(f)\n    except OSError:\n        if flag_acompcor:\n            raise ValueError(\n                (\n                    f\"Could not find associated json file {confounds_json}.\"\n                    \"This is necessary for anatomical CompCor.\"\n                    \"The CompCor component is only supported for fMRIprep \"\n                    \"version >= 1.4.0.\"\n                )\n            )\n    return confounds_json\n\n\ndef _load_confounds_file_as_dataframe(confounds_raw_path):\n    \"\"\"Load raw confounds as a pandas DataFrame.\"\"\"\n    confounds_raw = pd.read_csv(\n        confounds_raw_path, delimiter=\"\\t\", encoding=\"utf-8\"\n    )\n\n    # check if the version of fMRIprep (>=1.2.0) is supported based on\n    # header format. 1.0.x and 1.1.x series uses camel case\n    if any(_is_camel_case(col_name) for col_name in confounds_raw.columns):\n        raise ValueError(\n            \"The confound file contains header in camel case.\"\n            \"This is likely the output from 1.0.x and 1.1.x \"\n            \"series. We only support fmriprep outputs >= 1.2.0.\"\n            f\"{confounds_raw.columns}\"\n        )\n\n    # even old version with no header will have the first row as header\n    try:\n        too_old = float(confounds_raw.columns[0])\n    except ValueError:\n        too_old = False\n\n    if too_old:\n        bad_file = pd.read_csv(\n            confounds_raw_path, delimiter=\"\\t\", encoding=\"utf-8\", header=None\n        )\n        raise ValueError(\n            \"The confound file contains no header.\"\n            \"Is this an old version fMRIprep output?\"\n            f\"{bad_file.head()}\"\n        )\n    return confounds_raw\n\n\ndef _ext_validator(image_file, ext):\n    \"\"\"Check image is valid based on extension.\"\"\"\n    try:\n        valid_img = all(\n            bool(re.search(img_file_patterns[ext], img)) for img in image_file\n        )\n        error_message = img_file_error[ext]\n    except KeyError:\n        valid_img = False\n        error_message = \"Unsupported input.\"\n    return valid_img, error_message\n\n\ndef _check_images(image_file, flag_full_aroma):\n    \"\"\"Validate input file and ICA AROMA related file.\"\"\"\n    if len(image_file) == 2:  # must be gifti\n        valid_img, error_message = _ext_validator(image_file, \"func.gii\")\n    elif flag_full_aroma:\n        valid_img, error_message = _ext_validator([image_file], \"aroma\")\n    else:\n        ext = \".\".join(image_file.split(\".\")[-2:])\n        valid_img, error_message = _ext_validator([image_file], ext)\n    if not valid_img:\n        raise ValueError(error_message)\n\n\ndef _prepare_output(confounds, demean):\n    \"\"\"Demean and create sample mask for the selected confounds.\"\"\"\n    sample_mask, confounds, outliers = _extract_outlier_regressors(confounds)\n    if confounds.size != 0:  # ica_aroma = \"full\" generate empty output\n        # Derivatives have NaN on the first row\n        # Replace them by estimates at second time point,\n        # otherwise nilearn will crash.\n        mask_nan = np.isnan(confounds.values[0, :])\n        confounds.iloc[0, mask_nan] = confounds.iloc[1, mask_nan]\n        if demean:\n            confounds = _demean_confounds(confounds, sample_mask)\n    return sample_mask, confounds\n\n\ndef _demean_confounds(confounds, sample_mask):\n    \"\"\"Demean the confounds. The mean is calculated on non-outlier values.\"\"\"\n    confound_cols = confounds.columns\n    if sample_mask is None:\n        confounds = scale(confounds, axis=0, with_std=False)\n    else:  # calculate the mean without outliers.\n        confounds_mean = confounds.iloc[sample_mask, :].mean(axis=0)\n        confounds -= confounds_mean\n    return pd.DataFrame(confounds, columns=confound_cols)\n\n\nclass MissingConfound(Exception):\n    \"\"\"\n    Exception raised when failing to find params in the confounds.\n\n    Parameters\n    ----------\n    params : list of missing params\n        Default values are empty lists.\n    keywords: list of missing keywords\n        Default values are empty lists.\n    \"\"\"\n\n    def __init__(self, params=None, keywords=None):\n        \"\"\"Set missing parameters and keywords.\"\"\"\n        self.params = params or []\n        self.keywords = keywords or []\n", "path": "nilearn/interfaces/fmriprep/load_confounds_utils.py"}]}