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.
- junifer/_version.py +2 -2
- junifer/data/__init__.pyi +17 -31
- junifer/data/_dispatch.py +251 -0
- junifer/data/coordinates/__init__.py +9 -0
- junifer/data/coordinates/__init__.pyi +5 -0
- junifer/data/coordinates/_ants_coordinates_warper.py +96 -0
- junifer/data/coordinates/_coordinates.py +356 -0
- junifer/data/coordinates/_fsl_coordinates_warper.py +83 -0
- junifer/data/{tests → coordinates/tests}/test_coordinates.py +25 -31
- junifer/data/masks/__init__.py +9 -0
- junifer/data/masks/__init__.pyi +6 -0
- junifer/data/masks/_ants_mask_warper.py +144 -0
- junifer/data/masks/_fsl_mask_warper.py +87 -0
- junifer/data/masks/_masks.py +624 -0
- junifer/data/{tests → masks/tests}/test_masks.py +63 -58
- junifer/data/parcellations/__init__.py +9 -0
- junifer/data/parcellations/__init__.pyi +6 -0
- junifer/data/parcellations/_ants_parcellation_warper.py +154 -0
- junifer/data/parcellations/_fsl_parcellation_warper.py +91 -0
- junifer/data/{parcellations.py → parcellations/_parcellations.py} +450 -473
- junifer/data/{tests → parcellations/tests}/test_parcellations.py +73 -81
- junifer/data/pipeline_data_registry_base.py +74 -0
- junifer/data/utils.py +4 -0
- junifer/markers/complexity/hurst_exponent.py +2 -2
- junifer/markers/complexity/multiscale_entropy_auc.py +2 -2
- junifer/markers/complexity/perm_entropy.py +2 -2
- junifer/markers/complexity/range_entropy.py +2 -2
- junifer/markers/complexity/range_entropy_auc.py +2 -2
- junifer/markers/complexity/sample_entropy.py +2 -2
- junifer/markers/complexity/weighted_perm_entropy.py +2 -2
- junifer/markers/ets_rss.py +2 -2
- junifer/markers/falff/falff_parcels.py +2 -2
- junifer/markers/falff/falff_spheres.py +2 -2
- junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +1 -1
- junifer/markers/functional_connectivity/edge_functional_connectivity_spheres.py +1 -1
- junifer/markers/functional_connectivity/functional_connectivity_parcels.py +1 -1
- junifer/markers/functional_connectivity/functional_connectivity_spheres.py +1 -1
- junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +3 -3
- junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +2 -2
- junifer/markers/parcel_aggregation.py +11 -7
- junifer/markers/reho/reho_parcels.py +2 -2
- junifer/markers/reho/reho_spheres.py +2 -2
- junifer/markers/sphere_aggregation.py +11 -7
- junifer/markers/temporal_snr/temporal_snr_parcels.py +2 -2
- junifer/markers/temporal_snr/temporal_snr_spheres.py +2 -2
- junifer/markers/tests/test_ets_rss.py +3 -3
- junifer/markers/tests/test_parcel_aggregation.py +24 -24
- junifer/markers/tests/test_sphere_aggregation.py +6 -6
- junifer/pipeline/pipeline_component_registry.py +1 -1
- junifer/preprocess/confounds/fmriprep_confound_remover.py +6 -3
- {junifer-0.0.6.dev175.dist-info → junifer-0.0.6.dev194.dist-info}/METADATA +1 -1
- {junifer-0.0.6.dev175.dist-info → junifer-0.0.6.dev194.dist-info}/RECORD +76 -62
- {junifer-0.0.6.dev175.dist-info → junifer-0.0.6.dev194.dist-info}/WHEEL +1 -1
- junifer/data/coordinates.py +0 -408
- junifer/data/masks.py +0 -670
- /junifer/data/{VOIs → coordinates/VOIs}/meta/AutobiographicalMemory_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/CogAC_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/CogAR_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/DMNBuckner_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/Dosenbach2010_MNI_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/Empathy_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/Motor_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/MultiTask_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/PhysioStress_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/Power2011_MNI_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/Power2013_MNI_VOIs.tsv +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/Rew_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/Somatosensory_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/ToM_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/VigAtt_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/WM_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/eMDN_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/eSAD_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/extDMN_VOIs.txt +0 -0
- {junifer-0.0.6.dev175.dist-info → junifer-0.0.6.dev194.dist-info}/AUTHORS.rst +0 -0
- {junifer-0.0.6.dev175.dist-info → junifer-0.0.6.dev194.dist-info}/LICENSE.md +0 -0
- {junifer-0.0.6.dev175.dist-info → junifer-0.0.6.dev194.dist-info}/entry_points.txt +0 -0
- {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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|