junifer 0.0.4.dev493__py3-none-any.whl → 0.0.4.dev530__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/api/res/ants/ResampleImage +3 -0
- junifer/data/coordinates.py +83 -44
- junifer/data/masks.py +46 -37
- junifer/data/parcellations.py +46 -40
- junifer/markers/falff/falff_estimator.py +33 -72
- junifer/markers/reho/reho_estimator.py +7 -54
- junifer/preprocess/ants/__init__.py +4 -0
- junifer/preprocess/ants/ants_apply_transforms_warper.py +224 -0
- junifer/preprocess/ants/tests/test_ants_apply_transforms_warper.py +124 -0
- junifer/preprocess/bold_warper.py +40 -9
- junifer/preprocess/fsl/apply_warper.py +5 -56
- junifer/preprocess/fsl/tests/test_apply_warper.py +53 -22
- junifer/preprocess/tests/test_bold_warper.py +64 -14
- junifer/utils/__init__.py +1 -0
- junifer/utils/helpers.py +53 -0
- junifer/utils/tests/test_helpers.py +35 -0
- {junifer-0.0.4.dev493.dist-info → junifer-0.0.4.dev530.dist-info}/METADATA +1 -1
- {junifer-0.0.4.dev493.dist-info → junifer-0.0.4.dev530.dist-info}/RECORD +24 -18
- {junifer-0.0.4.dev493.dist-info → junifer-0.0.4.dev530.dist-info}/AUTHORS.rst +0 -0
- {junifer-0.0.4.dev493.dist-info → junifer-0.0.4.dev530.dist-info}/LICENSE.md +0 -0
- {junifer-0.0.4.dev493.dist-info → junifer-0.0.4.dev530.dist-info}/WHEEL +0 -0
- {junifer-0.0.4.dev493.dist-info → junifer-0.0.4.dev530.dist-info}/entry_points.txt +0 -0
- {junifer-0.0.4.dev493.dist-info → junifer-0.0.4.dev530.dist-info}/top_level.txt +0 -0
@@ -6,11 +6,10 @@
|
|
6
6
|
|
7
7
|
|
8
8
|
import hashlib
|
9
|
-
import subprocess
|
10
9
|
from functools import lru_cache
|
11
10
|
from itertools import product
|
12
11
|
from pathlib import Path
|
13
|
-
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
|
12
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
|
14
13
|
|
15
14
|
import nibabel as nib
|
16
15
|
import numpy as np
|
@@ -20,7 +19,7 @@ from scipy.stats import rankdata
|
|
20
19
|
|
21
20
|
from ...pipeline import WorkDirManager
|
22
21
|
from ...pipeline.singleton import singleton
|
23
|
-
from ...utils import logger, raise_error
|
22
|
+
from ...utils import logger, raise_error, run_ext_cmd
|
24
23
|
|
25
24
|
|
26
25
|
if TYPE_CHECKING:
|
@@ -112,11 +111,6 @@ class ReHoEstimator:
|
|
112
111
|
pathlib.Path
|
113
112
|
The path to the ReHo map as NIfTI.
|
114
113
|
|
115
|
-
Raises
|
116
|
-
------
|
117
|
-
RuntimeError
|
118
|
-
If the 3dReHo command fails due to some issue.
|
119
|
-
|
120
114
|
Notes
|
121
115
|
-----
|
122
116
|
For more information on the publication, please check [1]_ , and for
|
@@ -174,30 +168,10 @@ class ReHoEstimator:
|
|
174
168
|
else:
|
175
169
|
reho_cmd.append(f"-nneigh {nneigh}")
|
176
170
|
# Call 3dReHo
|
177
|
-
|
178
|
-
logger.info(f"3dReHo command to be executed: {reho_cmd_str}")
|
179
|
-
reho_process = subprocess.run(
|
180
|
-
reho_cmd_str, # string needed with shell=True
|
181
|
-
stdin=subprocess.DEVNULL,
|
182
|
-
stdout=subprocess.PIPE,
|
183
|
-
stderr=subprocess.STDOUT,
|
184
|
-
shell=True, # needed for respecting $PATH
|
185
|
-
check=False,
|
186
|
-
)
|
187
|
-
if reho_process.returncode == 0:
|
188
|
-
logger.info(
|
189
|
-
"3dReHo succeeded with the following output: "
|
190
|
-
f"{reho_process.stdout}"
|
191
|
-
)
|
192
|
-
else:
|
193
|
-
raise_error(
|
194
|
-
msg="3dReHo failed with the following error: "
|
195
|
-
f"{reho_process.stdout}",
|
196
|
-
klass=RuntimeError,
|
197
|
-
)
|
171
|
+
run_ext_cmd(name="3dReHo", cmd=reho_cmd)
|
198
172
|
|
199
173
|
# SHA256 for bypassing memmap
|
200
|
-
sha256_params = hashlib.sha256(bytes(
|
174
|
+
sha256_params = hashlib.sha256(bytes(" ".join(reho_cmd), "utf-8"))
|
201
175
|
# Create element-scoped tempdir so that the ReHo map is
|
202
176
|
# available later as get_coordinates and the like need it
|
203
177
|
# in ReHoSpheres and the like to transform to other template
|
@@ -216,27 +190,7 @@ class ReHoEstimator:
|
|
216
190
|
f"{reho_afni_out_path_prefix}+tlrc.BRIK",
|
217
191
|
]
|
218
192
|
# Call 3dAFNItoNIFTI
|
219
|
-
|
220
|
-
logger.info(f"3dAFNItoNIFTI command to be executed: {convert_cmd_str}")
|
221
|
-
convert_process = subprocess.run(
|
222
|
-
convert_cmd_str, # string needed with shell=True
|
223
|
-
stdin=subprocess.DEVNULL,
|
224
|
-
stdout=subprocess.PIPE,
|
225
|
-
stderr=subprocess.STDOUT,
|
226
|
-
shell=True, # needed for respecting $PATH
|
227
|
-
check=False,
|
228
|
-
)
|
229
|
-
if convert_process.returncode == 0:
|
230
|
-
logger.info(
|
231
|
-
"3dAFNItoNIFTI succeeded with the following output: "
|
232
|
-
f"{convert_process.stdout}"
|
233
|
-
)
|
234
|
-
else:
|
235
|
-
raise_error(
|
236
|
-
msg="3dAFNItoNIFTI failed with the following error: "
|
237
|
-
f"{convert_process.stdout}",
|
238
|
-
klass=RuntimeError,
|
239
|
-
)
|
193
|
+
run_ext_cmd(name="3dAFNItoNIFTI", cmd=convert_cmd)
|
240
194
|
|
241
195
|
# Cleanup intermediate files
|
242
196
|
for fname in self.temp_dir_path.glob("reho*"): # type: ignore
|
@@ -244,9 +198,8 @@ class ReHoEstimator:
|
|
244
198
|
|
245
199
|
# Load nifti
|
246
200
|
output_data = nib.load(reho_afni_to_nifti_out_path)
|
247
|
-
|
248
|
-
output_data
|
249
|
-
return output_data, reho_afni_to_nifti_out_path
|
201
|
+
|
202
|
+
return output_data, reho_afni_to_nifti_out_path # type: ignore
|
250
203
|
|
251
204
|
def _compute_reho_python(
|
252
205
|
self,
|
@@ -0,0 +1,224 @@
|
|
1
|
+
"""Provide class for warping via ANTs antsApplyTransforms."""
|
2
|
+
|
3
|
+
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
|
4
|
+
# License: AGPL
|
5
|
+
|
6
|
+
from pathlib import Path
|
7
|
+
from typing import (
|
8
|
+
TYPE_CHECKING,
|
9
|
+
Any,
|
10
|
+
ClassVar,
|
11
|
+
Dict,
|
12
|
+
List,
|
13
|
+
Optional,
|
14
|
+
Tuple,
|
15
|
+
Union,
|
16
|
+
)
|
17
|
+
|
18
|
+
import nibabel as nib
|
19
|
+
import numpy as np
|
20
|
+
|
21
|
+
from ...pipeline import WorkDirManager
|
22
|
+
from ...utils import logger, raise_error, run_ext_cmd
|
23
|
+
from ..base import BasePreprocessor
|
24
|
+
|
25
|
+
|
26
|
+
if TYPE_CHECKING:
|
27
|
+
from nibabel import Nifti1Image
|
28
|
+
|
29
|
+
|
30
|
+
class _AntsApplyTransformsWarper(BasePreprocessor):
|
31
|
+
"""Class for warping NIfTI images via ANTs antsApplyTransforms.
|
32
|
+
|
33
|
+
Warps ANTs ``antsApplyTransforms``.
|
34
|
+
|
35
|
+
Parameters
|
36
|
+
----------
|
37
|
+
reference : str
|
38
|
+
The data type to use as reference for warping.
|
39
|
+
on : str
|
40
|
+
The data type to use for warping.
|
41
|
+
|
42
|
+
Raises
|
43
|
+
------
|
44
|
+
ValueError
|
45
|
+
If a list was passed for ``on``.
|
46
|
+
|
47
|
+
"""
|
48
|
+
|
49
|
+
_EXT_DEPENDENCIES: ClassVar[
|
50
|
+
List[Dict[str, Union[str, bool, List[str]]]]
|
51
|
+
] = [
|
52
|
+
{
|
53
|
+
"name": "ants",
|
54
|
+
"optional": False,
|
55
|
+
"commands": ["ResampleImage", "antsApplyTransforms"],
|
56
|
+
},
|
57
|
+
]
|
58
|
+
|
59
|
+
def __init__(self, reference: str, on: str) -> None:
|
60
|
+
"""Initialize the class."""
|
61
|
+
self.ref = reference
|
62
|
+
# Check only single data type is passed
|
63
|
+
if isinstance(on, list):
|
64
|
+
raise_error("Can only work on single data type, list was passed.")
|
65
|
+
self.on = on # needed for the base validation to work
|
66
|
+
super().__init__(
|
67
|
+
on=self.on, required_data_types=[self.on, self.ref, "Warp"]
|
68
|
+
)
|
69
|
+
|
70
|
+
def get_valid_inputs(self) -> List[str]:
|
71
|
+
"""Get valid data types for input.
|
72
|
+
|
73
|
+
Returns
|
74
|
+
-------
|
75
|
+
list of str
|
76
|
+
The list of data types that can be used as input for this
|
77
|
+
preprocessor.
|
78
|
+
|
79
|
+
"""
|
80
|
+
# Constructed dynamically
|
81
|
+
return [self.on]
|
82
|
+
|
83
|
+
def get_output_type(self, input: List[str]) -> List[str]:
|
84
|
+
"""Get output type.
|
85
|
+
|
86
|
+
Parameters
|
87
|
+
----------
|
88
|
+
input : list of str
|
89
|
+
The input to the preprocessor. The list must contain the
|
90
|
+
available Junifer Data dictionary keys.
|
91
|
+
|
92
|
+
Returns
|
93
|
+
-------
|
94
|
+
list of str
|
95
|
+
The updated list of available Junifer Data object keys after
|
96
|
+
the pipeline step.
|
97
|
+
|
98
|
+
"""
|
99
|
+
# Does not add any new keys
|
100
|
+
return input
|
101
|
+
|
102
|
+
def _run_apply_transforms(
|
103
|
+
self,
|
104
|
+
input_data: Dict,
|
105
|
+
ref_path: Path,
|
106
|
+
warp_path: Path,
|
107
|
+
) -> Tuple["Nifti1Image", Path]:
|
108
|
+
"""Run ``antsApplyTransforms``.
|
109
|
+
|
110
|
+
Parameters
|
111
|
+
----------
|
112
|
+
input_data : dict
|
113
|
+
The input data.
|
114
|
+
ref_path : pathlib.Path
|
115
|
+
The path to the reference file.
|
116
|
+
warp_path : pathlib.Path
|
117
|
+
The path to the warp file.
|
118
|
+
|
119
|
+
Returns
|
120
|
+
-------
|
121
|
+
Niimg-like object
|
122
|
+
The warped input image.
|
123
|
+
pathlib.Path
|
124
|
+
The path to the resampled reference image.
|
125
|
+
|
126
|
+
"""
|
127
|
+
# Get the min of the voxel sizes from input and use it as the
|
128
|
+
# resolution
|
129
|
+
resolution = np.min(input_data["data"].header.get_zooms()[:3])
|
130
|
+
|
131
|
+
# Create element-specific tempdir for storing post-warping assets
|
132
|
+
tempdir = WorkDirManager().get_element_tempdir(
|
133
|
+
prefix="applytransforms"
|
134
|
+
)
|
135
|
+
|
136
|
+
# Create a tempfile for resampled reference output
|
137
|
+
resample_image_out_path = tempdir / "reference_resampled.nii.gz"
|
138
|
+
# Set ResampleImage command
|
139
|
+
resample_image_cmd = [
|
140
|
+
"ResampleImage",
|
141
|
+
"3", # image dimension
|
142
|
+
f"{ref_path.resolve()}",
|
143
|
+
f"{resample_image_out_path.resolve()}",
|
144
|
+
f"{resolution}x{resolution}x{resolution}",
|
145
|
+
"0", # option for spacing and not size
|
146
|
+
"3 3", # Lanczos windowed sinc
|
147
|
+
]
|
148
|
+
# Call ResampleImage
|
149
|
+
run_ext_cmd(name="ResampleImage", cmd=resample_image_cmd)
|
150
|
+
|
151
|
+
# Create a tempfile for warped output
|
152
|
+
apply_transforms_out_path = tempdir / "input_warped.nii.gz"
|
153
|
+
# Set antsApplyTransforms command
|
154
|
+
apply_transforms_cmd = [
|
155
|
+
"antsApplyTransforms",
|
156
|
+
"-d 3",
|
157
|
+
"-e 3",
|
158
|
+
"-n LanczosWindowedSinc",
|
159
|
+
f"-i {input_data['path'].resolve()}",
|
160
|
+
# use resampled reference
|
161
|
+
f"-r {resample_image_out_path.resolve()}",
|
162
|
+
f"-t {warp_path.resolve()}",
|
163
|
+
f"-o {apply_transforms_out_path.resolve()}",
|
164
|
+
]
|
165
|
+
# Call antsApplyTransforms
|
166
|
+
run_ext_cmd(name="antsApplyTransforms", cmd=apply_transforms_cmd)
|
167
|
+
|
168
|
+
# Load nifti
|
169
|
+
output_img = nib.load(apply_transforms_out_path)
|
170
|
+
|
171
|
+
return output_img, resample_image_out_path # type: ignore
|
172
|
+
|
173
|
+
def preprocess(
|
174
|
+
self,
|
175
|
+
input: Dict[str, Any],
|
176
|
+
extra_input: Optional[Dict[str, Any]] = None,
|
177
|
+
) -> Tuple[str, Dict[str, Any]]:
|
178
|
+
"""Preprocess.
|
179
|
+
|
180
|
+
Parameters
|
181
|
+
----------
|
182
|
+
input : dict
|
183
|
+
A single input from the Junifer Data object in which to preprocess.
|
184
|
+
extra_input : dict, optional
|
185
|
+
The other fields in the Junifer Data object. Must include the
|
186
|
+
``Warp`` and ``ref`` value's keys.
|
187
|
+
|
188
|
+
Returns
|
189
|
+
-------
|
190
|
+
str
|
191
|
+
The key to store the output in the Junifer Data object.
|
192
|
+
dict
|
193
|
+
The computed result as dictionary. This will be stored in the
|
194
|
+
Junifer Data object under the key ``data`` of the data type.
|
195
|
+
|
196
|
+
Raises
|
197
|
+
------
|
198
|
+
ValueError
|
199
|
+
If ``extra_input`` is None.
|
200
|
+
|
201
|
+
"""
|
202
|
+
logger.debug("Warping via ANTs using antsApplyTransforms")
|
203
|
+
# Check for extra inputs
|
204
|
+
if extra_input is None:
|
205
|
+
raise_error(
|
206
|
+
f"No extra input provided, requires `Warp` and `{self.ref}` "
|
207
|
+
"data types in particular."
|
208
|
+
)
|
209
|
+
# Retrieve data type info to warp
|
210
|
+
to_warp_input = input
|
211
|
+
# Retrieve data type info to use as reference
|
212
|
+
ref_input = extra_input[self.ref]
|
213
|
+
# Retrieve Warp data
|
214
|
+
warp = extra_input["Warp"]
|
215
|
+
# Replace original data with warped data and add resampled reference
|
216
|
+
# path
|
217
|
+
input["data"], input["reference_path"] = self._run_apply_transforms(
|
218
|
+
input_data=to_warp_input,
|
219
|
+
ref_path=ref_input["path"],
|
220
|
+
warp_path=warp["path"],
|
221
|
+
)
|
222
|
+
# Use reference input's space as warped input's space
|
223
|
+
input["space"] = ref_input["space"]
|
224
|
+
return self.on, input
|
@@ -0,0 +1,124 @@
|
|
1
|
+
"""Provide tests for AntsApplyTransformsWarper."""
|
2
|
+
|
3
|
+
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
|
4
|
+
# License: AGPL
|
5
|
+
|
6
|
+
import socket
|
7
|
+
from pathlib import Path
|
8
|
+
from typing import List
|
9
|
+
|
10
|
+
import nibabel as nib
|
11
|
+
import pytest
|
12
|
+
|
13
|
+
from junifer.datagrabber import DMCC13Benchmark
|
14
|
+
from junifer.datareader import DefaultDataReader
|
15
|
+
from junifer.pipeline.utils import _check_ants
|
16
|
+
from junifer.preprocess.ants.ants_apply_transforms_warper import (
|
17
|
+
_AntsApplyTransformsWarper,
|
18
|
+
)
|
19
|
+
|
20
|
+
|
21
|
+
def test_AntsApplyTransformsWarper_init() -> None:
|
22
|
+
"""Test AntsApplyTransformsWarper init."""
|
23
|
+
ants_apply_transforms_warper = _AntsApplyTransformsWarper(
|
24
|
+
reference="T1w", on="BOLD"
|
25
|
+
)
|
26
|
+
assert ants_apply_transforms_warper.ref == "T1w"
|
27
|
+
assert ants_apply_transforms_warper.on == "BOLD"
|
28
|
+
assert ants_apply_transforms_warper._on == ["BOLD"]
|
29
|
+
|
30
|
+
|
31
|
+
def test_AntsApplyTransformsWarper_get_valid_inputs() -> None:
|
32
|
+
"""Test AntsApplyTransformsWarper get_valid_inputs."""
|
33
|
+
ants_apply_transforms_warper = _AntsApplyTransformsWarper(
|
34
|
+
reference="T1w", on="BOLD"
|
35
|
+
)
|
36
|
+
assert ants_apply_transforms_warper.get_valid_inputs() == ["BOLD"]
|
37
|
+
|
38
|
+
|
39
|
+
@pytest.mark.parametrize(
|
40
|
+
"input_",
|
41
|
+
[
|
42
|
+
["BOLD", "T1w", "Warp"],
|
43
|
+
["BOLD", "T1w"],
|
44
|
+
["BOLD"],
|
45
|
+
],
|
46
|
+
)
|
47
|
+
def test_AntsApplyTransformsWarper_get_output_type(input_: List[str]) -> None:
|
48
|
+
"""Test AntsApplyTransformsWarper get_output_type.
|
49
|
+
|
50
|
+
Parameters
|
51
|
+
----------
|
52
|
+
input_ : list of str
|
53
|
+
The input data types.
|
54
|
+
|
55
|
+
"""
|
56
|
+
ants_apply_transforms_warper = _AntsApplyTransformsWarper(
|
57
|
+
reference="T1w", on="BOLD"
|
58
|
+
)
|
59
|
+
assert ants_apply_transforms_warper.get_output_type(input_) == input_
|
60
|
+
|
61
|
+
|
62
|
+
@pytest.mark.skipif(
|
63
|
+
_check_ants() is False, reason="requires ANTs to be in PATH"
|
64
|
+
)
|
65
|
+
@pytest.mark.skipif(
|
66
|
+
socket.gethostname() != "juseless",
|
67
|
+
reason="only for juseless",
|
68
|
+
)
|
69
|
+
def test_AntsApplyTransformsWarper__run_apply_transform() -> None:
|
70
|
+
"""Test AntsApplyTransformsWarper _run_apply_transform."""
|
71
|
+
with DMCC13Benchmark(
|
72
|
+
types=["BOLD", "T1w", "Warp"],
|
73
|
+
sessions=["wave1bas"],
|
74
|
+
tasks=["Rest"],
|
75
|
+
phase_encodings=["AP"],
|
76
|
+
runs=["1"],
|
77
|
+
native_t1w=True,
|
78
|
+
) as dg:
|
79
|
+
# Read data
|
80
|
+
element_data = DefaultDataReader().fit_transform(
|
81
|
+
dg[("f9057kp", "wave1bas", "Rest", "AP", "1")]
|
82
|
+
)
|
83
|
+
# Preprocess data
|
84
|
+
warped_data, resampled_ref_path = _AntsApplyTransformsWarper(
|
85
|
+
reference="T1w", on="BOLD"
|
86
|
+
)._run_apply_transforms(
|
87
|
+
input_data=element_data["BOLD"],
|
88
|
+
ref_path=element_data["T1w"]["path"],
|
89
|
+
warp_path=element_data["Warp"]["path"],
|
90
|
+
)
|
91
|
+
assert isinstance(warped_data, nib.Nifti1Image)
|
92
|
+
assert isinstance(resampled_ref_path, Path)
|
93
|
+
|
94
|
+
|
95
|
+
@pytest.mark.skipif(
|
96
|
+
_check_ants() is False, reason="requires ANTs to be in PATH"
|
97
|
+
)
|
98
|
+
@pytest.mark.skipif(
|
99
|
+
socket.gethostname() != "juseless",
|
100
|
+
reason="only for juseless",
|
101
|
+
)
|
102
|
+
def test_AntsApplyTransformsWarper_preprocess() -> None:
|
103
|
+
"""Test AntsApplyTransformsWarper preprocess."""
|
104
|
+
with DMCC13Benchmark(
|
105
|
+
types=["BOLD", "T1w", "Warp"],
|
106
|
+
sessions=["wave1bas"],
|
107
|
+
tasks=["Rest"],
|
108
|
+
phase_encodings=["AP"],
|
109
|
+
runs=["1"],
|
110
|
+
native_t1w=True,
|
111
|
+
) as dg:
|
112
|
+
# Read data
|
113
|
+
element_data = DefaultDataReader().fit_transform(
|
114
|
+
dg[("f9057kp", "wave1bas", "Rest", "AP", "1")]
|
115
|
+
)
|
116
|
+
# Preprocess data
|
117
|
+
data_type, data = _AntsApplyTransformsWarper(
|
118
|
+
reference="T1w", on="BOLD"
|
119
|
+
).preprocess(
|
120
|
+
input=element_data["BOLD"],
|
121
|
+
extra_input=element_data,
|
122
|
+
)
|
123
|
+
assert isinstance(data_type, str)
|
124
|
+
assert isinstance(data, dict)
|
@@ -15,6 +15,7 @@ from typing import (
|
|
15
15
|
|
16
16
|
from ..api.decorators import register_preprocessor
|
17
17
|
from ..utils import logger, raise_error
|
18
|
+
from .ants.ants_apply_transforms_warper import _AntsApplyTransformsWarper
|
18
19
|
from .base import BasePreprocessor
|
19
20
|
from .fsl.apply_warper import _ApplyWarper
|
20
21
|
|
@@ -35,8 +36,13 @@ class BOLDWarper(BasePreprocessor):
|
|
35
36
|
] = [
|
36
37
|
{
|
37
38
|
"name": "fsl",
|
38
|
-
"optional":
|
39
|
-
"commands": ["applywarp"],
|
39
|
+
"optional": True,
|
40
|
+
"commands": ["flirt", "applywarp"],
|
41
|
+
},
|
42
|
+
{
|
43
|
+
"name": "ants",
|
44
|
+
"optional": True,
|
45
|
+
"commands": ["ResampleImage", "antsApplyTransforms"],
|
40
46
|
},
|
41
47
|
]
|
42
48
|
|
@@ -105,6 +111,8 @@ class BOLDWarper(BasePreprocessor):
|
|
105
111
|
------
|
106
112
|
ValueError
|
107
113
|
If ``extra_input`` is None.
|
114
|
+
RuntimeError
|
115
|
+
If warp / transformation file extension is not ".mat" or ".h5".
|
108
116
|
|
109
117
|
"""
|
110
118
|
logger.debug("Warping BOLD using BOLDWarper")
|
@@ -114,11 +122,34 @@ class BOLDWarper(BasePreprocessor):
|
|
114
122
|
f"No extra input provided, requires `Warp` and `{self.ref}` "
|
115
123
|
"data types in particular."
|
116
124
|
)
|
117
|
-
#
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
125
|
+
# Check for warp file type to use correct tool
|
126
|
+
warp_file_ext = extra_input["Warp"]["path"].suffix
|
127
|
+
if warp_file_ext == ".mat":
|
128
|
+
logger.debug("Using FSL with BOLDWarper")
|
129
|
+
# Initialize ApplyWarper for computation
|
130
|
+
apply_warper = _ApplyWarper(reference=self.ref, on="BOLD")
|
131
|
+
# Replace original BOLD data with warped BOLD data
|
132
|
+
_, input = apply_warper.preprocess(
|
133
|
+
input=input,
|
134
|
+
extra_input=extra_input,
|
135
|
+
)
|
136
|
+
elif warp_file_ext == ".h5":
|
137
|
+
logger.debug("Using ANTs with BOLDWarper")
|
138
|
+
# Initialize AntsApplyTransformsWarper for computation
|
139
|
+
ants_apply_transforms_warper = _AntsApplyTransformsWarper(
|
140
|
+
reference=self.ref, on="BOLD"
|
141
|
+
)
|
142
|
+
# Replace original BOLD data with warped BOLD data
|
143
|
+
_, input = ants_apply_transforms_warper.preprocess(
|
144
|
+
input=input,
|
145
|
+
extra_input=extra_input,
|
146
|
+
)
|
147
|
+
else:
|
148
|
+
raise_error(
|
149
|
+
msg=(
|
150
|
+
"Unknown warp / transformation file extension: "
|
151
|
+
f"{warp_file_ext}"
|
152
|
+
),
|
153
|
+
klass=RuntimeError,
|
154
|
+
)
|
124
155
|
return "BOLD", input
|
@@ -3,7 +3,6 @@
|
|
3
3
|
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
|
4
4
|
# License: AGPL
|
5
5
|
|
6
|
-
import subprocess
|
7
6
|
from pathlib import Path
|
8
7
|
from typing import (
|
9
8
|
TYPE_CHECKING,
|
@@ -14,14 +13,13 @@ from typing import (
|
|
14
13
|
Optional,
|
15
14
|
Tuple,
|
16
15
|
Union,
|
17
|
-
cast,
|
18
16
|
)
|
19
17
|
|
20
18
|
import nibabel as nib
|
21
19
|
import numpy as np
|
22
20
|
|
23
21
|
from ...pipeline import WorkDirManager
|
24
|
-
from ...utils import logger, raise_error
|
22
|
+
from ...utils import logger, raise_error, run_ext_cmd
|
25
23
|
from ..base import BasePreprocessor
|
26
24
|
|
27
25
|
|
@@ -54,7 +52,7 @@ class _ApplyWarper(BasePreprocessor):
|
|
54
52
|
{
|
55
53
|
"name": "fsl",
|
56
54
|
"optional": False,
|
57
|
-
"commands": ["applywarp"],
|
55
|
+
"commands": ["flirt", "applywarp"],
|
58
56
|
},
|
59
57
|
]
|
60
58
|
|
@@ -125,11 +123,6 @@ class _ApplyWarper(BasePreprocessor):
|
|
125
123
|
pathlib.Path
|
126
124
|
The path to the resampled reference image.
|
127
125
|
|
128
|
-
Raises
|
129
|
-
------
|
130
|
-
RuntimeError
|
131
|
-
If FSL commands fail.
|
132
|
-
|
133
126
|
"""
|
134
127
|
# Get the min of the voxel sizes from input and use it as the
|
135
128
|
# resolution
|
@@ -150,29 +143,7 @@ class _ApplyWarper(BasePreprocessor):
|
|
150
143
|
f"-out {flirt_out_path.resolve()}",
|
151
144
|
]
|
152
145
|
# Call flirt
|
153
|
-
|
154
|
-
logger.info(f"flirt command to be executed: {flirt_cmd_str}")
|
155
|
-
flirt_process = subprocess.run(
|
156
|
-
flirt_cmd_str,
|
157
|
-
stdin=subprocess.DEVNULL,
|
158
|
-
stdout=subprocess.PIPE,
|
159
|
-
stderr=subprocess.STDOUT,
|
160
|
-
shell=True, # needed for respecting $PATH
|
161
|
-
check=False,
|
162
|
-
)
|
163
|
-
if flirt_process.returncode == 0:
|
164
|
-
logger.info(
|
165
|
-
"flirt succeeded with the following output: "
|
166
|
-
f"{flirt_process.stdout}"
|
167
|
-
)
|
168
|
-
else:
|
169
|
-
raise_error(
|
170
|
-
msg="flirt failed with the following error: "
|
171
|
-
f"{flirt_process.stdout}",
|
172
|
-
klass=RuntimeError,
|
173
|
-
)
|
174
|
-
|
175
|
-
# TODO(synchon): Modify reference or not?
|
146
|
+
run_ext_cmd(name="flirt", cmd=flirt_cmd)
|
176
147
|
|
177
148
|
# Create a tempfile for warped output
|
178
149
|
applywarp_out_path = tempdir / "input_warped.nii.gz"
|
@@ -186,34 +157,12 @@ class _ApplyWarper(BasePreprocessor):
|
|
186
157
|
f"-o {applywarp_out_path.resolve()}",
|
187
158
|
]
|
188
159
|
# Call applywarp
|
189
|
-
|
190
|
-
logger.info(f"applywarp command to be executed: {applywarp_cmd_str}")
|
191
|
-
applywarp_process = subprocess.run(
|
192
|
-
applywarp_cmd_str, # string needed with shell=True
|
193
|
-
stdin=subprocess.DEVNULL,
|
194
|
-
stdout=subprocess.PIPE,
|
195
|
-
stderr=subprocess.STDOUT,
|
196
|
-
shell=True, # needed for respecting $PATH
|
197
|
-
check=False,
|
198
|
-
)
|
199
|
-
if applywarp_process.returncode == 0:
|
200
|
-
logger.info(
|
201
|
-
"applywarp succeeded with the following output: "
|
202
|
-
f"{applywarp_process.stdout}"
|
203
|
-
)
|
204
|
-
else:
|
205
|
-
raise_error(
|
206
|
-
msg="applywarp failed with the following error: "
|
207
|
-
f"{applywarp_process.stdout}",
|
208
|
-
klass=RuntimeError,
|
209
|
-
)
|
160
|
+
run_ext_cmd(name="applywarp", cmd=applywarp_cmd)
|
210
161
|
|
211
162
|
# Load nifti
|
212
163
|
output_img = nib.load(applywarp_out_path)
|
213
164
|
|
214
|
-
#
|
215
|
-
output_img = cast("Nifti1Image", output_img)
|
216
|
-
return output_img, flirt_out_path
|
165
|
+
return output_img, flirt_out_path # type: ignore
|
217
166
|
|
218
167
|
def preprocess(
|
219
168
|
self,
|