junifer 0.0.3.dev188__py3-none-any.whl → 0.0.4__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 +14 -2
- junifer/api/cli.py +162 -17
- junifer/api/functions.py +87 -419
- junifer/api/parser.py +24 -0
- junifer/api/queue_context/__init__.py +8 -0
- junifer/api/queue_context/gnu_parallel_local_adapter.py +258 -0
- junifer/api/queue_context/htcondor_adapter.py +365 -0
- junifer/api/queue_context/queue_context_adapter.py +60 -0
- junifer/api/queue_context/tests/test_gnu_parallel_local_adapter.py +192 -0
- junifer/api/queue_context/tests/test_htcondor_adapter.py +257 -0
- junifer/api/res/afni/run_afni_docker.sh +6 -6
- junifer/api/res/ants/ResampleImage +3 -0
- junifer/api/res/ants/antsApplyTransforms +3 -0
- junifer/api/res/ants/antsApplyTransformsToPoints +3 -0
- junifer/api/res/ants/run_ants_docker.sh +39 -0
- junifer/api/res/fsl/applywarp +3 -0
- junifer/api/res/fsl/flirt +3 -0
- junifer/api/res/fsl/img2imgcoord +3 -0
- junifer/api/res/fsl/run_fsl_docker.sh +39 -0
- junifer/api/res/fsl/std2imgcoord +3 -0
- junifer/api/res/run_conda.sh +4 -4
- junifer/api/res/run_venv.sh +22 -0
- junifer/api/tests/data/partly_cloudy_agg_mean_tian.yml +16 -0
- junifer/api/tests/test_api_utils.py +21 -3
- junifer/api/tests/test_cli.py +232 -9
- junifer/api/tests/test_functions.py +211 -439
- junifer/api/tests/test_parser.py +1 -1
- junifer/configs/juseless/datagrabbers/aomic_id1000_vbm.py +6 -1
- junifer/configs/juseless/datagrabbers/camcan_vbm.py +6 -1
- junifer/configs/juseless/datagrabbers/ixi_vbm.py +6 -1
- junifer/configs/juseless/datagrabbers/tests/test_ucla.py +8 -8
- junifer/configs/juseless/datagrabbers/ucla.py +44 -26
- junifer/configs/juseless/datagrabbers/ukb_vbm.py +6 -1
- junifer/data/VOIs/meta/AutobiographicalMemory_VOIs.txt +23 -0
- junifer/data/VOIs/meta/Power2013_MNI_VOIs.tsv +264 -0
- junifer/data/__init__.py +4 -0
- junifer/data/coordinates.py +298 -31
- junifer/data/masks.py +360 -28
- junifer/data/parcellations.py +621 -188
- junifer/data/template_spaces.py +190 -0
- junifer/data/tests/test_coordinates.py +34 -3
- junifer/data/tests/test_data_utils.py +1 -0
- junifer/data/tests/test_masks.py +202 -86
- junifer/data/tests/test_parcellations.py +266 -55
- junifer/data/tests/test_template_spaces.py +104 -0
- junifer/data/utils.py +4 -2
- junifer/datagrabber/__init__.py +1 -0
- junifer/datagrabber/aomic/id1000.py +111 -70
- junifer/datagrabber/aomic/piop1.py +116 -53
- junifer/datagrabber/aomic/piop2.py +116 -53
- junifer/datagrabber/aomic/tests/test_id1000.py +27 -27
- junifer/datagrabber/aomic/tests/test_piop1.py +27 -27
- junifer/datagrabber/aomic/tests/test_piop2.py +27 -27
- junifer/datagrabber/base.py +62 -10
- junifer/datagrabber/datalad_base.py +0 -2
- junifer/datagrabber/dmcc13_benchmark.py +372 -0
- junifer/datagrabber/hcp1200/datalad_hcp1200.py +5 -0
- junifer/datagrabber/hcp1200/hcp1200.py +30 -13
- junifer/datagrabber/pattern.py +133 -27
- junifer/datagrabber/pattern_datalad.py +111 -13
- junifer/datagrabber/tests/test_base.py +57 -6
- junifer/datagrabber/tests/test_datagrabber_utils.py +204 -76
- junifer/datagrabber/tests/test_datalad_base.py +0 -6
- junifer/datagrabber/tests/test_dmcc13_benchmark.py +256 -0
- junifer/datagrabber/tests/test_multiple.py +43 -10
- junifer/datagrabber/tests/test_pattern.py +125 -178
- junifer/datagrabber/tests/test_pattern_datalad.py +44 -25
- junifer/datagrabber/utils.py +151 -16
- junifer/datareader/default.py +36 -10
- junifer/external/nilearn/junifer_nifti_spheres_masker.py +6 -0
- junifer/markers/base.py +25 -16
- junifer/markers/collection.py +35 -16
- junifer/markers/complexity/__init__.py +27 -0
- junifer/markers/complexity/complexity_base.py +149 -0
- junifer/markers/complexity/hurst_exponent.py +136 -0
- junifer/markers/complexity/multiscale_entropy_auc.py +140 -0
- junifer/markers/complexity/perm_entropy.py +132 -0
- junifer/markers/complexity/range_entropy.py +136 -0
- junifer/markers/complexity/range_entropy_auc.py +145 -0
- junifer/markers/complexity/sample_entropy.py +134 -0
- junifer/markers/complexity/tests/test_complexity_base.py +19 -0
- junifer/markers/complexity/tests/test_hurst_exponent.py +69 -0
- junifer/markers/complexity/tests/test_multiscale_entropy_auc.py +68 -0
- junifer/markers/complexity/tests/test_perm_entropy.py +68 -0
- junifer/markers/complexity/tests/test_range_entropy.py +69 -0
- junifer/markers/complexity/tests/test_range_entropy_auc.py +69 -0
- junifer/markers/complexity/tests/test_sample_entropy.py +68 -0
- junifer/markers/complexity/tests/test_weighted_perm_entropy.py +68 -0
- junifer/markers/complexity/weighted_perm_entropy.py +133 -0
- junifer/markers/falff/_afni_falff.py +153 -0
- junifer/markers/falff/_junifer_falff.py +142 -0
- junifer/markers/falff/falff_base.py +91 -84
- junifer/markers/falff/falff_parcels.py +61 -45
- junifer/markers/falff/falff_spheres.py +64 -48
- junifer/markers/falff/tests/test_falff_parcels.py +89 -121
- junifer/markers/falff/tests/test_falff_spheres.py +92 -127
- junifer/markers/functional_connectivity/crossparcellation_functional_connectivity.py +1 -0
- junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +1 -0
- junifer/markers/functional_connectivity/functional_connectivity_base.py +1 -0
- junifer/markers/functional_connectivity/tests/test_crossparcellation_functional_connectivity.py +46 -44
- junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_parcels.py +34 -39
- junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_spheres.py +40 -52
- junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +62 -70
- junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +99 -85
- junifer/markers/parcel_aggregation.py +60 -38
- junifer/markers/reho/_afni_reho.py +192 -0
- junifer/markers/reho/_junifer_reho.py +281 -0
- junifer/markers/reho/reho_base.py +69 -34
- junifer/markers/reho/reho_parcels.py +26 -16
- junifer/markers/reho/reho_spheres.py +23 -9
- junifer/markers/reho/tests/test_reho_parcels.py +93 -92
- junifer/markers/reho/tests/test_reho_spheres.py +88 -86
- junifer/markers/sphere_aggregation.py +54 -9
- junifer/markers/temporal_snr/temporal_snr_base.py +1 -0
- junifer/markers/temporal_snr/tests/test_temporal_snr_parcels.py +38 -37
- junifer/markers/temporal_snr/tests/test_temporal_snr_spheres.py +34 -38
- junifer/markers/tests/test_collection.py +43 -42
- junifer/markers/tests/test_ets_rss.py +29 -37
- junifer/markers/tests/test_parcel_aggregation.py +587 -468
- junifer/markers/tests/test_sphere_aggregation.py +209 -157
- junifer/markers/utils.py +2 -40
- junifer/onthefly/read_transform.py +13 -6
- junifer/pipeline/__init__.py +1 -0
- junifer/pipeline/pipeline_step_mixin.py +105 -41
- junifer/pipeline/registry.py +17 -0
- junifer/pipeline/singleton.py +45 -0
- junifer/pipeline/tests/test_pipeline_step_mixin.py +139 -51
- junifer/pipeline/tests/test_update_meta_mixin.py +1 -0
- junifer/pipeline/tests/test_workdir_manager.py +104 -0
- junifer/pipeline/update_meta_mixin.py +8 -2
- junifer/pipeline/utils.py +154 -15
- junifer/pipeline/workdir_manager.py +246 -0
- junifer/preprocess/__init__.py +3 -0
- junifer/preprocess/ants/__init__.py +4 -0
- junifer/preprocess/ants/ants_apply_transforms_warper.py +185 -0
- junifer/preprocess/ants/tests/test_ants_apply_transforms_warper.py +56 -0
- junifer/preprocess/base.py +96 -69
- junifer/preprocess/bold_warper.py +265 -0
- junifer/preprocess/confounds/fmriprep_confound_remover.py +91 -134
- junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py +106 -111
- junifer/preprocess/fsl/__init__.py +4 -0
- junifer/preprocess/fsl/apply_warper.py +179 -0
- junifer/preprocess/fsl/tests/test_apply_warper.py +45 -0
- junifer/preprocess/tests/test_bold_warper.py +159 -0
- junifer/preprocess/tests/test_preprocess_base.py +6 -6
- junifer/preprocess/warping/__init__.py +6 -0
- junifer/preprocess/warping/_ants_warper.py +167 -0
- junifer/preprocess/warping/_fsl_warper.py +109 -0
- junifer/preprocess/warping/space_warper.py +213 -0
- junifer/preprocess/warping/tests/test_space_warper.py +198 -0
- junifer/stats.py +18 -4
- junifer/storage/base.py +9 -1
- junifer/storage/hdf5.py +8 -3
- junifer/storage/pandas_base.py +2 -1
- junifer/storage/sqlite.py +1 -0
- junifer/storage/tests/test_hdf5.py +2 -1
- junifer/storage/tests/test_sqlite.py +8 -8
- junifer/storage/tests/test_utils.py +6 -6
- junifer/storage/utils.py +1 -0
- junifer/testing/datagrabbers.py +11 -7
- junifer/testing/utils.py +1 -0
- junifer/tests/test_stats.py +2 -0
- junifer/utils/__init__.py +1 -0
- junifer/utils/helpers.py +53 -0
- junifer/utils/logging.py +14 -3
- junifer/utils/tests/test_helpers.py +35 -0
- {junifer-0.0.3.dev188.dist-info → junifer-0.0.4.dist-info}/METADATA +59 -28
- junifer-0.0.4.dist-info/RECORD +257 -0
- {junifer-0.0.3.dev188.dist-info → junifer-0.0.4.dist-info}/WHEEL +1 -1
- junifer/markers/falff/falff_estimator.py +0 -334
- junifer/markers/falff/tests/test_falff_estimator.py +0 -238
- junifer/markers/reho/reho_estimator.py +0 -515
- junifer/markers/reho/tests/test_reho_estimator.py +0 -260
- junifer-0.0.3.dev188.dist-info/RECORD +0 -199
- {junifer-0.0.3.dev188.dist-info → junifer-0.0.4.dist-info}/AUTHORS.rst +0 -0
- {junifer-0.0.3.dev188.dist-info → junifer-0.0.4.dist-info}/LICENSE.md +0 -0
- {junifer-0.0.3.dev188.dist-info → junifer-0.0.4.dist-info}/entry_points.txt +0 -0
- {junifer-0.0.3.dev188.dist-info → junifer-0.0.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,159 @@
|
|
1
|
+
"""Provide tests for BOLDWarper."""
|
2
|
+
|
3
|
+
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
|
4
|
+
# License: AGPL
|
5
|
+
|
6
|
+
import socket
|
7
|
+
from typing import TYPE_CHECKING, Tuple
|
8
|
+
|
9
|
+
import pytest
|
10
|
+
from numpy.testing import assert_array_equal, assert_raises
|
11
|
+
|
12
|
+
from junifer.datagrabber import DataladHCP1200, DMCC13Benchmark
|
13
|
+
from junifer.datareader import DefaultDataReader
|
14
|
+
from junifer.pipeline.utils import _check_ants, _check_fsl
|
15
|
+
from junifer.preprocess import BOLDWarper
|
16
|
+
|
17
|
+
|
18
|
+
if TYPE_CHECKING:
|
19
|
+
from junifer.datagrabber import BaseDataGrabber
|
20
|
+
|
21
|
+
|
22
|
+
def test_BOLDWarper_init() -> None:
|
23
|
+
"""Test BOLDWarper init."""
|
24
|
+
bold_warper = BOLDWarper(using="ants", reference="T1w")
|
25
|
+
assert bold_warper._on == ["BOLD"]
|
26
|
+
|
27
|
+
|
28
|
+
def test_BOLDWarper_get_valid_inputs() -> None:
|
29
|
+
"""Test BOLDWarper get_valid_inputs."""
|
30
|
+
bold_warper = BOLDWarper(using="ants", reference="T1w")
|
31
|
+
assert bold_warper.get_valid_inputs() == ["BOLD"]
|
32
|
+
|
33
|
+
|
34
|
+
def test_BOLDWarper_get_output_type() -> None:
|
35
|
+
"""Test BOLDWarper get_output_type."""
|
36
|
+
bold_warper = BOLDWarper(using="ants", reference="T1w")
|
37
|
+
assert bold_warper.get_output_type("BOLD") == "BOLD"
|
38
|
+
|
39
|
+
|
40
|
+
@pytest.mark.parametrize(
|
41
|
+
"datagrabber, element",
|
42
|
+
[
|
43
|
+
[
|
44
|
+
DMCC13Benchmark(
|
45
|
+
types=["BOLD", "T1w", "Warp"],
|
46
|
+
sessions=["ses-wave1bas"],
|
47
|
+
tasks=["Rest"],
|
48
|
+
phase_encodings=["AP"],
|
49
|
+
runs=["1"],
|
50
|
+
native_t1w=True,
|
51
|
+
),
|
52
|
+
("sub-f9057kp", "ses-wave1bas", "Rest", "AP", "1"),
|
53
|
+
],
|
54
|
+
[
|
55
|
+
DataladHCP1200(
|
56
|
+
tasks=["REST1"],
|
57
|
+
phase_encodings=["LR"],
|
58
|
+
ica_fix=True,
|
59
|
+
),
|
60
|
+
("100206", "REST1", "LR"),
|
61
|
+
],
|
62
|
+
],
|
63
|
+
)
|
64
|
+
@pytest.mark.skipif(_check_fsl() is False, reason="requires FSL to be in PATH")
|
65
|
+
@pytest.mark.skipif(
|
66
|
+
_check_ants() is False, reason="requires ANTs to be in PATH"
|
67
|
+
)
|
68
|
+
@pytest.mark.skipif(
|
69
|
+
socket.gethostname() != "juseless",
|
70
|
+
reason="only for juseless",
|
71
|
+
)
|
72
|
+
def test_BOLDWarper_preprocess_to_native(
|
73
|
+
datagrabber: "BaseDataGrabber", element: Tuple[str, ...]
|
74
|
+
) -> None:
|
75
|
+
"""Test BOLDWarper preprocess.
|
76
|
+
|
77
|
+
Parameters
|
78
|
+
----------
|
79
|
+
datagrabber : DataGrabber-like object
|
80
|
+
The parametrized DataGrabber objects.
|
81
|
+
element : tuple of str
|
82
|
+
The parametrized elements.
|
83
|
+
|
84
|
+
"""
|
85
|
+
with datagrabber as dg:
|
86
|
+
# Read data
|
87
|
+
element_data = DefaultDataReader().fit_transform(dg[element])
|
88
|
+
# Preprocess data
|
89
|
+
data, _ = BOLDWarper(reference="T1w").preprocess(
|
90
|
+
input=element_data["BOLD"],
|
91
|
+
extra_input=element_data,
|
92
|
+
)
|
93
|
+
assert isinstance(data, dict)
|
94
|
+
|
95
|
+
|
96
|
+
@pytest.mark.parametrize(
|
97
|
+
"datagrabber, element, space",
|
98
|
+
[
|
99
|
+
[
|
100
|
+
DMCC13Benchmark(
|
101
|
+
types=["BOLD"],
|
102
|
+
sessions=["ses-wave1bas"],
|
103
|
+
tasks=["Rest"],
|
104
|
+
phase_encodings=["AP"],
|
105
|
+
runs=["1"],
|
106
|
+
native_t1w=False,
|
107
|
+
),
|
108
|
+
("sub-f9057kp", "ses-wave1bas", "Rest", "AP", "1"),
|
109
|
+
"MNI152NLin2009aAsym",
|
110
|
+
],
|
111
|
+
[
|
112
|
+
DMCC13Benchmark(
|
113
|
+
types=["BOLD"],
|
114
|
+
sessions=["ses-wave1bas"],
|
115
|
+
tasks=["Rest"],
|
116
|
+
phase_encodings=["AP"],
|
117
|
+
runs=["1"],
|
118
|
+
native_t1w=False,
|
119
|
+
),
|
120
|
+
("sub-f9057kp", "ses-wave1bas", "Rest", "AP", "1"),
|
121
|
+
"MNI152NLin6Asym",
|
122
|
+
],
|
123
|
+
],
|
124
|
+
)
|
125
|
+
@pytest.mark.skipif(
|
126
|
+
_check_ants() is False, reason="requires ANTs to be in PATH"
|
127
|
+
)
|
128
|
+
@pytest.mark.skipif(
|
129
|
+
socket.gethostname() != "juseless",
|
130
|
+
reason="only for juseless",
|
131
|
+
)
|
132
|
+
def test_BOLDWarper_preprocess_to_multi_mni(
|
133
|
+
datagrabber: "BaseDataGrabber", element: Tuple[str, ...], space: str
|
134
|
+
) -> None:
|
135
|
+
"""Test BOLDWarper preprocess.
|
136
|
+
|
137
|
+
Parameters
|
138
|
+
----------
|
139
|
+
datagrabber : DataGrabber-like object
|
140
|
+
The parametrized DataGrabber objects.
|
141
|
+
element : tuple of str
|
142
|
+
The parametrized elements.
|
143
|
+
space : str
|
144
|
+
The parametrized template space to transform to.
|
145
|
+
|
146
|
+
"""
|
147
|
+
with datagrabber as dg:
|
148
|
+
# Read data
|
149
|
+
element_data = DefaultDataReader().fit_transform(dg[element])
|
150
|
+
pre_xfm_data = element_data["BOLD"]["data"].get_fdata().copy()
|
151
|
+
# Preprocess data
|
152
|
+
data, _ = BOLDWarper(reference=space).preprocess(
|
153
|
+
input=element_data["BOLD"],
|
154
|
+
extra_input=element_data,
|
155
|
+
)
|
156
|
+
assert isinstance(data, dict)
|
157
|
+
assert data["space"] == space
|
158
|
+
with assert_raises(AssertionError):
|
159
|
+
assert_array_equal(pre_xfm_data, data["data"])
|
@@ -27,12 +27,12 @@ def test_base_preprocessor_subclassing() -> None:
|
|
27
27
|
def get_valid_inputs(self):
|
28
28
|
return ["BOLD", "T1w"]
|
29
29
|
|
30
|
-
def get_output_type(self,
|
31
|
-
return
|
30
|
+
def get_output_type(self, input_type):
|
31
|
+
return input_type
|
32
32
|
|
33
|
-
def preprocess(self, input, extra_input):
|
34
|
-
input["data"] = f"
|
35
|
-
return
|
33
|
+
def preprocess(self, input, extra_input=None):
|
34
|
+
input["data"] = f"modified_{input['data']}"
|
35
|
+
return input, extra_input
|
36
36
|
|
37
37
|
with pytest.raises(ValueError, match=r"cannot be computed on \['T2w'\]"):
|
38
38
|
MyBasePreprocessor(on=["BOLD", "T2w"])
|
@@ -70,7 +70,7 @@ def test_base_preprocessor_subclassing() -> None:
|
|
70
70
|
# Check output
|
71
71
|
assert "BOLD" in output
|
72
72
|
assert "data" in output["BOLD"]
|
73
|
-
assert output["BOLD"]["data"] == "
|
73
|
+
assert output["BOLD"]["data"] == "modified_data"
|
74
74
|
assert "path" in output["BOLD"]
|
75
75
|
assert "meta" in output["BOLD"]
|
76
76
|
|
@@ -0,0 +1,167 @@
|
|
1
|
+
"""Provide class for space warping via ANTs antsApplyTransforms."""
|
2
|
+
|
3
|
+
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
|
4
|
+
# License: AGPL
|
5
|
+
|
6
|
+
from typing import (
|
7
|
+
Any,
|
8
|
+
ClassVar,
|
9
|
+
Dict,
|
10
|
+
List,
|
11
|
+
Set,
|
12
|
+
Union,
|
13
|
+
)
|
14
|
+
|
15
|
+
import nibabel as nib
|
16
|
+
import numpy as np
|
17
|
+
|
18
|
+
from ...data import get_template, get_xfm
|
19
|
+
from ...pipeline import WorkDirManager
|
20
|
+
from ...utils import logger, run_ext_cmd
|
21
|
+
|
22
|
+
|
23
|
+
class ANTsWarper:
|
24
|
+
"""Class for space warping via ANTs antsApplyTransforms.
|
25
|
+
|
26
|
+
This class uses ANTs' ``ResampleImage`` for resampling (if required) and
|
27
|
+
``antsApplyTransforms`` for transformation.
|
28
|
+
|
29
|
+
"""
|
30
|
+
|
31
|
+
_EXT_DEPENDENCIES: ClassVar[List[Dict[str, Union[str, List[str]]]]] = [
|
32
|
+
{
|
33
|
+
"name": "ants",
|
34
|
+
"commands": ["ResampleImage", "antsApplyTransforms"],
|
35
|
+
},
|
36
|
+
]
|
37
|
+
|
38
|
+
_DEPENDENCIES: ClassVar[Set[str]] = {"numpy", "nibabel"}
|
39
|
+
|
40
|
+
def preprocess(
|
41
|
+
self,
|
42
|
+
input: Dict[str, Any],
|
43
|
+
extra_input: Dict[str, Any],
|
44
|
+
reference: str,
|
45
|
+
) -> Dict[str, Any]:
|
46
|
+
"""Preprocess using ANTs.
|
47
|
+
|
48
|
+
Parameters
|
49
|
+
----------
|
50
|
+
input : dict
|
51
|
+
A single input from the Junifer Data object in which to preprocess.
|
52
|
+
extra_input : dict
|
53
|
+
The other fields in the Junifer Data object. Should have ``T1w``
|
54
|
+
and ``Warp`` data types.
|
55
|
+
reference : str
|
56
|
+
The data type or template space to use as reference for warping.
|
57
|
+
|
58
|
+
Returns
|
59
|
+
-------
|
60
|
+
dict
|
61
|
+
The ``input`` dictionary with modified ``data`` and ``space`` key
|
62
|
+
values and new ``reference_path`` key whose value points to the
|
63
|
+
reference file used for warping.
|
64
|
+
|
65
|
+
"""
|
66
|
+
# Create element-specific tempdir for storing post-warping assets
|
67
|
+
element_tempdir = WorkDirManager().get_element_tempdir(
|
68
|
+
prefix="ants_warper"
|
69
|
+
)
|
70
|
+
|
71
|
+
# Native space warping
|
72
|
+
if reference == "T1w":
|
73
|
+
logger.debug("Using ANTs for space warping")
|
74
|
+
|
75
|
+
# Get the min of the voxel sizes from input and use it as the
|
76
|
+
# resolution
|
77
|
+
resolution = np.min(input["data"].header.get_zooms()[:3])
|
78
|
+
|
79
|
+
# Create a tempfile for resampled reference output
|
80
|
+
resample_image_out_path = (
|
81
|
+
element_tempdir / "resampled_reference.nii.gz"
|
82
|
+
)
|
83
|
+
# Set ResampleImage command
|
84
|
+
resample_image_cmd = [
|
85
|
+
"ResampleImage",
|
86
|
+
"3", # image dimension
|
87
|
+
f"{extra_input['T1w']['path'].resolve()}",
|
88
|
+
f"{resample_image_out_path.resolve()}",
|
89
|
+
f"{resolution}x{resolution}x{resolution}",
|
90
|
+
"0", # option for spacing and not size
|
91
|
+
"3 3", # Lanczos windowed sinc
|
92
|
+
]
|
93
|
+
# Call ResampleImage
|
94
|
+
run_ext_cmd(name="ResampleImage", cmd=resample_image_cmd)
|
95
|
+
|
96
|
+
# Create a tempfile for warped output
|
97
|
+
apply_transforms_out_path = element_tempdir / "output.nii.gz"
|
98
|
+
# Set antsApplyTransforms command
|
99
|
+
apply_transforms_cmd = [
|
100
|
+
"antsApplyTransforms",
|
101
|
+
"-d 3",
|
102
|
+
"-e 3",
|
103
|
+
"-n LanczosWindowedSinc",
|
104
|
+
f"-i {input['path'].resolve()}",
|
105
|
+
# use resampled reference
|
106
|
+
f"-r {resample_image_out_path.resolve()}",
|
107
|
+
f"-t {extra_input['Warp']['path'].resolve()}",
|
108
|
+
f"-o {apply_transforms_out_path.resolve()}",
|
109
|
+
]
|
110
|
+
# Call antsApplyTransforms
|
111
|
+
run_ext_cmd(name="antsApplyTransforms", cmd=apply_transforms_cmd)
|
112
|
+
|
113
|
+
# Load nifti
|
114
|
+
input["data"] = nib.load(apply_transforms_out_path)
|
115
|
+
# Save resampled reference path
|
116
|
+
input["reference_path"] = resample_image_out_path
|
117
|
+
# Use reference input's space as warped input's space
|
118
|
+
input["space"] = extra_input["T1w"]["space"]
|
119
|
+
|
120
|
+
# Template space warping
|
121
|
+
else:
|
122
|
+
logger.debug(
|
123
|
+
f"Using ANTs to warp data from {input['space']} to {reference}"
|
124
|
+
)
|
125
|
+
|
126
|
+
# Get xfm file
|
127
|
+
xfm_file_path = get_xfm(src=input["space"], dst=reference)
|
128
|
+
# Get template space image
|
129
|
+
template_space_img = get_template(
|
130
|
+
space=reference,
|
131
|
+
target_data=input,
|
132
|
+
extra_input=None,
|
133
|
+
)
|
134
|
+
|
135
|
+
# Create component-scoped tempdir
|
136
|
+
tempdir = WorkDirManager().get_tempdir(prefix="ants_warper")
|
137
|
+
# Save template
|
138
|
+
template_space_img_path = tempdir / f"{reference}_T1w.nii.gz"
|
139
|
+
nib.save(template_space_img, template_space_img_path)
|
140
|
+
|
141
|
+
# Create a tempfile for warped output
|
142
|
+
warped_output_path = element_tempdir / (
|
143
|
+
f"data_warped_from_{input['space']}_to_" f"{reference}.nii.gz"
|
144
|
+
)
|
145
|
+
|
146
|
+
# Set antsApplyTransforms command
|
147
|
+
apply_transforms_cmd = [
|
148
|
+
"antsApplyTransforms",
|
149
|
+
"-d 3",
|
150
|
+
"-e 3",
|
151
|
+
"-n LanczosWindowedSinc",
|
152
|
+
f"-i {input['path'].resolve()}",
|
153
|
+
f"-r {template_space_img_path.resolve()}",
|
154
|
+
f"-t {xfm_file_path.resolve()}",
|
155
|
+
f"-o {warped_output_path.resolve()}",
|
156
|
+
]
|
157
|
+
# Call antsApplyTransforms
|
158
|
+
run_ext_cmd(name="antsApplyTransforms", cmd=apply_transforms_cmd)
|
159
|
+
|
160
|
+
# Delete tempdir
|
161
|
+
WorkDirManager().delete_tempdir(tempdir)
|
162
|
+
|
163
|
+
# Modify target data
|
164
|
+
input["data"] = nib.load(warped_output_path)
|
165
|
+
input["space"] = reference
|
166
|
+
|
167
|
+
return input
|
@@ -0,0 +1,109 @@
|
|
1
|
+
"""Provide class for space warping via FSL FLIRT."""
|
2
|
+
|
3
|
+
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
|
4
|
+
# License: AGPL
|
5
|
+
|
6
|
+
from typing import (
|
7
|
+
Any,
|
8
|
+
ClassVar,
|
9
|
+
Dict,
|
10
|
+
List,
|
11
|
+
Set,
|
12
|
+
Union,
|
13
|
+
)
|
14
|
+
|
15
|
+
import nibabel as nib
|
16
|
+
import numpy as np
|
17
|
+
|
18
|
+
from ...pipeline import WorkDirManager
|
19
|
+
from ...utils import logger, run_ext_cmd
|
20
|
+
|
21
|
+
|
22
|
+
class FSLWarper:
|
23
|
+
"""Class for space warping via FSL FLIRT.
|
24
|
+
|
25
|
+
This class uses FSL FLIRT's ``flirt`` for resampling and ``applywarp`` for
|
26
|
+
transformation.
|
27
|
+
|
28
|
+
"""
|
29
|
+
|
30
|
+
_EXT_DEPENDENCIES: ClassVar[List[Dict[str, Union[str, List[str]]]]] = [
|
31
|
+
{
|
32
|
+
"name": "fsl",
|
33
|
+
"commands": ["flirt", "applywarp"],
|
34
|
+
},
|
35
|
+
]
|
36
|
+
|
37
|
+
_DEPENDENCIES: ClassVar[Set[str]] = {"numpy", "nibabel"}
|
38
|
+
|
39
|
+
def preprocess(
|
40
|
+
self,
|
41
|
+
input: Dict[str, Any],
|
42
|
+
extra_input: Dict[str, Any],
|
43
|
+
) -> Dict[str, Any]:
|
44
|
+
"""Preprocess using FSL.
|
45
|
+
|
46
|
+
Parameters
|
47
|
+
----------
|
48
|
+
input : dict
|
49
|
+
A single input from the Junifer Data object in which to preprocess.
|
50
|
+
extra_input : dict
|
51
|
+
The other fields in the Junifer Data object. Should have ``T1w``
|
52
|
+
and ``Warp`` data types.
|
53
|
+
|
54
|
+
Returns
|
55
|
+
-------
|
56
|
+
dict
|
57
|
+
The ``input`` dictionary with modified ``data`` and ``space`` key
|
58
|
+
values and new ``reference_path`` key whose value points to the
|
59
|
+
reference file used for warping.
|
60
|
+
|
61
|
+
"""
|
62
|
+
logger.debug("Using FSL for space warping")
|
63
|
+
|
64
|
+
# Get the min of the voxel sizes from input and use it as the
|
65
|
+
# resolution
|
66
|
+
resolution = np.min(input["data"].header.get_zooms()[:3])
|
67
|
+
|
68
|
+
# Create element-specific tempdir for storing post-warping assets
|
69
|
+
element_tempdir = WorkDirManager().get_element_tempdir(
|
70
|
+
prefix="fsl_warper"
|
71
|
+
)
|
72
|
+
|
73
|
+
# Create a tempfile for resampled reference output
|
74
|
+
flirt_out_path = element_tempdir / "resampled_reference.nii.gz"
|
75
|
+
# Set flirt command
|
76
|
+
flirt_cmd = [
|
77
|
+
"flirt",
|
78
|
+
"-interp spline",
|
79
|
+
f"-in {extra_input['T1w']['path'].resolve()}",
|
80
|
+
f"-ref {extra_input['T1w']['path'].resolve()}",
|
81
|
+
f"-applyisoxfm {resolution}",
|
82
|
+
f"-out {flirt_out_path.resolve()}",
|
83
|
+
]
|
84
|
+
# Call flirt
|
85
|
+
run_ext_cmd(name="flirt", cmd=flirt_cmd)
|
86
|
+
|
87
|
+
# Create a tempfile for warped output
|
88
|
+
applywarp_out_path = element_tempdir / "output.nii.gz"
|
89
|
+
# Set applywarp command
|
90
|
+
applywarp_cmd = [
|
91
|
+
"applywarp",
|
92
|
+
"--interp=spline",
|
93
|
+
f"-i {input['path'].resolve()}",
|
94
|
+
f"-r {flirt_out_path.resolve()}", # use resampled reference
|
95
|
+
f"-w {extra_input['Warp']['path'].resolve()}",
|
96
|
+
f"-o {applywarp_out_path.resolve()}",
|
97
|
+
]
|
98
|
+
# Call applywarp
|
99
|
+
run_ext_cmd(name="applywarp", cmd=applywarp_cmd)
|
100
|
+
|
101
|
+
# Load nifti
|
102
|
+
input["data"] = nib.load(applywarp_out_path)
|
103
|
+
# Save resampled reference path
|
104
|
+
input["reference_path"] = flirt_out_path
|
105
|
+
|
106
|
+
# Use reference input's space as warped input's space
|
107
|
+
input["space"] = extra_input["T1w"]["space"]
|
108
|
+
|
109
|
+
return input
|
@@ -0,0 +1,213 @@
|
|
1
|
+
"""Provide class for warping data to other template spaces."""
|
2
|
+
|
3
|
+
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
|
4
|
+
# License: AGPL
|
5
|
+
|
6
|
+
from typing import Any, ClassVar, Dict, List, Optional, Tuple, Type, Union
|
7
|
+
|
8
|
+
from templateflow import api as tflow
|
9
|
+
|
10
|
+
from ...api.decorators import register_preprocessor
|
11
|
+
from ...utils import logger, raise_error
|
12
|
+
from ..base import BasePreprocessor
|
13
|
+
from ._ants_warper import ANTsWarper
|
14
|
+
from ._fsl_warper import FSLWarper
|
15
|
+
|
16
|
+
|
17
|
+
__all__ = ["SpaceWarper"]
|
18
|
+
|
19
|
+
|
20
|
+
@register_preprocessor
|
21
|
+
class SpaceWarper(BasePreprocessor):
|
22
|
+
"""Class for warping data to other template spaces.
|
23
|
+
|
24
|
+
Parameters
|
25
|
+
----------
|
26
|
+
using : {"fsl", "ants"}
|
27
|
+
Implementation to use for warping:
|
28
|
+
|
29
|
+
* "fsl" : Use FSL's ``applywarp``
|
30
|
+
* "ants" : Use ANTs' ``antsApplyTransforms``
|
31
|
+
|
32
|
+
reference : str
|
33
|
+
The data type to use as reference for warping, can be either a data
|
34
|
+
type like ``"T1w"`` or a template space like ``"MNI152NLin2009cAsym"``.
|
35
|
+
Use ``"T1w"`` for native space warping and named templates for
|
36
|
+
template space warping.
|
37
|
+
on : {"T1w", "T2w", "BOLD", "VBM_GM", "VBM_WM", "VBM_CSF", "fALFF", \
|
38
|
+
"GCOR", "LCOR"} or list of the options
|
39
|
+
The data type to warp.
|
40
|
+
|
41
|
+
Raises
|
42
|
+
------
|
43
|
+
ValueError
|
44
|
+
If ``using`` is invalid or
|
45
|
+
if ``reference`` is invalid.
|
46
|
+
|
47
|
+
"""
|
48
|
+
|
49
|
+
_CONDITIONAL_DEPENDENCIES: ClassVar[List[Dict[str, Union[str, Type]]]] = [
|
50
|
+
{
|
51
|
+
"using": "fsl",
|
52
|
+
"depends_on": FSLWarper,
|
53
|
+
},
|
54
|
+
{
|
55
|
+
"using": "ants",
|
56
|
+
"depends_on": ANTsWarper,
|
57
|
+
},
|
58
|
+
]
|
59
|
+
|
60
|
+
def __init__(
|
61
|
+
self, using: str, reference: str, on: Union[List[str], str]
|
62
|
+
) -> None:
|
63
|
+
"""Initialize the class."""
|
64
|
+
# Validate `using` parameter
|
65
|
+
valid_using = [dep["using"] for dep in self._CONDITIONAL_DEPENDENCIES]
|
66
|
+
if using not in valid_using:
|
67
|
+
raise_error(
|
68
|
+
f"Invalid value for `using`, should be one of: {valid_using}"
|
69
|
+
)
|
70
|
+
self.using = using
|
71
|
+
self.reference = reference
|
72
|
+
# Set required data types based on reference and
|
73
|
+
# initialize superclass
|
74
|
+
if self.reference == "T1w":
|
75
|
+
required_data_types = [self.reference, "Warp"]
|
76
|
+
# Listify on
|
77
|
+
if not isinstance(on, list):
|
78
|
+
on = [on]
|
79
|
+
# Extend required data types
|
80
|
+
required_data_types.extend(on)
|
81
|
+
|
82
|
+
super().__init__(
|
83
|
+
on=on,
|
84
|
+
required_data_types=required_data_types,
|
85
|
+
)
|
86
|
+
elif self.reference in tflow.templates():
|
87
|
+
super().__init__(on=on)
|
88
|
+
else:
|
89
|
+
raise_error(f"Unknown reference: {self.reference}")
|
90
|
+
|
91
|
+
def get_valid_inputs(self) -> List[str]:
|
92
|
+
"""Get valid data types for input.
|
93
|
+
|
94
|
+
Returns
|
95
|
+
-------
|
96
|
+
list of str
|
97
|
+
The list of data types that can be used as input for this
|
98
|
+
preprocessor.
|
99
|
+
|
100
|
+
"""
|
101
|
+
return [
|
102
|
+
"T1w",
|
103
|
+
"T2w",
|
104
|
+
"BOLD",
|
105
|
+
"VBM_GM",
|
106
|
+
"VBM_WM",
|
107
|
+
"VBM_CSF",
|
108
|
+
"fALFF",
|
109
|
+
"GCOR",
|
110
|
+
"LCOR",
|
111
|
+
]
|
112
|
+
|
113
|
+
def get_output_type(self, input_type: str) -> str:
|
114
|
+
"""Get output type.
|
115
|
+
|
116
|
+
Parameters
|
117
|
+
----------
|
118
|
+
input_type : str
|
119
|
+
The data type input to the preprocessor.
|
120
|
+
|
121
|
+
Returns
|
122
|
+
-------
|
123
|
+
str
|
124
|
+
The data type output by the preprocessor.
|
125
|
+
|
126
|
+
"""
|
127
|
+
# Does not add any new keys
|
128
|
+
return input_type
|
129
|
+
|
130
|
+
def preprocess(
|
131
|
+
self,
|
132
|
+
input: Dict[str, Any],
|
133
|
+
extra_input: Optional[Dict[str, Any]] = None,
|
134
|
+
) -> Tuple[Dict[str, Any], Optional[Dict[str, Dict[str, Any]]]]:
|
135
|
+
"""Preprocess.
|
136
|
+
|
137
|
+
Parameters
|
138
|
+
----------
|
139
|
+
input : dict
|
140
|
+
The input from the Junifer Data object.
|
141
|
+
extra_input : dict, optional
|
142
|
+
The other fields in the Junifer Data object.
|
143
|
+
|
144
|
+
Returns
|
145
|
+
-------
|
146
|
+
dict
|
147
|
+
The computed result as dictionary.
|
148
|
+
None
|
149
|
+
Extra "helper" data types as dictionary to add to the Junifer Data
|
150
|
+
object.
|
151
|
+
|
152
|
+
Raises
|
153
|
+
------
|
154
|
+
ValueError
|
155
|
+
If ``extra_input`` is None when transforming to native space
|
156
|
+
i.e., using ``"T1w"`` as reference.
|
157
|
+
RuntimeError
|
158
|
+
If the data is in the correct space and does not require
|
159
|
+
warping or
|
160
|
+
if FSL is used for template space warping.
|
161
|
+
|
162
|
+
"""
|
163
|
+
logger.info(f"Warping to {self.reference} space using SpaceWarper")
|
164
|
+
# Transform to native space
|
165
|
+
if self.using in ["fsl", "ants"] and self.reference == "T1w":
|
166
|
+
# Check for extra inputs
|
167
|
+
if extra_input is None:
|
168
|
+
raise_error(
|
169
|
+
"No extra input provided, requires `Warp` and "
|
170
|
+
f"`{self.reference}` data types in particular."
|
171
|
+
)
|
172
|
+
# Conditional preprocessor
|
173
|
+
if self.using == "fsl":
|
174
|
+
input = FSLWarper().preprocess(
|
175
|
+
input=input,
|
176
|
+
extra_input=extra_input,
|
177
|
+
)
|
178
|
+
elif self.using == "ants":
|
179
|
+
input = ANTsWarper().preprocess(
|
180
|
+
input=input,
|
181
|
+
extra_input=extra_input,
|
182
|
+
reference=self.reference,
|
183
|
+
)
|
184
|
+
# Transform to template space with ANTs possible
|
185
|
+
elif self.using == "ants" and self.reference != "T1w":
|
186
|
+
# Check pre-requirements for space manipulation
|
187
|
+
if self.reference == input["space"]:
|
188
|
+
raise_error(
|
189
|
+
(
|
190
|
+
f"The target data is in {self.reference} space "
|
191
|
+
"and thus warping will not be performed, hence you "
|
192
|
+
"should remove the SpaceWarper from the preprocess "
|
193
|
+
"step."
|
194
|
+
),
|
195
|
+
klass=RuntimeError,
|
196
|
+
)
|
197
|
+
|
198
|
+
input = ANTsWarper().preprocess(
|
199
|
+
input=input,
|
200
|
+
extra_input={},
|
201
|
+
reference=self.reference,
|
202
|
+
)
|
203
|
+
# Transform to template space with FSL not possible
|
204
|
+
elif self.using == "fsl" and self.reference != "T1w":
|
205
|
+
raise_error(
|
206
|
+
(
|
207
|
+
f"Warping to {self.reference} space not possible with "
|
208
|
+
"FSL, use ANTs instead."
|
209
|
+
),
|
210
|
+
klass=RuntimeError,
|
211
|
+
)
|
212
|
+
|
213
|
+
return input, None
|