junifer 0.0.6.dev175__py3-none-any.whl → 0.0.6.dev194__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. junifer/_version.py +2 -2
  2. junifer/data/__init__.pyi +17 -31
  3. junifer/data/_dispatch.py +251 -0
  4. junifer/data/coordinates/__init__.py +9 -0
  5. junifer/data/coordinates/__init__.pyi +5 -0
  6. junifer/data/coordinates/_ants_coordinates_warper.py +96 -0
  7. junifer/data/coordinates/_coordinates.py +356 -0
  8. junifer/data/coordinates/_fsl_coordinates_warper.py +83 -0
  9. junifer/data/{tests → coordinates/tests}/test_coordinates.py +25 -31
  10. junifer/data/masks/__init__.py +9 -0
  11. junifer/data/masks/__init__.pyi +6 -0
  12. junifer/data/masks/_ants_mask_warper.py +144 -0
  13. junifer/data/masks/_fsl_mask_warper.py +87 -0
  14. junifer/data/masks/_masks.py +624 -0
  15. junifer/data/{tests → masks/tests}/test_masks.py +63 -58
  16. junifer/data/parcellations/__init__.py +9 -0
  17. junifer/data/parcellations/__init__.pyi +6 -0
  18. junifer/data/parcellations/_ants_parcellation_warper.py +154 -0
  19. junifer/data/parcellations/_fsl_parcellation_warper.py +91 -0
  20. junifer/data/{parcellations.py → parcellations/_parcellations.py} +450 -473
  21. junifer/data/{tests → parcellations/tests}/test_parcellations.py +73 -81
  22. junifer/data/pipeline_data_registry_base.py +74 -0
  23. junifer/data/utils.py +4 -0
  24. junifer/markers/complexity/hurst_exponent.py +2 -2
  25. junifer/markers/complexity/multiscale_entropy_auc.py +2 -2
  26. junifer/markers/complexity/perm_entropy.py +2 -2
  27. junifer/markers/complexity/range_entropy.py +2 -2
  28. junifer/markers/complexity/range_entropy_auc.py +2 -2
  29. junifer/markers/complexity/sample_entropy.py +2 -2
  30. junifer/markers/complexity/weighted_perm_entropy.py +2 -2
  31. junifer/markers/ets_rss.py +2 -2
  32. junifer/markers/falff/falff_parcels.py +2 -2
  33. junifer/markers/falff/falff_spheres.py +2 -2
  34. junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +1 -1
  35. junifer/markers/functional_connectivity/edge_functional_connectivity_spheres.py +1 -1
  36. junifer/markers/functional_connectivity/functional_connectivity_parcels.py +1 -1
  37. junifer/markers/functional_connectivity/functional_connectivity_spheres.py +1 -1
  38. junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +3 -3
  39. junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +2 -2
  40. junifer/markers/parcel_aggregation.py +11 -7
  41. junifer/markers/reho/reho_parcels.py +2 -2
  42. junifer/markers/reho/reho_spheres.py +2 -2
  43. junifer/markers/sphere_aggregation.py +11 -7
  44. junifer/markers/temporal_snr/temporal_snr_parcels.py +2 -2
  45. junifer/markers/temporal_snr/temporal_snr_spheres.py +2 -2
  46. junifer/markers/tests/test_ets_rss.py +3 -3
  47. junifer/markers/tests/test_parcel_aggregation.py +24 -24
  48. junifer/markers/tests/test_sphere_aggregation.py +6 -6
  49. junifer/pipeline/pipeline_component_registry.py +1 -1
  50. junifer/preprocess/confounds/fmriprep_confound_remover.py +6 -3
  51. {junifer-0.0.6.dev175.dist-info → junifer-0.0.6.dev194.dist-info}/METADATA +1 -1
  52. {junifer-0.0.6.dev175.dist-info → junifer-0.0.6.dev194.dist-info}/RECORD +76 -62
  53. {junifer-0.0.6.dev175.dist-info → junifer-0.0.6.dev194.dist-info}/WHEEL +1 -1
  54. junifer/data/coordinates.py +0 -408
  55. junifer/data/masks.py +0 -670
  56. /junifer/data/{VOIs → coordinates/VOIs}/meta/AutobiographicalMemory_VOIs.txt +0 -0
  57. /junifer/data/{VOIs → coordinates/VOIs}/meta/CogAC_VOIs.txt +0 -0
  58. /junifer/data/{VOIs → coordinates/VOIs}/meta/CogAR_VOIs.txt +0 -0
  59. /junifer/data/{VOIs → coordinates/VOIs}/meta/DMNBuckner_VOIs.txt +0 -0
  60. /junifer/data/{VOIs → coordinates/VOIs}/meta/Dosenbach2010_MNI_VOIs.txt +0 -0
  61. /junifer/data/{VOIs → coordinates/VOIs}/meta/Empathy_VOIs.txt +0 -0
  62. /junifer/data/{VOIs → coordinates/VOIs}/meta/Motor_VOIs.txt +0 -0
  63. /junifer/data/{VOIs → coordinates/VOIs}/meta/MultiTask_VOIs.txt +0 -0
  64. /junifer/data/{VOIs → coordinates/VOIs}/meta/PhysioStress_VOIs.txt +0 -0
  65. /junifer/data/{VOIs → coordinates/VOIs}/meta/Power2011_MNI_VOIs.txt +0 -0
  66. /junifer/data/{VOIs → coordinates/VOIs}/meta/Power2013_MNI_VOIs.tsv +0 -0
  67. /junifer/data/{VOIs → coordinates/VOIs}/meta/Rew_VOIs.txt +0 -0
  68. /junifer/data/{VOIs → coordinates/VOIs}/meta/Somatosensory_VOIs.txt +0 -0
  69. /junifer/data/{VOIs → coordinates/VOIs}/meta/ToM_VOIs.txt +0 -0
  70. /junifer/data/{VOIs → coordinates/VOIs}/meta/VigAtt_VOIs.txt +0 -0
  71. /junifer/data/{VOIs → coordinates/VOIs}/meta/WM_VOIs.txt +0 -0
  72. /junifer/data/{VOIs → coordinates/VOIs}/meta/eMDN_VOIs.txt +0 -0
  73. /junifer/data/{VOIs → coordinates/VOIs}/meta/eSAD_VOIs.txt +0 -0
  74. /junifer/data/{VOIs → coordinates/VOIs}/meta/extDMN_VOIs.txt +0 -0
  75. {junifer-0.0.6.dev175.dist-info → junifer-0.0.6.dev194.dist-info}/AUTHORS.rst +0 -0
  76. {junifer-0.0.6.dev175.dist-info → junifer-0.0.6.dev194.dist-info}/LICENSE.md +0 -0
  77. {junifer-0.0.6.dev175.dist-info → junifer-0.0.6.dev194.dist-info}/entry_points.txt +0 -0
  78. {junifer-0.0.6.dev175.dist-info → junifer-0.0.6.dev194.dist-info}/top_level.txt +0 -0
junifer/data/masks.py DELETED
@@ -1,670 +0,0 @@
1
- """Functions for mask manipulation."""
2
-
3
- # Authors: Federico Raimondo <f.raimondo@fz-juelich.de>
4
- # Synchon Mandal <s.mandal@fz-juelich.de>
5
- # License: AGPL
6
-
7
- import typing
8
- from pathlib import Path
9
- from typing import (
10
- TYPE_CHECKING,
11
- Any,
12
- Callable,
13
- Dict,
14
- List,
15
- Optional,
16
- Tuple,
17
- Union,
18
- )
19
-
20
- import nibabel as nib
21
- import numpy as np
22
- from nilearn.image import get_data, new_img_like, resample_to_img
23
- from nilearn.masking import (
24
- compute_background_mask,
25
- compute_epi_mask,
26
- intersect_masks,
27
- )
28
-
29
- from ..pipeline import WorkDirManager
30
- from ..utils import logger, raise_error, run_ext_cmd
31
- from .template_spaces import get_template, get_xfm
32
- from .utils import closest_resolution
33
-
34
-
35
- if TYPE_CHECKING:
36
- from nibabel import Nifti1Image
37
-
38
-
39
- __all__ = [
40
- "compute_brain_mask",
41
- "register_mask",
42
- "list_masks",
43
- "get_mask",
44
- "load_mask",
45
- ]
46
-
47
-
48
- # Path to the masks
49
- _masks_path = Path(__file__).parent / "masks"
50
-
51
-
52
- def compute_brain_mask(
53
- target_data: Dict[str, Any],
54
- extra_input: Optional[Dict[str, Any]] = None,
55
- mask_type: str = "brain",
56
- threshold: float = 0.5,
57
- ) -> "Nifti1Image":
58
- """Compute the whole-brain, grey-matter or white-matter mask.
59
-
60
- This mask is calculated using the template space and resolution as found
61
- in the ``target_data``.
62
-
63
- Parameters
64
- ----------
65
- target_data : dict
66
- The corresponding item of the data object for which mask will be
67
- loaded.
68
- extra_input : dict, optional
69
- The other fields in the data object. Useful for accessing other data
70
- types (default None).
71
- mask_type : {"brain", "gm", "wm"}, optional
72
- Type of mask to be computed:
73
-
74
- * "brain" : whole-brain mask
75
- * "gm" : grey-matter mask
76
- * "wm" : white-matter mask
77
-
78
- (default "brain").
79
- threshold : float, optional
80
- The value under which the template is cut off (default 0.5).
81
-
82
- Returns
83
- -------
84
- Nifti1Image
85
- The mask (3D image).
86
-
87
- Raises
88
- ------
89
- ValueError
90
- If ``mask_type`` is invalid or
91
- if ``extra_input`` is None when ``target_data``'s space is native.
92
-
93
- """
94
- logger.debug(f"Computing {mask_type} mask")
95
-
96
- if mask_type not in ["brain", "gm", "wm"]:
97
- raise_error(f"Unknown mask type: {mask_type}")
98
-
99
- # Check pre-requirements for space manipulation
100
- target_space = target_data["space"]
101
- # Set target standard space to target space
102
- target_std_space = target_space
103
- # Extra data type requirement check if target space is native
104
- if target_space == "native":
105
- # Check for extra inputs
106
- if extra_input is None:
107
- raise_error(
108
- "No extra input provided, requires `Warp` "
109
- "data type to infer target template space."
110
- )
111
- # Set target standard space to warp file space source
112
- target_std_space = extra_input["Warp"]["src"]
113
-
114
- # Fetch template in closest resolution
115
- template = get_template(
116
- space=target_std_space,
117
- target_data=target_data,
118
- extra_input=extra_input,
119
- template_type=mask_type if mask_type in ["gm", "wm"] else "T1w",
120
- )
121
- # Resample template to target image
122
- target_img = target_data["data"]
123
- resampled_template = resample_to_img(
124
- source_img=template, target_img=target_img
125
- )
126
-
127
- # Threshold and get mask
128
- mask = (get_data(resampled_template) >= threshold).astype("int8")
129
-
130
- return new_img_like(target_img, mask) # type: ignore
131
-
132
-
133
- # A dictionary containing all supported masks and their respective file or
134
- # data.
135
-
136
- # Each entry is a dictionary that must contain at least the following keys:
137
- # * 'family': the mask's family name (e.g., 'Vickery-Patil', 'Callable')
138
- # * 'space': the mask's space (e.g., 'MNI', 'inherit')
139
-
140
- # The built-in masks are files that are shipped with the package in the
141
- # data/masks directory. The user can also register their own masks.
142
-
143
- # Callable masks should be functions that take at least one parameter:
144
- # * `target_img`: the image to which the mask will be applied.
145
- _available_masks: Dict[str, Dict[str, Any]] = {
146
- "GM_prob0.2": {"family": "Vickery-Patil", "space": "IXI549Space"},
147
- "GM_prob0.2_cortex": {
148
- "family": "Vickery-Patil",
149
- "space": "IXI549Space",
150
- },
151
- "compute_brain_mask": {
152
- "family": "Callable",
153
- "func": compute_brain_mask,
154
- "space": "inherit",
155
- },
156
- "compute_background_mask": {
157
- "family": "Callable",
158
- "func": compute_background_mask,
159
- "space": "inherit",
160
- },
161
- "compute_epi_mask": {
162
- "family": "Callable",
163
- "func": compute_epi_mask,
164
- "space": "inherit",
165
- },
166
- "UKB_15K_GM": {
167
- "family": "UKB",
168
- "space": "MNI152NLin6Asym",
169
- },
170
- }
171
-
172
-
173
- def register_mask(
174
- name: str,
175
- mask_path: Union[str, Path],
176
- space: str,
177
- overwrite: bool = False,
178
- ) -> None:
179
- """Register a custom user mask.
180
-
181
- Parameters
182
- ----------
183
- name : str
184
- The name of the mask.
185
- mask_path : str or pathlib.Path
186
- The path to the mask file.
187
- space : str
188
- The space of the mask, for e.g., "MNI152NLin6Asym".
189
- overwrite : bool, optional
190
- If True, overwrite an existing mask with the same name.
191
- Does not apply to built-in mask (default False).
192
-
193
- Raises
194
- ------
195
- ValueError
196
- If the mask name is already registered and overwrite is set to
197
- False or if the mask name is a built-in mask.
198
-
199
- """
200
- # Check for attempt of overwriting built-in parcellations
201
- if name in _available_masks:
202
- if overwrite is True:
203
- logger.info(f"Overwriting {name} mask")
204
- if _available_masks[name]["family"] != "CustomUserMask":
205
- raise_error(
206
- f"Cannot overwrite {name} mask. It is a built-in mask."
207
- )
208
- else:
209
- raise_error(
210
- f"Mask {name} already registered. Set `overwrite=True` "
211
- "to update its value."
212
- )
213
- # Convert str to Path
214
- if not isinstance(mask_path, Path):
215
- mask_path = Path(mask_path)
216
- # Add user parcellation info
217
- _available_masks[name] = {
218
- "path": str(mask_path.absolute()),
219
- "family": "CustomUserMask",
220
- "space": space,
221
- }
222
-
223
-
224
- def list_masks() -> List[str]:
225
- """List all the available masks.
226
-
227
- Returns
228
- -------
229
- list of str
230
- A list with all available masks names.
231
-
232
- """
233
- return sorted(_available_masks.keys())
234
-
235
-
236
- def get_mask( # noqa: C901
237
- masks: Union[str, Dict, List[Union[Dict, str]]],
238
- target_data: Dict[str, Any],
239
- extra_input: Optional[Dict[str, Any]] = None,
240
- ) -> "Nifti1Image":
241
- """Get mask, tailored for the target image.
242
-
243
- Parameters
244
- ----------
245
- masks : str, dict or list of dict or str
246
- The name of the mask, or the name of a callable mask and the parameters
247
- of the mask as a dictionary. Several masks can be passed as a list.
248
- target_data : dict
249
- The corresponding item of the data object to which the mask will be
250
- applied.
251
- extra_input : dict, optional
252
- The other fields in the data object. Useful for accessing other data
253
- kinds that needs to be used in the computation of masks (default None).
254
-
255
- Returns
256
- -------
257
- Nifti1Image
258
- The mask image.
259
-
260
- Raises
261
- ------
262
- RuntimeError
263
- If warp / transformation file extension is not ".mat" or ".h5".
264
- ValueError
265
- If extra key is provided in addition to mask name in ``masks`` or
266
- if no mask is provided or
267
- if ``masks = "inherit"`` and ``mask`` key for the ``target_data`` is
268
- not found or
269
- if callable parameters are passed to non-callable mask or
270
- if parameters are passed to :func:`nilearn.masking.intersect_masks`
271
- when there is only one mask or
272
- if ``extra_input`` is None when ``target_data``'s space is native.
273
-
274
- """
275
- # Check pre-requirements for space manipulation
276
- target_space = target_data["space"]
277
- # Set target standard space to target space
278
- target_std_space = target_space
279
- # Extra data type requirement check if target space is native
280
- if target_space == "native":
281
- # Check for extra inputs
282
- if extra_input is None:
283
- raise_error(
284
- "No extra input provided, requires `Warp` and `T1w` "
285
- "data types in particular for transformation to "
286
- f"{target_data['space']} space for further computation."
287
- )
288
- # Set target standard space to warp file space source
289
- target_std_space = extra_input["Warp"]["src"]
290
-
291
- # Get the min of the voxels sizes and use it as the resolution
292
- target_img = target_data["data"]
293
- resolution = np.min(target_img.header.get_zooms()[:3])
294
-
295
- # Convert masks to list if not already
296
- if not isinstance(masks, list):
297
- masks = [masks]
298
-
299
- # Check that masks passed as dicts have only one key
300
- invalid_elements = [
301
- x for x in masks if isinstance(x, dict) and len(x) != 1
302
- ]
303
- if len(invalid_elements) > 0:
304
- raise_error(
305
- "Each of the masks dictionary must have only one key, "
306
- "the name of the mask. The following dictionaries are invalid: "
307
- f"{invalid_elements}"
308
- )
309
-
310
- # Check params for the intersection function
311
- intersect_params = {}
312
- true_masks = []
313
- for t_mask in masks:
314
- if isinstance(t_mask, dict):
315
- if "threshold" in t_mask:
316
- intersect_params["threshold"] = t_mask["threshold"]
317
- continue
318
- elif "connected" in t_mask:
319
- intersect_params["connected"] = t_mask["connected"]
320
- continue
321
- # All the other elements are masks
322
- true_masks.append(t_mask)
323
-
324
- if len(true_masks) == 0:
325
- raise_error("No mask was passed. At least one mask is required.")
326
-
327
- # Get the nested mask data type for the input data type
328
- inherited_mask_item = target_data.get("mask", None)
329
-
330
- # Create component-scoped tempdir
331
- tempdir = WorkDirManager().get_tempdir(prefix="masks")
332
- # Create element-scoped tempdir so that warped mask is
333
- # available later as nibabel stores file path reference for
334
- # loading on computation
335
- element_tempdir = WorkDirManager().get_element_tempdir(prefix="masks")
336
-
337
- # Get all the masks
338
- all_masks = []
339
- for t_mask in true_masks:
340
- if isinstance(t_mask, dict):
341
- mask_name = next(iter(t_mask.keys()))
342
- mask_params = t_mask[mask_name]
343
- else:
344
- mask_name = t_mask
345
- mask_params = None
346
-
347
- # If mask is being inherited from the datagrabber or a preprocessor,
348
- # check that it's accessible
349
- if mask_name == "inherit":
350
- if inherited_mask_item is None:
351
- raise_error(
352
- "Cannot inherit mask from the target data. Either the "
353
- "DataGrabber or a Preprocessor does not provide `mask` "
354
- "for the target data type."
355
- )
356
- mask_img = inherited_mask_item["data"]
357
- # Starting with new mask
358
- else:
359
- # Load mask
360
- mask_object, _, mask_space = load_mask(
361
- mask_name, path_only=False, resolution=resolution
362
- )
363
- # Replace mask space with target space if mask's space is inherit
364
- if mask_space == "inherit":
365
- mask_space = target_std_space
366
- # If mask is callable like from nilearn
367
- if callable(mask_object):
368
- if mask_params is None:
369
- mask_params = {}
370
- # From nilearn
371
- if mask_name != "compute_brain_mask":
372
- mask_img = mask_object(target_img, **mask_params)
373
- # Not from nilearn
374
- else:
375
- mask_img = mask_object(target_data, **mask_params)
376
- # Mask is a Nifti1Image
377
- else:
378
- # Mask params provided
379
- if mask_params is not None:
380
- # Unused params
381
- raise_error(
382
- "Cannot pass callable params to a non-callable mask."
383
- )
384
- # Resample mask to target image
385
- mask_img = resample_to_img(
386
- source_img=mask_object,
387
- target_img=target_img,
388
- interpolation="nearest",
389
- copy=True,
390
- )
391
- # Convert mask space if required
392
- if mask_space != target_std_space:
393
- # Get xfm file
394
- xfm_file_path = get_xfm(src=mask_space, dst=target_std_space)
395
- # Get target standard space template
396
- target_std_space_template_img = get_template(
397
- space=target_std_space,
398
- target_data=target_data,
399
- extra_input=extra_input,
400
- )
401
-
402
- # Save mask image to a component-scoped tempfile
403
- mask_path = tempdir / f"{mask_name}.nii.gz"
404
- nib.save(mask_img, mask_path)
405
-
406
- # Save template
407
- target_std_space_template_path = (
408
- tempdir / f"{target_std_space}_T1w_{resolution}.nii.gz"
409
- )
410
- nib.save(
411
- target_std_space_template_img,
412
- target_std_space_template_path,
413
- )
414
-
415
- # Set warped mask path
416
- warped_mask_path = element_tempdir / (
417
- f"{mask_name}_warped_from_{mask_space}_to_"
418
- f"{target_std_space}.nii.gz"
419
- )
420
-
421
- logger.debug(
422
- f"Using ANTs to warp {mask_name} "
423
- f"from {mask_space} to {target_std_space}"
424
- )
425
- # Set antsApplyTransforms command
426
- apply_transforms_cmd = [
427
- "antsApplyTransforms",
428
- "-d 3",
429
- "-e 3",
430
- "-n 'GenericLabel[NearestNeighbor]'",
431
- f"-i {mask_path.resolve()}",
432
- f"-r {target_std_space_template_path.resolve()}",
433
- f"-t {xfm_file_path.resolve()}",
434
- f"-o {warped_mask_path.resolve()}",
435
- ]
436
- # Call antsApplyTransforms
437
- run_ext_cmd(
438
- name="antsApplyTransforms", cmd=apply_transforms_cmd
439
- )
440
-
441
- mask_img = nib.load(warped_mask_path)
442
-
443
- all_masks.append(mask_img)
444
-
445
- # Multiple masks, need intersection / union
446
- if len(all_masks) > 1:
447
- # Intersect / union of masks
448
- mask_img = intersect_masks(all_masks, **intersect_params)
449
- # Single mask
450
- else:
451
- if len(intersect_params) > 0:
452
- # Yes, I'm this strict!
453
- raise_error(
454
- "Cannot pass parameters to the intersection function "
455
- "when there is only one mask."
456
- )
457
- mask_img = all_masks[0]
458
-
459
- # Warp mask if target data is native
460
- if target_space == "native":
461
- # Save mask image to a component-scoped tempfile
462
- prewarp_mask_path = tempdir / "prewarp_mask.nii.gz"
463
- nib.save(mask_img, prewarp_mask_path)
464
-
465
- # Create an element-scoped tempfile for warped output
466
- warped_mask_path = element_tempdir / "mask_warped.nii.gz"
467
-
468
- # Check for warp file type to use correct tool
469
- warp_file_ext = extra_input["Warp"]["path"].suffix
470
- if warp_file_ext == ".mat":
471
- logger.debug("Using FSL for mask warping")
472
- # Set applywarp command
473
- applywarp_cmd = [
474
- "applywarp",
475
- "--interp=nn",
476
- f"-i {prewarp_mask_path.resolve()}",
477
- # use resampled reference
478
- f"-r {target_data['reference_path'].resolve()}",
479
- f"-w {extra_input['Warp']['path'].resolve()}",
480
- f"-o {warped_mask_path.resolve()}",
481
- ]
482
- # Call applywarp
483
- run_ext_cmd(name="applywarp", cmd=applywarp_cmd)
484
-
485
- elif warp_file_ext == ".h5":
486
- logger.debug("Using ANTs for mask warping")
487
- # Set antsApplyTransforms command
488
- apply_transforms_cmd = [
489
- "antsApplyTransforms",
490
- "-d 3",
491
- "-e 3",
492
- "-n 'GenericLabel[NearestNeighbor]'",
493
- f"-i {prewarp_mask_path.resolve()}",
494
- # use resampled reference
495
- f"-r {target_data['reference_path'].resolve()}",
496
- f"-t {extra_input['Warp']['path'].resolve()}",
497
- f"-o {warped_mask_path.resolve()}",
498
- ]
499
- # Call antsApplyTransforms
500
- run_ext_cmd(name="antsApplyTransforms", cmd=apply_transforms_cmd)
501
-
502
- else:
503
- raise_error(
504
- msg=(
505
- "Unknown warp / transformation file extension: "
506
- f"{warp_file_ext}"
507
- ),
508
- klass=RuntimeError,
509
- )
510
-
511
- # Load nifti
512
- mask_img = nib.load(warped_mask_path)
513
-
514
- # Delete tempdir
515
- WorkDirManager().delete_tempdir(tempdir)
516
-
517
- return mask_img # type: ignore
518
-
519
-
520
- def load_mask(
521
- name: str,
522
- resolution: Optional[float] = None,
523
- path_only: bool = False,
524
- ) -> Tuple[Optional[Union["Nifti1Image", Callable]], Optional[Path], str]:
525
- """Load a mask.
526
-
527
- Parameters
528
- ----------
529
- name : str
530
- The name of the mask. Check valid options by calling
531
- :func:`.list_masks`.
532
- resolution : float, optional
533
- The desired resolution of the mask to load. If it is not
534
- available, the closest resolution will be loaded. Preferably, use a
535
- resolution higher than the desired one. By default, will load the
536
- highest one (default None).
537
- path_only : bool, optional
538
- If True, the mask image will not be loaded (default False).
539
-
540
- Returns
541
- -------
542
- Nifti1Image, Callable or None
543
- Loaded mask image.
544
- pathlib.Path or None
545
- File path to the mask image.
546
- str
547
- The space of the mask.
548
-
549
- Raises
550
- ------
551
- ValueError
552
- If the ``name`` is invalid of if the mask family is invalid.
553
-
554
- """
555
- # Check for valid mask name
556
- if name not in _available_masks:
557
- raise_error(
558
- f"Mask {name} not found. Valid options are: {list_masks()}"
559
- )
560
-
561
- # Copy mask definition to avoid edits in original object
562
- mask_definition = _available_masks[name].copy()
563
- t_family = mask_definition.pop("family")
564
-
565
- # Check if the mask family is custom or built-in
566
- mask_img = None
567
- if t_family == "CustomUserMask":
568
- mask_fname = Path(mask_definition["path"])
569
- elif t_family == "Vickery-Patil":
570
- mask_fname = _load_vickery_patil_mask(name, resolution)
571
- elif t_family == "Callable":
572
- mask_img = mask_definition["func"]
573
- mask_fname = None
574
- elif t_family == "UKB":
575
- mask_fname = _load_ukb_mask(name)
576
- else:
577
- raise_error(f"I don't know about the {t_family} mask family.")
578
-
579
- # Load mask
580
- if mask_fname is not None:
581
- logger.info(f"Loading mask {mask_fname.absolute()!s}")
582
- if path_only is False:
583
- # Load via nibabel
584
- mask_img = nib.load(mask_fname)
585
-
586
- # Type-cast to remove error
587
- mask_img = typing.cast("Nifti1Image", mask_img)
588
- return mask_img, mask_fname, mask_definition["space"]
589
-
590
-
591
- def _load_vickery_patil_mask(
592
- name: str,
593
- resolution: Optional[float] = None,
594
- ) -> Path:
595
- """Load Vickery-Patil mask.
596
-
597
- Parameters
598
- ----------
599
- name : {"GM_prob0.2", "GM_prob0.2_cortex"}
600
- The name of the mask.
601
- resolution : float, optional
602
- The desired resolution of the mask to load. If it is not
603
- available, the closest resolution will be loaded. Preferably, use a
604
- resolution higher than the desired one. By default, will load the
605
- highest one (default None).
606
-
607
- Returns
608
- -------
609
- pathlib.Path
610
- File path to the mask image.
611
-
612
- Raises
613
- ------
614
- ValueError
615
- If ``name`` is invalid or if ``resolution`` is invalid for
616
- ``name = "GM_prob0.2"``.
617
-
618
- """
619
- if name == "GM_prob0.2":
620
- available_resolutions = [1.5, 3.0]
621
- to_load = closest_resolution(resolution, available_resolutions)
622
- if to_load == 3.0:
623
- mask_fname = (
624
- "CAT12_IXI555_MNI152_TMP_GS_GMprob0.2_clean_3mm.nii.gz"
625
- )
626
- elif to_load == 1.5:
627
- mask_fname = "CAT12_IXI555_MNI152_TMP_GS_GMprob0.2_clean.nii.gz"
628
- else:
629
- raise_error(
630
- f"Cannot find a GM_prob0.2 mask of resolution {resolution}"
631
- )
632
- elif name == "GM_prob0.2_cortex":
633
- mask_fname = "GMprob0.2_cortex_3mm_NA_rm.nii.gz"
634
- else:
635
- raise_error(f"Cannot find a Vickery-Patil mask called {name}")
636
-
637
- # Set path for masks
638
- mask_fname = _masks_path / "vickery-patil" / mask_fname
639
-
640
- return mask_fname
641
-
642
-
643
- def _load_ukb_mask(name: str) -> Path:
644
- """Load UKB mask.
645
-
646
- Parameters
647
- ----------
648
- name : {"UKB_15K_GM"}
649
- The name of the mask.
650
-
651
- Returns
652
- -------
653
- pathlib.Path
654
- File path to the mask image.
655
-
656
- Raises
657
- ------
658
- ValueError
659
- If ``name`` is invalid.
660
-
661
- """
662
- if name == "UKB_15K_GM":
663
- mask_fname = "UKB_15K_GM_template.nii.gz"
664
- else:
665
- raise_error(f"Cannot find a UKB mask called {name}")
666
-
667
- # Set path for masks
668
- mask_fname = _masks_path / "ukb" / mask_fname
669
-
670
- return mask_fname