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.
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.dev201.dist-info}/METADATA +1 -1
  52. {junifer-0.0.6.dev175.dist-info → junifer-0.0.6.dev201.dist-info}/RECORD +76 -62
  53. {junifer-0.0.6.dev175.dist-info → junifer-0.0.6.dev201.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.dev201.dist-info}/AUTHORS.rst +0 -0
  76. {junifer-0.0.6.dev175.dist-info → junifer-0.0.6.dev201.dist-info}/LICENSE.md +0 -0
  77. {junifer-0.0.6.dev175.dist-info → junifer-0.0.6.dev201.dist-info}/entry_points.txt +0 -0
  78. {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.masks import (
24
- _available_masks,
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 test_register_mask_built_in_check() -> None:
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
- register_mask(
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 test_list_masks_incorrect() -> None:
124
+ def test_list_incorrect() -> None:
129
125
  """Test incorrect information check for list masks."""
130
- masks = list_masks()
131
- assert "testmask" not in masks
126
+ assert "testmask" not in MaskRegistry().list
132
127
 
133
128
 
134
- def test_register_mask_already_registered() -> None:
129
+ def test_register_already_registered() -> None:
135
130
  """Test mask registration check for already registered."""
136
131
  # Register custom mask
137
- register_mask(
132
+ MaskRegistry().register(
138
133
  name="testmask",
139
134
  mask_path="testmask.nii.gz",
140
135
  space="MNI",
141
136
  )
142
- out = load_mask("testmask", path_only=True)
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
- register_mask(
143
+ MaskRegistry().register(
149
144
  name="testmask",
150
145
  mask_path="testmask.nii.gz",
151
146
  space="MNI",
152
147
  )
153
- register_mask(
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 = load_mask("testmask", path_only=True)
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 test_register_mask(
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
- register_mask(
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
- masks = list_masks()
202
- assert name in masks
196
+ assert name in MaskRegistry().list
203
197
  # Load registered mask
204
- _, fname, mask_space = load_mask(name=name, path_only=True)
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 test_list_masks_correct(mask_name: str) -> None:
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
- masks = list_masks()
229
- assert mask_name in masks
222
+ assert mask_name in MaskRegistry().list
230
223
 
231
224
 
232
- def test_load_mask_incorrect() -> None:
225
+ def test_load_incorrect() -> None:
233
226
  """Test loading of invalid masks."""
234
227
  with pytest.raises(ValueError, match=r"not found"):
235
- load_mask("wrongmask")
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 = load_mask(name, resolution=resolution)
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 = load_mask("UKB_15K_GM", resolution=2.0)
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 test_get_mask() -> None:
312
- """Test the get_mask function."""
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 = get_mask(masks="compute_brain_mask", target_data=vbm_gm)
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, _, _ = load_mask(
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
- _available_masks["identity"] = {
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 = get_mask(masks="identity", target_data=vbm_gm)
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 _available_masks["identity"]
349
+ del MaskRegistry()._registry["identity"]
355
350
 
356
351
 
357
- def test_get_mask_errors() -> None:
358
- """Test passing wrong parameters to get_mask."""
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
- get_mask(masks={"GM_prob0.2": {}, "Other": {}}, target_data=vbm_gm)
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
- get_mask(masks={"GM_prob0.2": {"param": 1}}, target_data=vbm_gm)
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
- get_mask(masks={"threshold": 1}, target_data=vbm_gm)
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
- get_mask(
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
- get_mask(masks="inherit", target_data=vbm_gm)
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 = get_mask(masks=mask_spec, target_data=bold)
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 test_get_mask_inherit() -> None:
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 = get_mask(
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 = get_mask(
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 test_get_mask_multiple(
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 = get_mask(
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 _available_masks[x]["family"] == "Callable"
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 _available_masks[x]["family"] != "Callable"
523
+ if MaskRegistry()._registry[x]["family"] != "Callable"
525
524
  ]
526
525
 
527
526
  mask_imgs = [
528
- load_mask(t_mask, path_only=False, resolution=resolution)[0]
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
- _available_masks[t_func]["func"](element_data["BOLD"])
537
+ MaskRegistry()._registry[t_func]["func"](
538
+ element_data["BOLD"]
539
+ )
537
540
  )
538
541
  else:
539
- mask_imgs.append(_available_masks[t_func]["func"](target_img))
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,9 @@
1
+ """Parcellations."""
2
+
3
+ # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
+ # License: AGPL
5
+
6
+ import lazy_loader as lazy
7
+
8
+
9
+ __getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__)
@@ -0,0 +1,6 @@
1
+ __all__ = [
2
+ "ParcellationRegistry",
3
+ "merge_parcellations",
4
+ ]
5
+
6
+ from ._parcellations import ParcellationRegistry, merge_parcellations
@@ -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)