junifer 0.0.6.dev175__py3-none-any.whl → 0.0.6.dev201__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.dev201.dist-info}/METADATA +1 -1
- {junifer-0.0.6.dev175.dist-info → junifer-0.0.6.dev201.dist-info}/RECORD +76 -62
- {junifer-0.0.6.dev175.dist-info → junifer-0.0.6.dev201.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.dev201.dist-info}/AUTHORS.rst +0 -0
- {junifer-0.0.6.dev175.dist-info → junifer-0.0.6.dev201.dist-info}/LICENSE.md +0 -0
- {junifer-0.0.6.dev175.dist-info → junifer-0.0.6.dev201.dist-info}/entry_points.txt +0 -0
- {junifer-0.0.6.dev175.dist-info → junifer-0.0.6.dev201.dist-info}/top_level.txt +0 -0
@@ -20,15 +20,11 @@ from nilearn.masking import (
|
|
20
20
|
)
|
21
21
|
from numpy.testing import assert_array_almost_equal, assert_array_equal
|
22
22
|
|
23
|
-
from junifer.data
|
24
|
-
|
23
|
+
from junifer.data import MaskRegistry
|
24
|
+
from junifer.data.masks import compute_brain_mask
|
25
|
+
from junifer.data.masks._masks import (
|
25
26
|
_load_ukb_mask,
|
26
27
|
_load_vickery_patil_mask,
|
27
|
-
compute_brain_mask,
|
28
|
-
get_mask,
|
29
|
-
list_masks,
|
30
|
-
load_mask,
|
31
|
-
register_mask,
|
32
28
|
)
|
33
29
|
from junifer.datagrabber import DMCC13Benchmark
|
34
30
|
from junifer.datareader import DefaultDataReader
|
@@ -71,7 +67,7 @@ def test_compute_brain_mask(mask_type: str, threshold: float) -> None:
|
|
71
67
|
extra_input=None,
|
72
68
|
mask_type=mask_type,
|
73
69
|
)
|
74
|
-
assert isinstance(mask, nib.Nifti1Image)
|
70
|
+
assert isinstance(mask, nib.nifti1.Nifti1Image)
|
75
71
|
|
76
72
|
|
77
73
|
@pytest.mark.skipif(
|
@@ -111,13 +107,13 @@ def test_compute_brain_mask_for_native(mask_type: str) -> None:
|
|
111
107
|
extra_input=None,
|
112
108
|
mask_type=mask_type,
|
113
109
|
)
|
114
|
-
assert isinstance(mask, nib.Nifti1Image)
|
110
|
+
assert isinstance(mask, nib.nifti1.Nifti1Image)
|
115
111
|
|
116
112
|
|
117
|
-
def
|
113
|
+
def test_register_built_in_check() -> None:
|
118
114
|
"""Test mask registration check for built-in masks."""
|
119
115
|
with pytest.raises(ValueError, match=r"built-in mask"):
|
120
|
-
|
116
|
+
MaskRegistry().register(
|
121
117
|
name="GM_prob0.2",
|
122
118
|
mask_path="testmask.nii.gz",
|
123
119
|
space="MNI",
|
@@ -125,39 +121,38 @@ def test_register_mask_built_in_check() -> None:
|
|
125
121
|
)
|
126
122
|
|
127
123
|
|
128
|
-
def
|
124
|
+
def test_list_incorrect() -> None:
|
129
125
|
"""Test incorrect information check for list masks."""
|
130
|
-
|
131
|
-
assert "testmask" not in masks
|
126
|
+
assert "testmask" not in MaskRegistry().list
|
132
127
|
|
133
128
|
|
134
|
-
def
|
129
|
+
def test_register_already_registered() -> None:
|
135
130
|
"""Test mask registration check for already registered."""
|
136
131
|
# Register custom mask
|
137
|
-
|
132
|
+
MaskRegistry().register(
|
138
133
|
name="testmask",
|
139
134
|
mask_path="testmask.nii.gz",
|
140
135
|
space="MNI",
|
141
136
|
)
|
142
|
-
out =
|
137
|
+
out = MaskRegistry().load("testmask", path_only=True)
|
143
138
|
assert out[1] is not None
|
144
139
|
assert out[1].name == "testmask.nii.gz"
|
145
140
|
|
146
141
|
# Try registering again
|
147
142
|
with pytest.raises(ValueError, match=r"already registered."):
|
148
|
-
|
143
|
+
MaskRegistry().register(
|
149
144
|
name="testmask",
|
150
145
|
mask_path="testmask.nii.gz",
|
151
146
|
space="MNI",
|
152
147
|
)
|
153
|
-
|
148
|
+
MaskRegistry().register(
|
154
149
|
name="testmask",
|
155
150
|
mask_path="testmask2.nii.gz",
|
156
151
|
space="MNI",
|
157
152
|
overwrite=True,
|
158
153
|
)
|
159
154
|
|
160
|
-
out =
|
155
|
+
out = MaskRegistry().load("testmask", path_only=True)
|
161
156
|
assert out[1] is not None
|
162
157
|
assert out[1].name == "testmask2.nii.gz"
|
163
158
|
|
@@ -170,7 +165,7 @@ def test_register_mask_already_registered() -> None:
|
|
170
165
|
("testmask_3", Path("testmask_3.nii.gz"), "MNI", True),
|
171
166
|
],
|
172
167
|
)
|
173
|
-
def
|
168
|
+
def test_register(
|
174
169
|
name: str,
|
175
170
|
mask_path: str,
|
176
171
|
space: str,
|
@@ -191,17 +186,16 @@ def test_register_mask(
|
|
191
186
|
|
192
187
|
"""
|
193
188
|
# Register custom mask
|
194
|
-
|
189
|
+
MaskRegistry().register(
|
195
190
|
name=name,
|
196
191
|
mask_path=mask_path,
|
197
192
|
space=space,
|
198
193
|
overwrite=overwrite,
|
199
194
|
)
|
200
195
|
# List available mask and check registration
|
201
|
-
|
202
|
-
assert name in masks
|
196
|
+
assert name in MaskRegistry().list
|
203
197
|
# Load registered mask
|
204
|
-
_, fname, mask_space =
|
198
|
+
_, fname, mask_space = MaskRegistry().load(name=name, path_only=True)
|
205
199
|
# Check values for registered mask
|
206
200
|
assert fname is not None
|
207
201
|
assert fname.name == f"{name}.nii.gz"
|
@@ -216,7 +210,7 @@ def test_register_mask(
|
|
216
210
|
"UKB_15K_GM",
|
217
211
|
],
|
218
212
|
)
|
219
|
-
def
|
213
|
+
def test_list_correct(mask_name: str) -> None:
|
220
214
|
"""Test correct information check for list masks.
|
221
215
|
|
222
216
|
Parameters
|
@@ -225,14 +219,13 @@ def test_list_masks_correct(mask_name: str) -> None:
|
|
225
219
|
The parametrized mask name.
|
226
220
|
|
227
221
|
"""
|
228
|
-
|
229
|
-
assert mask_name in masks
|
222
|
+
assert mask_name in MaskRegistry().list
|
230
223
|
|
231
224
|
|
232
|
-
def
|
225
|
+
def test_load_incorrect() -> None:
|
233
226
|
"""Test loading of invalid masks."""
|
234
227
|
with pytest.raises(ValueError, match=r"not found"):
|
235
|
-
|
228
|
+
MaskRegistry().load("wrongmask")
|
236
229
|
|
237
230
|
|
238
231
|
@pytest.mark.parametrize(
|
@@ -278,7 +271,7 @@ def test_vickery_patil(
|
|
278
271
|
The parametrized name of the mask file.
|
279
272
|
|
280
273
|
"""
|
281
|
-
mask, mask_fname, space =
|
274
|
+
mask, mask_fname, space = MaskRegistry().load(name, resolution=resolution)
|
282
275
|
assert_array_almost_equal(
|
283
276
|
mask.header["pixdim"][1:4], pixdim # type: ignore
|
284
277
|
)
|
@@ -295,7 +288,7 @@ def test_vickery_patil_error() -> None:
|
|
295
288
|
|
296
289
|
def test_ukb() -> None:
|
297
290
|
"""Test UKB mask."""
|
298
|
-
mask, mask_fname, space =
|
291
|
+
mask, mask_fname, space = MaskRegistry().load("UKB_15K_GM", resolution=2.0)
|
299
292
|
assert_array_almost_equal(mask.header["pixdim"][1:4], 2.0) # type: ignore
|
300
293
|
assert space == "MNI152NLin6Asym"
|
301
294
|
assert mask_fname is not None
|
@@ -308,18 +301,20 @@ def test_ukb_error() -> None:
|
|
308
301
|
_load_ukb_mask(name="wrong")
|
309
302
|
|
310
303
|
|
311
|
-
def
|
312
|
-
"""Test
|
304
|
+
def test_get() -> None:
|
305
|
+
"""Test tailored mask fetch."""
|
313
306
|
with OasisVBMTestingDataGrabber() as dg:
|
314
307
|
element_data = DefaultDataReader().fit_transform(dg["sub-01"])
|
315
308
|
vbm_gm = element_data["VBM_GM"]
|
316
309
|
vbm_gm_img = vbm_gm["data"]
|
317
|
-
mask =
|
310
|
+
mask = MaskRegistry().get(
|
311
|
+
masks="compute_brain_mask", target_data=vbm_gm
|
312
|
+
)
|
318
313
|
|
319
314
|
assert mask.shape == vbm_gm_img.shape
|
320
315
|
assert_array_equal(mask.affine, vbm_gm_img.affine)
|
321
316
|
|
322
|
-
raw_mask_callable, _, _ =
|
317
|
+
raw_mask_callable, _, _ = MaskRegistry().load(
|
323
318
|
"compute_brain_mask", resolution=1.5
|
324
319
|
)
|
325
320
|
raw_mask_img = raw_mask_callable(vbm_gm) # type: ignore
|
@@ -338,7 +333,7 @@ def test_mask_callable() -> None:
|
|
338
333
|
def ident(x):
|
339
334
|
return x
|
340
335
|
|
341
|
-
|
336
|
+
MaskRegistry()._registry["identity"] = {
|
342
337
|
"family": "Callable",
|
343
338
|
"func": ident,
|
344
339
|
"space": "MNI152Lin",
|
@@ -347,44 +342,48 @@ def test_mask_callable() -> None:
|
|
347
342
|
element_data = DefaultDataReader().fit_transform(dg["sub-01"])
|
348
343
|
vbm_gm = element_data["VBM_GM"]
|
349
344
|
vbm_gm_img = vbm_gm["data"]
|
350
|
-
mask =
|
345
|
+
mask = MaskRegistry().get(masks="identity", target_data=vbm_gm)
|
351
346
|
|
352
347
|
assert_array_equal(mask.get_fdata(), vbm_gm_img.get_fdata())
|
353
348
|
|
354
|
-
del
|
349
|
+
del MaskRegistry()._registry["identity"]
|
355
350
|
|
356
351
|
|
357
|
-
def
|
358
|
-
"""Test passing wrong parameters to
|
352
|
+
def test_get_errors() -> None:
|
353
|
+
"""Test passing wrong parameters to fetch mask."""
|
359
354
|
with OasisVBMTestingDataGrabber() as dg:
|
360
355
|
element_data = DefaultDataReader().fit_transform(dg["sub-01"])
|
361
356
|
vbm_gm = element_data["VBM_GM"]
|
362
357
|
# Test wrong masks definitions (more than one key per dict)
|
363
358
|
with pytest.raises(ValueError, match=r"only one key"):
|
364
|
-
|
359
|
+
MaskRegistry().get(
|
360
|
+
masks={"GM_prob0.2": {}, "Other": {}}, target_data=vbm_gm
|
361
|
+
)
|
365
362
|
|
366
363
|
# Test wrong masks definitions (pass paramaeters to non-callable mask)
|
367
364
|
with pytest.raises(ValueError, match=r"callable params"):
|
368
|
-
|
365
|
+
MaskRegistry().get(
|
366
|
+
masks={"GM_prob0.2": {"param": 1}}, target_data=vbm_gm
|
367
|
+
)
|
369
368
|
|
370
369
|
# Pass only parameters to the intersection function
|
371
370
|
with pytest.raises(
|
372
371
|
ValueError, match=r" At least one mask is required."
|
373
372
|
):
|
374
|
-
|
373
|
+
MaskRegistry().get(masks={"threshold": 1}, target_data=vbm_gm)
|
375
374
|
|
376
375
|
# Pass parameters to the intersection function when only one mask
|
377
376
|
with pytest.raises(
|
378
377
|
ValueError, match=r"parameters to the intersection"
|
379
378
|
):
|
380
|
-
|
379
|
+
MaskRegistry().get(
|
381
380
|
masks=["compute_brain_mask", {"threshold": 1}],
|
382
381
|
target_data=vbm_gm,
|
383
382
|
)
|
384
383
|
|
385
384
|
# Test "inherited" masks error
|
386
385
|
with pytest.raises(ValueError, match=r"provide `mask`"):
|
387
|
-
|
386
|
+
MaskRegistry().get(masks="inherit", target_data=vbm_gm)
|
388
387
|
|
389
388
|
|
390
389
|
@pytest.mark.parametrize(
|
@@ -425,7 +424,7 @@ def test_nilearn_compute_masks(
|
|
425
424
|
else:
|
426
425
|
mask_spec = {mask_name: params}
|
427
426
|
|
428
|
-
mask =
|
427
|
+
mask = MaskRegistry().get(masks=mask_spec, target_data=bold)
|
429
428
|
|
430
429
|
assert_array_equal(mask.affine, bold_img.affine)
|
431
430
|
|
@@ -443,15 +442,15 @@ def test_nilearn_compute_masks(
|
|
443
442
|
assert_array_equal(mask.get_fdata(), ni_mask.get_fdata())
|
444
443
|
|
445
444
|
|
446
|
-
def
|
447
|
-
"""Test using the inherit mask functionality."""
|
445
|
+
def test_get_inherit() -> None:
|
446
|
+
"""Test mask fetch using the inherit mask functionality."""
|
448
447
|
with SPMAuditoryTestingDataGrabber() as dg:
|
449
448
|
element_data = DefaultDataReader().fit_transform(dg["sub001"])
|
450
449
|
# Compute brain mask using nilearn
|
451
450
|
gm_mask = compute_brain_mask(element_data["BOLD"], threshold=0.2)
|
452
451
|
|
453
452
|
# Get mask using the compute_brain_mask function
|
454
|
-
mask1 =
|
453
|
+
mask1 = MaskRegistry().get(
|
455
454
|
masks={"compute_brain_mask": {"threshold": 0.2}},
|
456
455
|
target_data=element_data["BOLD"],
|
457
456
|
)
|
@@ -463,7 +462,7 @@ def test_get_mask_inherit() -> None:
|
|
463
462
|
"data": gm_mask,
|
464
463
|
"space": element_data["BOLD"]["space"],
|
465
464
|
}
|
466
|
-
mask2 =
|
465
|
+
mask2 = MaskRegistry().get(
|
467
466
|
masks="inherit",
|
468
467
|
target_data=bold_dict,
|
469
468
|
)
|
@@ -479,7 +478,7 @@ def test_get_mask_inherit() -> None:
|
|
479
478
|
(["compute_brain_mask", "compute_epi_mask"], {}),
|
480
479
|
],
|
481
480
|
)
|
482
|
-
def
|
481
|
+
def test_get_multiple(
|
483
482
|
masks: Union[str, Dict, List[Union[Dict, str]]], params: Dict
|
484
483
|
) -> None:
|
485
484
|
"""Test getting multiple masks.
|
@@ -505,7 +504,7 @@ def test_get_mask_multiple(
|
|
505
504
|
target_img = element_data["BOLD"]["data"]
|
506
505
|
resolution = np.min(target_img.header.get_zooms()[:3])
|
507
506
|
|
508
|
-
computed =
|
507
|
+
computed = MaskRegistry().get(
|
509
508
|
masks=junifer_masks, target_data=element_data["BOLD"]
|
510
509
|
)
|
511
510
|
|
@@ -516,16 +515,18 @@ def test_get_mask_multiple(
|
|
516
515
|
mask_funcs = [
|
517
516
|
x
|
518
517
|
for x in masks_names
|
519
|
-
if
|
518
|
+
if MaskRegistry()._registry[x]["family"] == "Callable"
|
520
519
|
]
|
521
520
|
mask_files = [
|
522
521
|
x
|
523
522
|
for x in masks_names
|
524
|
-
if
|
523
|
+
if MaskRegistry()._registry[x]["family"] != "Callable"
|
525
524
|
]
|
526
525
|
|
527
526
|
mask_imgs = [
|
528
|
-
|
527
|
+
MaskRegistry().load(
|
528
|
+
t_mask, path_only=False, resolution=resolution
|
529
|
+
)[0]
|
529
530
|
for t_mask in mask_files
|
530
531
|
]
|
531
532
|
|
@@ -533,10 +534,14 @@ def test_get_mask_multiple(
|
|
533
534
|
# Bypass for custom mask
|
534
535
|
if t_func == "compute_brain_mask":
|
535
536
|
mask_imgs.append(
|
536
|
-
|
537
|
+
MaskRegistry()._registry[t_func]["func"](
|
538
|
+
element_data["BOLD"]
|
539
|
+
)
|
537
540
|
)
|
538
541
|
else:
|
539
|
-
mask_imgs.append(
|
542
|
+
mask_imgs.append(
|
543
|
+
MaskRegistry()._registry[t_func]["func"](target_img)
|
544
|
+
)
|
540
545
|
|
541
546
|
mask_imgs = [
|
542
547
|
resample_to_img(
|
@@ -0,0 +1,154 @@
|
|
1
|
+
"""Provide class for parcellation space warping via ANTs."""
|
2
|
+
|
3
|
+
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
|
4
|
+
# License: AGPL
|
5
|
+
|
6
|
+
import uuid
|
7
|
+
from typing import TYPE_CHECKING, Any, Dict, Optional
|
8
|
+
|
9
|
+
import nibabel as nib
|
10
|
+
|
11
|
+
from ...pipeline import WorkDirManager
|
12
|
+
from ...utils import logger, run_ext_cmd
|
13
|
+
from ..template_spaces import get_template, get_xfm
|
14
|
+
|
15
|
+
|
16
|
+
if TYPE_CHECKING:
|
17
|
+
from nibabel.nifti1 import Nifti1Image
|
18
|
+
|
19
|
+
|
20
|
+
__all__ = ["ANTsParcellationWarper"]
|
21
|
+
|
22
|
+
|
23
|
+
class ANTsParcellationWarper:
|
24
|
+
"""Class for parcellation space warping via ANTs.
|
25
|
+
|
26
|
+
This class uses ANTs ``antsApplyTransforms`` for transformation.
|
27
|
+
|
28
|
+
"""
|
29
|
+
|
30
|
+
def warp(
|
31
|
+
self,
|
32
|
+
parcellation_name: str,
|
33
|
+
parcellation_img: "Nifti1Image",
|
34
|
+
src: str,
|
35
|
+
dst: str,
|
36
|
+
target_data: Dict[str, Any],
|
37
|
+
extra_input: Optional[Dict[str, Any]] = None,
|
38
|
+
) -> "Nifti1Image":
|
39
|
+
"""Warp ``parcellation_img`` to correct space.
|
40
|
+
|
41
|
+
Parameters
|
42
|
+
----------
|
43
|
+
parcellation_name : str
|
44
|
+
The name of the parcellation.
|
45
|
+
parcellation_img : nibabel.nifti1.Nifti1Image
|
46
|
+
The parcellation image to transform.
|
47
|
+
src : str
|
48
|
+
The data type or template space to warp from.
|
49
|
+
It should be empty string if ``dst="T1w"``.
|
50
|
+
dst : str
|
51
|
+
The data type or template space to warp to.
|
52
|
+
`"T1w"` is the only allowed data type and it uses the resampled T1w
|
53
|
+
found in ``target_data.reference_path``. The ``"reference_path"``
|
54
|
+
key is added when :class:`.SpaceWarper` is used.
|
55
|
+
target_data : dict
|
56
|
+
The corresponding item of the data object to which the parcellation
|
57
|
+
will be applied.
|
58
|
+
extra_input : dict, optional
|
59
|
+
The other fields in the data object. Useful for accessing other
|
60
|
+
data kinds that needs to be used in the computation of parcellation
|
61
|
+
(default None).
|
62
|
+
|
63
|
+
|
64
|
+
Returns
|
65
|
+
-------
|
66
|
+
nibabel.nifti1.Nifti1Image
|
67
|
+
The transformed parcellation image.
|
68
|
+
|
69
|
+
"""
|
70
|
+
# Create element-scoped tempdir so that warped parcellation is
|
71
|
+
# available later as nibabel stores file path reference for
|
72
|
+
# loading on computation
|
73
|
+
prefix = (
|
74
|
+
f"ants_parcellation_warper_{parcellation_name}"
|
75
|
+
f"{'' if not src else f'_from_{src}'}_to_{dst}_"
|
76
|
+
f"{uuid.uuid1()}"
|
77
|
+
)
|
78
|
+
element_tempdir = WorkDirManager().get_element_tempdir(
|
79
|
+
prefix=prefix,
|
80
|
+
)
|
81
|
+
|
82
|
+
# Native space warping
|
83
|
+
if dst == "T1w":
|
84
|
+
logger.debug("Using ANTs for parcellation transformation")
|
85
|
+
|
86
|
+
# Save existing parcellation image to a tempfile
|
87
|
+
prewarp_parcellation_path = (
|
88
|
+
element_tempdir / "prewarp_parcellation.nii.gz"
|
89
|
+
)
|
90
|
+
nib.save(parcellation_img, prewarp_parcellation_path)
|
91
|
+
|
92
|
+
# Create a tempfile for warped output
|
93
|
+
warped_parcellation_path = (
|
94
|
+
element_tempdir / "parcellation_warped.nii.gz"
|
95
|
+
)
|
96
|
+
# Set antsApplyTransforms command
|
97
|
+
apply_transforms_cmd = [
|
98
|
+
"antsApplyTransforms",
|
99
|
+
"-d 3",
|
100
|
+
"-e 3",
|
101
|
+
"-n 'GenericLabel[NearestNeighbor]'",
|
102
|
+
f"-i {prewarp_parcellation_path.resolve()}",
|
103
|
+
# use resampled reference
|
104
|
+
f"-r {target_data['reference_path'].resolve()}",
|
105
|
+
f"-t {extra_input['Warp']['path'].resolve()}",
|
106
|
+
f"-o {warped_parcellation_path.resolve()}",
|
107
|
+
]
|
108
|
+
# Call antsApplyTransforms
|
109
|
+
run_ext_cmd(name="antsApplyTransforms", cmd=apply_transforms_cmd)
|
110
|
+
|
111
|
+
# Template space warping
|
112
|
+
else:
|
113
|
+
logger.debug(
|
114
|
+
f"Using ANTs to warp parcellation from {src} to {dst}"
|
115
|
+
)
|
116
|
+
|
117
|
+
# Get xfm file
|
118
|
+
xfm_file_path = get_xfm(src=src, dst=dst)
|
119
|
+
# Get template space image
|
120
|
+
template_space_img = get_template(
|
121
|
+
space=dst,
|
122
|
+
target_data=target_data,
|
123
|
+
extra_input=None,
|
124
|
+
)
|
125
|
+
# Save template to a tempfile
|
126
|
+
template_space_img_path = element_tempdir / f"{dst}_T1w.nii.gz"
|
127
|
+
nib.save(template_space_img, template_space_img_path)
|
128
|
+
|
129
|
+
# Save existing parcellation image to a tempfile
|
130
|
+
prewarp_parcellation_path = (
|
131
|
+
element_tempdir / "prewarp_parcellation.nii.gz"
|
132
|
+
)
|
133
|
+
nib.save(parcellation_img, prewarp_parcellation_path)
|
134
|
+
|
135
|
+
# Create a tempfile for warped output
|
136
|
+
warped_parcellation_path = (
|
137
|
+
element_tempdir / "parcellation_warped.nii.gz"
|
138
|
+
)
|
139
|
+
# Set antsApplyTransforms command
|
140
|
+
apply_transforms_cmd = [
|
141
|
+
"antsApplyTransforms",
|
142
|
+
"-d 3",
|
143
|
+
"-e 3",
|
144
|
+
"-n 'GenericLabel[NearestNeighbor]'",
|
145
|
+
f"-i {prewarp_parcellation_path.resolve()}",
|
146
|
+
f"-r {template_space_img_path.resolve()}",
|
147
|
+
f"-t {xfm_file_path.resolve()}",
|
148
|
+
f"-o {warped_parcellation_path.resolve()}",
|
149
|
+
]
|
150
|
+
# Call antsApplyTransforms
|
151
|
+
run_ext_cmd(name="antsApplyTransforms", cmd=apply_transforms_cmd)
|
152
|
+
|
153
|
+
# Load nifti
|
154
|
+
return nib.load(warped_parcellation_path)
|
@@ -0,0 +1,91 @@
|
|
1
|
+
"""Provide class for parcellation space warping via FSL FLIRT."""
|
2
|
+
|
3
|
+
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
|
4
|
+
# License: AGPL
|
5
|
+
|
6
|
+
import uuid
|
7
|
+
from typing import TYPE_CHECKING, Any, Dict
|
8
|
+
|
9
|
+
import nibabel as nib
|
10
|
+
|
11
|
+
from ...pipeline import WorkDirManager
|
12
|
+
from ...utils import logger, run_ext_cmd
|
13
|
+
|
14
|
+
|
15
|
+
if TYPE_CHECKING:
|
16
|
+
from nibabel.nifti1 import Nifti1Image
|
17
|
+
|
18
|
+
|
19
|
+
__all__ = ["FSLParcellationWarper"]
|
20
|
+
|
21
|
+
|
22
|
+
class FSLParcellationWarper:
|
23
|
+
"""Class for parcellation space warping via FSL FLIRT.
|
24
|
+
|
25
|
+
This class uses FSL FLIRT's ``applywarp`` for transformation.
|
26
|
+
|
27
|
+
"""
|
28
|
+
|
29
|
+
def warp(
|
30
|
+
self,
|
31
|
+
parcellation_name: str,
|
32
|
+
parcellation_img: "Nifti1Image",
|
33
|
+
target_data: Dict[str, Any],
|
34
|
+
extra_input: Dict[str, Any],
|
35
|
+
) -> "Nifti1Image":
|
36
|
+
"""Warp ``parcellation_img`` to correct space.
|
37
|
+
|
38
|
+
Parameters
|
39
|
+
----------
|
40
|
+
parcellation_name : str
|
41
|
+
The name of the parcellation.
|
42
|
+
parcellation_img : nibabel.nifti1.Nifti1Image
|
43
|
+
The parcellation image to transform.
|
44
|
+
target_data : dict
|
45
|
+
The corresponding item of the data object to which the parcellation
|
46
|
+
will be applied.
|
47
|
+
extra_input : dict, optional
|
48
|
+
The other fields in the data object. Useful for accessing other
|
49
|
+
data kinds that needs to be used in the computation of parcellation
|
50
|
+
(default None).
|
51
|
+
|
52
|
+
Returns
|
53
|
+
-------
|
54
|
+
nibabel.nifti1.Nifti1Image
|
55
|
+
The transformed parcellation image.
|
56
|
+
|
57
|
+
"""
|
58
|
+
logger.debug("Using FSL for parcellation transformation")
|
59
|
+
|
60
|
+
# Create element-scoped tempdir so that warped parcellation is
|
61
|
+
# available later as nibabel stores file path reference for
|
62
|
+
# loading on computation
|
63
|
+
element_tempdir = WorkDirManager().get_element_tempdir(
|
64
|
+
prefix=f"fsl_parcellation_warper_{parcellation_name}_{uuid.uuid1()}"
|
65
|
+
)
|
66
|
+
|
67
|
+
# Save existing parcellation image to a tempfile
|
68
|
+
prewarp_parcellation_path = (
|
69
|
+
element_tempdir / "prewarp_parcellation.nii.gz"
|
70
|
+
)
|
71
|
+
nib.save(parcellation_img, prewarp_parcellation_path)
|
72
|
+
|
73
|
+
# Create a tempfile for warped output
|
74
|
+
warped_parcellation_path = (
|
75
|
+
element_tempdir / "parcellation_warped.nii.gz"
|
76
|
+
)
|
77
|
+
# Set applywarp command
|
78
|
+
applywarp_cmd = [
|
79
|
+
"applywarp",
|
80
|
+
"--interp=nn",
|
81
|
+
f"-i {prewarp_parcellation_path.resolve()}",
|
82
|
+
# use resampled reference
|
83
|
+
f"-r {target_data['reference_path'].resolve()}",
|
84
|
+
f"-w {extra_input['Warp']['path'].resolve()}",
|
85
|
+
f"-o {warped_parcellation_path.resolve()}",
|
86
|
+
]
|
87
|
+
# Call applywarp
|
88
|
+
run_ext_cmd(name="applywarp", cmd=applywarp_cmd)
|
89
|
+
|
90
|
+
# Load nifti
|
91
|
+
return nib.load(warped_parcellation_path)
|