junifer 0.0.7.dev132__py3-none-any.whl → 0.0.7.dev169__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/conftest.py +32 -0
- junifer/data/__init__.pyi +2 -0
- junifer/data/_dispatch.py +11 -7
- junifer/data/maps/__init__.py +9 -0
- junifer/data/maps/__init__.pyi +5 -0
- junifer/data/maps/_ants_maps_warper.py +156 -0
- junifer/data/maps/_fsl_maps_warper.py +85 -0
- junifer/data/maps/_maps.py +446 -0
- junifer/data/maps/tests/test_maps.py +255 -0
- junifer/data/masks/_masks.py +3 -2
- junifer/data/parcellations/_parcellations.py +1 -1
- junifer/markers/__init__.pyi +12 -2
- junifer/markers/falff/__init__.pyi +2 -1
- junifer/markers/falff/falff_maps.py +151 -0
- junifer/markers/falff/tests/test_falff_maps.py +167 -0
- junifer/markers/functional_connectivity/__init__.pyi +4 -0
- junifer/markers/functional_connectivity/edge_functional_connectivity_maps.py +115 -0
- junifer/markers/functional_connectivity/functional_connectivity_maps.py +95 -0
- junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_maps.py +74 -0
- junifer/markers/functional_connectivity/tests/test_functional_connectivity_maps.py +110 -0
- junifer/markers/maps_aggregation.py +201 -0
- junifer/markers/reho/__init__.pyi +2 -1
- junifer/markers/reho/reho_maps.py +161 -0
- junifer/markers/reho/tests/test_reho_maps.py +135 -0
- junifer/markers/temporal_snr/__init__.pyi +2 -1
- junifer/markers/temporal_snr/temporal_snr_maps.py +80 -0
- junifer/markers/temporal_snr/tests/test_temporal_snr_maps.py +67 -0
- junifer/markers/tests/test_maps_aggregation.py +430 -0
- junifer/typing/_typing.py +1 -3
- {junifer-0.0.7.dev132.dist-info → junifer-0.0.7.dev169.dist-info}/METADATA +2 -2
- {junifer-0.0.7.dev132.dist-info → junifer-0.0.7.dev169.dist-info}/RECORD +37 -19
- {junifer-0.0.7.dev132.dist-info → junifer-0.0.7.dev169.dist-info}/WHEEL +0 -0
- {junifer-0.0.7.dev132.dist-info → junifer-0.0.7.dev169.dist-info}/entry_points.txt +0 -0
- {junifer-0.0.7.dev132.dist-info → junifer-0.0.7.dev169.dist-info}/licenses/AUTHORS.rst +0 -0
- {junifer-0.0.7.dev132.dist-info → junifer-0.0.7.dev169.dist-info}/licenses/LICENSE.md +0 -0
- {junifer-0.0.7.dev132.dist-info → junifer-0.0.7.dev169.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,446 @@
|
|
1
|
+
"""Provide a class for centralized maps data registry."""
|
2
|
+
|
3
|
+
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
|
4
|
+
# License: AGPL
|
5
|
+
|
6
|
+
from pathlib import Path
|
7
|
+
from typing import TYPE_CHECKING, Any, Optional, Union
|
8
|
+
|
9
|
+
import nibabel as nib
|
10
|
+
import nilearn.image as nimg
|
11
|
+
import numpy as np
|
12
|
+
from junifer_data import get
|
13
|
+
|
14
|
+
from ...utils import logger, raise_error
|
15
|
+
from ..pipeline_data_registry_base import BasePipelineDataRegistry
|
16
|
+
from ..utils import (
|
17
|
+
JUNIFER_DATA_PARAMS,
|
18
|
+
closest_resolution,
|
19
|
+
get_dataset_path,
|
20
|
+
get_native_warper,
|
21
|
+
)
|
22
|
+
from ._ants_maps_warper import ANTsMapsWarper
|
23
|
+
from ._fsl_maps_warper import FSLMapsWarper
|
24
|
+
|
25
|
+
|
26
|
+
if TYPE_CHECKING:
|
27
|
+
from nibabel.nifti1 import Nifti1Image
|
28
|
+
|
29
|
+
|
30
|
+
__all__ = ["MapsRegistry"]
|
31
|
+
|
32
|
+
|
33
|
+
class MapsRegistry(BasePipelineDataRegistry):
|
34
|
+
"""Class for maps data registry.
|
35
|
+
|
36
|
+
This class is a singleton and is used for managing available maps
|
37
|
+
data in a centralized manner.
|
38
|
+
|
39
|
+
"""
|
40
|
+
|
41
|
+
def __init__(self) -> None:
|
42
|
+
"""Initialize the class."""
|
43
|
+
super().__init__()
|
44
|
+
# Each entry in registry is a dictionary that must contain at least
|
45
|
+
# the following keys:
|
46
|
+
# * 'family': the maps' family name (e.g., 'Smith')
|
47
|
+
# * 'space': the maps' space (e.g., 'MNI')
|
48
|
+
# and can also have optional key(s):
|
49
|
+
# * 'valid_resolutions': a list of valid resolutions for the
|
50
|
+
# maps (e.g., [1, 2])
|
51
|
+
# The built-in maps are files that are shipped with the
|
52
|
+
# junifer-data dataset.
|
53
|
+
# Make built-in and external dictionaries for validation later
|
54
|
+
self._builtin = {}
|
55
|
+
self._external = {}
|
56
|
+
|
57
|
+
# Add Smith
|
58
|
+
for comp in ["rsn", "bm"]:
|
59
|
+
for dim in [10, 20, 70]:
|
60
|
+
self._builtin.update(
|
61
|
+
{
|
62
|
+
f"Smith_{comp}_{dim}": {
|
63
|
+
"family": "Smith2009",
|
64
|
+
"components": comp,
|
65
|
+
"dimension": dim,
|
66
|
+
"space": "MNI152NLin6Asym",
|
67
|
+
}
|
68
|
+
}
|
69
|
+
)
|
70
|
+
|
71
|
+
# Update registry with built-in ones
|
72
|
+
self._registry.update(self._builtin)
|
73
|
+
|
74
|
+
def register(
|
75
|
+
self,
|
76
|
+
name: str,
|
77
|
+
maps_path: Union[str, Path],
|
78
|
+
maps_labels: list[str],
|
79
|
+
space: str,
|
80
|
+
overwrite: bool = False,
|
81
|
+
) -> None:
|
82
|
+
"""Register a custom user map(s).
|
83
|
+
|
84
|
+
Parameters
|
85
|
+
----------
|
86
|
+
name : str
|
87
|
+
The name of the map(s).
|
88
|
+
maps_path : str or pathlib.Path
|
89
|
+
The path to the map(s) file.
|
90
|
+
maps_labels : list of str
|
91
|
+
The list of labels for the map(s).
|
92
|
+
space : str
|
93
|
+
The template space of the map(s), e.g., "MNI152NLin6Asym".
|
94
|
+
overwrite : bool, optional
|
95
|
+
If True, overwrite an existing maps with the same name.
|
96
|
+
Does not apply to built-in maps (default False).
|
97
|
+
|
98
|
+
Raises
|
99
|
+
------
|
100
|
+
ValueError
|
101
|
+
If the map(s) ``name`` is a built-in map(s) or
|
102
|
+
if the map(s) ``name`` is already registered and
|
103
|
+
``overwrite=False``.
|
104
|
+
|
105
|
+
"""
|
106
|
+
# Check for attempt of overwriting built-in maps
|
107
|
+
if name in self._builtin:
|
108
|
+
raise_error(
|
109
|
+
f"Map(s): {name} already registered as built-in map(s)."
|
110
|
+
)
|
111
|
+
# Check for attempt of overwriting external maps
|
112
|
+
if name in self._external:
|
113
|
+
if overwrite:
|
114
|
+
logger.info(f"Overwriting map(s): {name}")
|
115
|
+
else:
|
116
|
+
raise_error(
|
117
|
+
f"Map(s): {name} already registered. Set "
|
118
|
+
"`overwrite=True` to update its value."
|
119
|
+
)
|
120
|
+
# Convert str to Path
|
121
|
+
if not isinstance(maps_path, Path):
|
122
|
+
maps_path = Path(maps_path)
|
123
|
+
# Registration
|
124
|
+
logger.info(f"Registering map(s): {name}")
|
125
|
+
# Add user maps info
|
126
|
+
self._external[name] = {
|
127
|
+
"path": maps_path,
|
128
|
+
"labels": maps_labels,
|
129
|
+
"family": "CustomUserMaps",
|
130
|
+
"space": space,
|
131
|
+
}
|
132
|
+
# Update registry
|
133
|
+
self._registry[name] = {
|
134
|
+
"path": maps_path,
|
135
|
+
"labels": maps_labels,
|
136
|
+
"family": "CustomUserMaps",
|
137
|
+
"space": space,
|
138
|
+
}
|
139
|
+
|
140
|
+
def deregister(self, name: str) -> None:
|
141
|
+
"""De-register a custom user map(s).
|
142
|
+
|
143
|
+
Parameters
|
144
|
+
----------
|
145
|
+
name : str
|
146
|
+
The name of the map(s).
|
147
|
+
|
148
|
+
"""
|
149
|
+
logger.info(f"De-registering map(s): {name}")
|
150
|
+
# Remove maps info
|
151
|
+
_ = self._external.pop(name)
|
152
|
+
# Update registry
|
153
|
+
_ = self._registry.pop(name)
|
154
|
+
|
155
|
+
def load(
|
156
|
+
self,
|
157
|
+
name: str,
|
158
|
+
target_space: str,
|
159
|
+
resolution: Optional[float] = None,
|
160
|
+
path_only: bool = False,
|
161
|
+
) -> tuple[Optional["Nifti1Image"], list[str], Path, str]:
|
162
|
+
"""Load map(s) and labels.
|
163
|
+
|
164
|
+
Parameters
|
165
|
+
----------
|
166
|
+
name : str
|
167
|
+
The name of the map(s).
|
168
|
+
target_space : str
|
169
|
+
The desired space of the map(s).
|
170
|
+
resolution : float, optional
|
171
|
+
The desired resolution of the map(s) to load. If it is not
|
172
|
+
available, the closest resolution will be loaded. Preferably, use a
|
173
|
+
resolution higher than the desired one. By default, will load the
|
174
|
+
highest one (default None).
|
175
|
+
path_only : bool, optional
|
176
|
+
If True, the map(s) image will not be loaded (default False).
|
177
|
+
|
178
|
+
Returns
|
179
|
+
-------
|
180
|
+
Nifti1Image or None
|
181
|
+
Loaded map(s) image.
|
182
|
+
list of str
|
183
|
+
Map(s) labels.
|
184
|
+
pathlib.Path
|
185
|
+
File path to the map(s) image.
|
186
|
+
str
|
187
|
+
The space of the map(s).
|
188
|
+
|
189
|
+
Raises
|
190
|
+
------
|
191
|
+
ValueError
|
192
|
+
If ``name`` is invalid or
|
193
|
+
if the map(s) family is invalid or
|
194
|
+
if the map(s) values and labels
|
195
|
+
don't have equal dimension or if the value range is invalid.
|
196
|
+
|
197
|
+
"""
|
198
|
+
# Check for valid maps name
|
199
|
+
if name not in self._registry:
|
200
|
+
raise_error(
|
201
|
+
f"Map(s): {name} not found. Valid options are: {self.list}"
|
202
|
+
)
|
203
|
+
|
204
|
+
# Copy maps definition to avoid edits in original object
|
205
|
+
maps_def = self._registry[name].copy()
|
206
|
+
t_family = maps_def.pop("family")
|
207
|
+
space = maps_def.pop("space")
|
208
|
+
|
209
|
+
# Check and get highest resolution
|
210
|
+
if space != target_space:
|
211
|
+
logger.info(
|
212
|
+
f"Map(s) will be warped from {space} to {target_space} "
|
213
|
+
"using highest resolution"
|
214
|
+
)
|
215
|
+
resolution = None
|
216
|
+
|
217
|
+
# Check if the maps family is custom or built-in
|
218
|
+
if t_family == "CustomUserMaps":
|
219
|
+
maps_fname = maps_def["path"]
|
220
|
+
maps_labels = maps_def["labels"]
|
221
|
+
elif t_family in [
|
222
|
+
"Smith2009",
|
223
|
+
]:
|
224
|
+
# Load maps and labels
|
225
|
+
if t_family == "Smith2009":
|
226
|
+
maps_fname, maps_labels = _retrieve_smith(
|
227
|
+
resolution=resolution,
|
228
|
+
**maps_def,
|
229
|
+
)
|
230
|
+
else: # pragma: no cover
|
231
|
+
raise_error(f"Unknown map(s) family: {t_family}")
|
232
|
+
|
233
|
+
# Load maps image and values
|
234
|
+
logger.info(f"Loading map(s): {maps_fname.absolute()!s}")
|
235
|
+
maps_img = None
|
236
|
+
if not path_only:
|
237
|
+
# Load image via nibabel
|
238
|
+
maps_img = nib.load(maps_fname)
|
239
|
+
# Get regions
|
240
|
+
maps_regions = maps_img.get_fdata().shape[-1]
|
241
|
+
# Check for dimension
|
242
|
+
if maps_regions != len(maps_labels):
|
243
|
+
raise_error(
|
244
|
+
f"Map(s) {name} has {maps_regions} "
|
245
|
+
f"regions but {len(maps_labels)} labels."
|
246
|
+
)
|
247
|
+
|
248
|
+
return maps_img, maps_labels, maps_fname, space
|
249
|
+
|
250
|
+
def get(
|
251
|
+
self,
|
252
|
+
maps: str,
|
253
|
+
target_data: dict[str, Any],
|
254
|
+
extra_input: Optional[dict[str, Any]] = None,
|
255
|
+
) -> tuple["Nifti1Image", list[str]]:
|
256
|
+
"""Get map(s), tailored for the target image.
|
257
|
+
|
258
|
+
Parameters
|
259
|
+
----------
|
260
|
+
maps : str
|
261
|
+
The name of the map(s).
|
262
|
+
target_data : dict
|
263
|
+
The corresponding item of the data object to which the map(s)
|
264
|
+
will be applied.
|
265
|
+
extra_input : dict, optional
|
266
|
+
The other fields in the data object. Useful for accessing other
|
267
|
+
data kinds that needs to be used in the computation of
|
268
|
+
map(s) (default None).
|
269
|
+
|
270
|
+
Returns
|
271
|
+
-------
|
272
|
+
Nifti1Image
|
273
|
+
The map(s) image.
|
274
|
+
list of str
|
275
|
+
Map(s) labels.
|
276
|
+
|
277
|
+
Raises
|
278
|
+
------
|
279
|
+
ValueError
|
280
|
+
If ``extra_input`` is None when ``target_data``'s space is native.
|
281
|
+
|
282
|
+
"""
|
283
|
+
# Check pre-requirements for space manipulation
|
284
|
+
target_space = target_data["space"]
|
285
|
+
logger.debug(f"Getting {maps} in {target_space} space.")
|
286
|
+
# Extra data type requirement check if target space is native
|
287
|
+
if target_space == "native": # pragma: no cover
|
288
|
+
# Check for extra inputs
|
289
|
+
if extra_input is None:
|
290
|
+
raise_error(
|
291
|
+
"No extra input provided, requires `Warp` and `T1w` "
|
292
|
+
"data types in particular for transformation to "
|
293
|
+
f"{target_data['space']} space for further computation."
|
294
|
+
)
|
295
|
+
# Get native space warper spec
|
296
|
+
warper_spec = get_native_warper(
|
297
|
+
target_data=target_data,
|
298
|
+
other_data=extra_input,
|
299
|
+
)
|
300
|
+
# Set target standard space to warp file space source
|
301
|
+
target_std_space = warper_spec["src"]
|
302
|
+
logger.debug(
|
303
|
+
f"Target space is native. Will warp from {target_std_space}"
|
304
|
+
)
|
305
|
+
else:
|
306
|
+
# Set target standard space to target space
|
307
|
+
target_std_space = target_space
|
308
|
+
|
309
|
+
# Get the min of the voxels sizes and use it as the resolution
|
310
|
+
target_img = target_data["data"]
|
311
|
+
resolution = np.min(target_img.header.get_zooms()[:3])
|
312
|
+
|
313
|
+
# Load maps
|
314
|
+
logger.debug(f"Loading map(s) {maps}")
|
315
|
+
img, labels, _, space = self.load(
|
316
|
+
name=maps,
|
317
|
+
resolution=resolution,
|
318
|
+
target_space=target_space,
|
319
|
+
)
|
320
|
+
|
321
|
+
# Convert maps spaces if required;
|
322
|
+
# cannot be "native" due to earlier check
|
323
|
+
if space != target_std_space:
|
324
|
+
logger.debug(
|
325
|
+
f"Warping {maps} to {target_std_space} space using ANTs."
|
326
|
+
)
|
327
|
+
raw_img = ANTsMapsWarper().warp(
|
328
|
+
maps_name=maps,
|
329
|
+
maps_img=img,
|
330
|
+
src=space,
|
331
|
+
dst=target_std_space,
|
332
|
+
target_data=target_data,
|
333
|
+
warp_data=None,
|
334
|
+
)
|
335
|
+
# Remove extra dimension added by ANTs
|
336
|
+
img = nimg.math_img("np.squeeze(img)", img=raw_img)
|
337
|
+
|
338
|
+
if target_space != "native":
|
339
|
+
# No warping is going to happen, just resampling, because
|
340
|
+
# we are in the correct space
|
341
|
+
logger.debug(f"Resampling {maps} to target image.")
|
342
|
+
# Resample maps to target image
|
343
|
+
img = nimg.resample_to_img(
|
344
|
+
source_img=img,
|
345
|
+
target_img=target_img,
|
346
|
+
interpolation="continuous",
|
347
|
+
copy=True,
|
348
|
+
)
|
349
|
+
else: # pragma: no cover
|
350
|
+
# Warp maps if target space is native as either
|
351
|
+
# the image is in the right non-native space or it's
|
352
|
+
# warped from one non-native space to another non-native space
|
353
|
+
logger.debug(
|
354
|
+
"Warping map(s) to native space using "
|
355
|
+
f"{warper_spec['warper']}."
|
356
|
+
)
|
357
|
+
# extra_input check done earlier and warper_spec exists
|
358
|
+
if warper_spec["warper"] == "fsl":
|
359
|
+
img = FSLMapsWarper().warp(
|
360
|
+
maps_name="native",
|
361
|
+
maps_img=img,
|
362
|
+
target_data=target_data,
|
363
|
+
warp_data=warper_spec,
|
364
|
+
)
|
365
|
+
elif warper_spec["warper"] == "ants":
|
366
|
+
img = ANTsMapsWarper().warp(
|
367
|
+
maps_name="native",
|
368
|
+
maps_img=img,
|
369
|
+
src="",
|
370
|
+
dst="native",
|
371
|
+
target_data=target_data,
|
372
|
+
warp_data=warper_spec,
|
373
|
+
)
|
374
|
+
|
375
|
+
return img, labels
|
376
|
+
|
377
|
+
|
378
|
+
def _retrieve_smith(
|
379
|
+
resolution: Optional[float] = None,
|
380
|
+
components: Optional[str] = None,
|
381
|
+
dimension: Optional[int] = None,
|
382
|
+
) -> tuple[Path, list[str]]:
|
383
|
+
"""Retrieve Smith maps.
|
384
|
+
|
385
|
+
Parameters
|
386
|
+
----------
|
387
|
+
resolution : 2.0, optional
|
388
|
+
The desired resolution of the maps to load. If it is not
|
389
|
+
available, the closest resolution will be loaded. Preferably, use a
|
390
|
+
resolution higher than the desired one. By default, will load the
|
391
|
+
highest one (default None). Available resolution for these
|
392
|
+
maps are 2mm.
|
393
|
+
components : {"rsn", "bm"}, optional
|
394
|
+
The components to load. "rsn" loads the resting-fMRI components and
|
395
|
+
"bm" loads the BrainMap components (default None).
|
396
|
+
dimension : {10, 20, 70}, optional
|
397
|
+
The number of dimensions to load (default None).
|
398
|
+
|
399
|
+
Returns
|
400
|
+
-------
|
401
|
+
pathlib.Path
|
402
|
+
File path to the maps image.
|
403
|
+
list of str
|
404
|
+
Maps labels.
|
405
|
+
|
406
|
+
Raises
|
407
|
+
------
|
408
|
+
ValueError
|
409
|
+
If invalid value is provided for ``components`` or ``dimension``.
|
410
|
+
|
411
|
+
"""
|
412
|
+
logger.info("Maps parameters:")
|
413
|
+
logger.info(f"\tresolution: {resolution}")
|
414
|
+
logger.info(f"\tcomponents: {components}")
|
415
|
+
logger.info(f"\tdimension: {dimension}")
|
416
|
+
|
417
|
+
# Check resolution
|
418
|
+
_valid_resolutions = [2.0]
|
419
|
+
resolution = closest_resolution(resolution, _valid_resolutions)
|
420
|
+
|
421
|
+
# Check components value
|
422
|
+
_valid_components = ["rsn", "bm"]
|
423
|
+
if components not in _valid_components:
|
424
|
+
raise_error(
|
425
|
+
f"The parameter `components` ({components}) needs to be one of "
|
426
|
+
f"the following: {_valid_components}"
|
427
|
+
)
|
428
|
+
|
429
|
+
# Check dimension value
|
430
|
+
_valid_dimension = [10, 20, 70]
|
431
|
+
if dimension not in _valid_dimension:
|
432
|
+
raise_error(
|
433
|
+
f"The parameter `dimension` ({dimension}) needs to be one of the "
|
434
|
+
f"following: {_valid_dimension}"
|
435
|
+
)
|
436
|
+
|
437
|
+
# Fetch file path
|
438
|
+
maps_img_path = get(
|
439
|
+
file_path=Path(
|
440
|
+
f"parcellations/Smith2009/{components}{dimension}.nii.gz"
|
441
|
+
),
|
442
|
+
dataset_path=get_dataset_path(),
|
443
|
+
**JUNIFER_DATA_PARAMS,
|
444
|
+
)
|
445
|
+
|
446
|
+
return maps_img_path, [f"Map_{i}" for i in range(dimension)]
|
@@ -0,0 +1,255 @@
|
|
1
|
+
"""Provide tests for maps."""
|
2
|
+
|
3
|
+
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
|
4
|
+
# License: AGPL
|
5
|
+
|
6
|
+
import pytest
|
7
|
+
from numpy.testing import assert_array_equal
|
8
|
+
|
9
|
+
from junifer.data import (
|
10
|
+
deregister_data,
|
11
|
+
get_data,
|
12
|
+
list_data,
|
13
|
+
load_data,
|
14
|
+
register_data,
|
15
|
+
)
|
16
|
+
from junifer.data.maps._maps import _retrieve_smith
|
17
|
+
from junifer.datagrabber import PatternDataladDataGrabber
|
18
|
+
from junifer.datareader import DefaultDataReader
|
19
|
+
from junifer.pipeline.utils import _check_ants
|
20
|
+
from junifer.testing.datagrabbers import PartlyCloudyTestingDataGrabber
|
21
|
+
|
22
|
+
|
23
|
+
def test_register_built_in_check() -> None:
|
24
|
+
"""Test maps registration check for built-in maps."""
|
25
|
+
with pytest.raises(ValueError, match=r"built-in"):
|
26
|
+
register_data(
|
27
|
+
kind="maps",
|
28
|
+
name="Smith_rsn_10",
|
29
|
+
maps_path="testmaps.nii.gz",
|
30
|
+
maps_labels=["1", "2"],
|
31
|
+
space="MNI",
|
32
|
+
)
|
33
|
+
|
34
|
+
|
35
|
+
def test_list_incorrect() -> None:
|
36
|
+
"""Test incorrect information check for list mapss."""
|
37
|
+
assert "testmaps" not in list_data(kind="maps")
|
38
|
+
|
39
|
+
|
40
|
+
def test_register_overwrite() -> None:
|
41
|
+
"""Test maps registration check for overwriting."""
|
42
|
+
register_data(
|
43
|
+
kind="maps",
|
44
|
+
name="testmaps",
|
45
|
+
maps_path="testmaps.nii.gz",
|
46
|
+
maps_labels=["1", "2"],
|
47
|
+
space="MNI152NLin6Sym",
|
48
|
+
)
|
49
|
+
with pytest.raises(ValueError, match=r"already registered"):
|
50
|
+
register_data(
|
51
|
+
kind="maps",
|
52
|
+
name="testmaps",
|
53
|
+
maps_path="testmaps.nii.gz",
|
54
|
+
maps_labels=["1", "2"],
|
55
|
+
space="MNI152NLin6Sym",
|
56
|
+
overwrite=False,
|
57
|
+
)
|
58
|
+
|
59
|
+
register_data(
|
60
|
+
kind="maps",
|
61
|
+
name="testmaps",
|
62
|
+
maps_path="testmaps.nii.gz",
|
63
|
+
maps_labels=["1", "2"],
|
64
|
+
space="MNI152NLin6Sym",
|
65
|
+
overwrite=True,
|
66
|
+
)
|
67
|
+
|
68
|
+
assert (
|
69
|
+
load_data(
|
70
|
+
kind="maps",
|
71
|
+
name="testmaps",
|
72
|
+
target_space="MNI152NLin6Sym",
|
73
|
+
path_only=True,
|
74
|
+
)[2].name
|
75
|
+
== "testmaps.nii.gz"
|
76
|
+
)
|
77
|
+
|
78
|
+
|
79
|
+
def test_register_valid_input() -> None:
|
80
|
+
"""Test maps registration check for valid input."""
|
81
|
+
maps, labels, maps_path, _ = load_data(
|
82
|
+
kind="maps",
|
83
|
+
name="Smith_rsn_10",
|
84
|
+
target_space="MNI152NLin6Asym",
|
85
|
+
)
|
86
|
+
assert maps is not None
|
87
|
+
|
88
|
+
# Test wrong number of labels
|
89
|
+
register_data(
|
90
|
+
kind="maps",
|
91
|
+
name="WrongLabels",
|
92
|
+
maps_path=maps_path,
|
93
|
+
maps_labels=labels[:5],
|
94
|
+
space="MNI152NLin6Asym",
|
95
|
+
)
|
96
|
+
with pytest.raises(ValueError, match=r"has 10 regions but 5"):
|
97
|
+
load_data(
|
98
|
+
kind="maps",
|
99
|
+
name="WrongLabels",
|
100
|
+
target_space="MNI152NLin6Asym",
|
101
|
+
)
|
102
|
+
|
103
|
+
|
104
|
+
def test_list() -> None:
|
105
|
+
"""Test listing of available coordinates."""
|
106
|
+
assert {"Smith_rsn_10", "Smith_bm_70"}.issubset(
|
107
|
+
set(list_data(kind="maps"))
|
108
|
+
)
|
109
|
+
|
110
|
+
|
111
|
+
def test_load_nonexisting() -> None:
|
112
|
+
"""Test loading maps that not exist."""
|
113
|
+
with pytest.raises(ValueError, match=r"not found"):
|
114
|
+
load_data(kind="maps", name="nomaps", target_space="MNI152NLin6Sym")
|
115
|
+
|
116
|
+
|
117
|
+
def test_get() -> None:
|
118
|
+
"""Test tailored maps fetch."""
|
119
|
+
with PatternDataladDataGrabber(
|
120
|
+
uri="https://github.com/OpenNeuroDatasets/ds005226.git",
|
121
|
+
types=["BOLD"],
|
122
|
+
patterns={
|
123
|
+
"BOLD": {
|
124
|
+
"pattern": (
|
125
|
+
"derivatives/pre-processed_data/space-MNI/{subject}/"
|
126
|
+
"{subject-padded}_task-{task}_run-{run}_space-MNI152NLin6Asym"
|
127
|
+
"_res-2_desc-preproc_bold.nii.gz"
|
128
|
+
),
|
129
|
+
"space": "MNI152NLin6Asym",
|
130
|
+
},
|
131
|
+
},
|
132
|
+
replacements=["subject", "subject-padded", "task", "run"],
|
133
|
+
) as dg:
|
134
|
+
element = dg[("sub-01", "sub-001", "rest", "1")]
|
135
|
+
element_data = DefaultDataReader().fit_transform(element)
|
136
|
+
bold = element_data["BOLD"]
|
137
|
+
bold_img = bold["data"]
|
138
|
+
# Get tailored coordinates
|
139
|
+
tailored_maps, tailored_labels = get_data(
|
140
|
+
kind="maps", names="Smith_rsn_10", target_data=bold
|
141
|
+
)
|
142
|
+
|
143
|
+
# Check shape with original element data
|
144
|
+
assert tailored_maps.shape[:3] == bold_img.shape[:3]
|
145
|
+
|
146
|
+
# Get raw maps
|
147
|
+
raw_maps, raw_labels, _, _ = load_data(
|
148
|
+
kind="maps", name="Smith_rsn_10", target_space="MNI152NLin6Asym"
|
149
|
+
)
|
150
|
+
# Tailored and raw shape should be same
|
151
|
+
assert tailored_maps.shape[:3] == raw_maps.shape[:3]
|
152
|
+
assert tailored_labels == raw_labels
|
153
|
+
|
154
|
+
|
155
|
+
@pytest.mark.skipif(
|
156
|
+
_check_ants() is False, reason="requires ANTs to be in PATH"
|
157
|
+
)
|
158
|
+
def test_get_different_space() -> None:
|
159
|
+
"""Test tailored maps fetch in different space."""
|
160
|
+
with PartlyCloudyTestingDataGrabber() as dg:
|
161
|
+
element = dg["sub-01"]
|
162
|
+
element_data = DefaultDataReader().fit_transform(element)
|
163
|
+
bold = element_data["BOLD"]
|
164
|
+
bold_img = bold["data"]
|
165
|
+
# Get tailored coordinates
|
166
|
+
tailored_maps, tailored_labels = get_data(
|
167
|
+
kind="maps", names="Smith_rsn_10", target_data=bold
|
168
|
+
)
|
169
|
+
|
170
|
+
# Check shape with original element data
|
171
|
+
assert tailored_maps.shape[:3] == bold_img.shape[:3]
|
172
|
+
|
173
|
+
# Get raw maps
|
174
|
+
raw_maps, raw_labels, _, _ = load_data(
|
175
|
+
kind="maps",
|
176
|
+
name="Smith_rsn_10",
|
177
|
+
target_space="MNI152NLin2009cAsym",
|
178
|
+
)
|
179
|
+
# Tailored and raw should not be same
|
180
|
+
assert tailored_maps.shape[:3] != raw_maps.shape[:3]
|
181
|
+
assert tailored_labels == raw_labels
|
182
|
+
|
183
|
+
|
184
|
+
def test_deregister() -> None:
|
185
|
+
"""Test maps deregistration."""
|
186
|
+
deregister_data(kind="maps", name="testmaps")
|
187
|
+
assert "testmaps" not in list_data(kind="maps")
|
188
|
+
|
189
|
+
|
190
|
+
@pytest.mark.parametrize(
|
191
|
+
"resolution, components, dimension",
|
192
|
+
[
|
193
|
+
(2.0, "rsn", 10),
|
194
|
+
(2.0, "rsn", 20),
|
195
|
+
(2.0, "rsn", 70),
|
196
|
+
(2.0, "bm", 10),
|
197
|
+
(2.0, "bm", 20),
|
198
|
+
(2.0, "bm", 70),
|
199
|
+
],
|
200
|
+
)
|
201
|
+
def test_smith(
|
202
|
+
resolution: float,
|
203
|
+
components: str,
|
204
|
+
dimension: int,
|
205
|
+
) -> None:
|
206
|
+
"""Test Smith maps.
|
207
|
+
|
208
|
+
Parameters
|
209
|
+
----------
|
210
|
+
resolution : float
|
211
|
+
The parametrized resolution values.
|
212
|
+
components : str
|
213
|
+
The parametrized components values.
|
214
|
+
dimension : int
|
215
|
+
The parametrized dimension values.
|
216
|
+
|
217
|
+
"""
|
218
|
+
maps = list_data(kind="maps")
|
219
|
+
maps_name = f"Smith_{components}_{dimension}"
|
220
|
+
assert maps_name in maps
|
221
|
+
|
222
|
+
maps_file = f"{components}{dimension}.nii.gz"
|
223
|
+
# Load maps
|
224
|
+
img, label, img_path, space = load_data(
|
225
|
+
kind="maps",
|
226
|
+
name=maps_name,
|
227
|
+
target_space="MNI152NLin6Asym",
|
228
|
+
resolution=resolution,
|
229
|
+
)
|
230
|
+
assert img is not None
|
231
|
+
assert img_path.name == maps_file
|
232
|
+
assert space == "MNI152NLin6Asym"
|
233
|
+
assert len(label) == dimension
|
234
|
+
assert_array_equal(
|
235
|
+
img.header["pixdim"][1:4],
|
236
|
+
3 * [2.0],
|
237
|
+
)
|
238
|
+
|
239
|
+
|
240
|
+
def test_retrieve_smith_incorrect_components() -> None:
|
241
|
+
"""Test retrieve Smith with incorrect components."""
|
242
|
+
with pytest.raises(ValueError, match="The parameter `components`"):
|
243
|
+
_retrieve_smith(
|
244
|
+
components="abc",
|
245
|
+
dimension=10,
|
246
|
+
)
|
247
|
+
|
248
|
+
|
249
|
+
def test_retrieve_smith_incorrect_dimension() -> None:
|
250
|
+
"""Test retrieve Smith with incorrect dimension."""
|
251
|
+
with pytest.raises(ValueError, match="The parameter `dimension`"):
|
252
|
+
_retrieve_smith(
|
253
|
+
components="rsn",
|
254
|
+
dimension=100,
|
255
|
+
)
|