junifer 0.0.5__py3-none-any.whl → 0.0.5.dev24__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/__init__.py +0 -17
- junifer/_version.py +2 -2
- junifer/api/__init__.py +1 -4
- junifer/api/cli.py +1 -91
- junifer/api/decorators.py +0 -9
- junifer/api/functions.py +10 -56
- junifer/api/parser.py +0 -3
- junifer/api/queue_context/__init__.py +1 -4
- junifer/api/res/afni/run_afni_docker.sh +1 -1
- junifer/api/res/ants/run_ants_docker.sh +1 -1
- junifer/api/res/fsl/run_fsl_docker.sh +1 -1
- junifer/api/tests/test_api_utils.py +2 -4
- junifer/api/tests/test_cli.py +0 -83
- junifer/api/tests/test_functions.py +2 -27
- junifer/configs/__init__.py +1 -1
- junifer/configs/juseless/__init__.py +1 -4
- junifer/configs/juseless/datagrabbers/__init__.py +1 -10
- junifer/configs/juseless/datagrabbers/aomic_id1000_vbm.py +0 -3
- junifer/configs/juseless/datagrabbers/camcan_vbm.py +0 -3
- junifer/configs/juseless/datagrabbers/ixi_vbm.py +0 -3
- junifer/configs/juseless/datagrabbers/tests/test_ucla.py +3 -1
- junifer/configs/juseless/datagrabbers/ucla.py +9 -12
- junifer/configs/juseless/datagrabbers/ukb_vbm.py +0 -3
- junifer/data/__init__.py +1 -21
- junifer/data/coordinates.py +19 -10
- junifer/data/masks.py +87 -58
- junifer/data/parcellations.py +3 -14
- junifer/data/template_spaces.py +1 -4
- junifer/data/tests/test_masks.py +37 -26
- junifer/data/utils.py +0 -3
- junifer/datagrabber/__init__.py +1 -18
- junifer/datagrabber/aomic/__init__.py +0 -3
- junifer/datagrabber/aomic/id1000.py +37 -70
- junifer/datagrabber/aomic/piop1.py +36 -69
- junifer/datagrabber/aomic/piop2.py +38 -71
- junifer/datagrabber/aomic/tests/test_id1000.py +99 -44
- junifer/datagrabber/aomic/tests/test_piop1.py +108 -65
- junifer/datagrabber/aomic/tests/test_piop2.py +102 -45
- junifer/datagrabber/base.py +6 -13
- junifer/datagrabber/datalad_base.py +1 -13
- junifer/datagrabber/dmcc13_benchmark.py +53 -36
- junifer/datagrabber/hcp1200/__init__.py +0 -3
- junifer/datagrabber/hcp1200/datalad_hcp1200.py +0 -3
- junifer/datagrabber/hcp1200/hcp1200.py +1 -4
- junifer/datagrabber/multiple.py +6 -45
- junifer/datagrabber/pattern.py +62 -170
- junifer/datagrabber/pattern_datalad.py +12 -25
- junifer/datagrabber/tests/test_datagrabber_utils.py +218 -0
- junifer/datagrabber/tests/test_datalad_base.py +4 -4
- junifer/datagrabber/tests/test_dmcc13_benchmark.py +19 -46
- junifer/datagrabber/tests/test_multiple.py +84 -161
- junifer/datagrabber/tests/test_pattern.py +0 -45
- junifer/datagrabber/tests/test_pattern_datalad.py +4 -4
- junifer/datagrabber/utils.py +230 -0
- junifer/datareader/__init__.py +1 -4
- junifer/datareader/default.py +43 -95
- junifer/external/__init__.py +1 -1
- junifer/external/nilearn/__init__.py +1 -5
- junifer/external/nilearn/junifer_nifti_spheres_masker.py +9 -23
- junifer/external/nilearn/tests/test_junifer_nifti_spheres_masker.py +1 -76
- junifer/markers/__init__.py +1 -23
- junifer/markers/base.py +28 -68
- junifer/markers/collection.py +2 -10
- junifer/markers/complexity/__init__.py +0 -10
- junifer/markers/complexity/complexity_base.py +43 -26
- junifer/markers/complexity/hurst_exponent.py +0 -3
- junifer/markers/complexity/multiscale_entropy_auc.py +0 -3
- junifer/markers/complexity/perm_entropy.py +0 -3
- junifer/markers/complexity/range_entropy.py +0 -3
- junifer/markers/complexity/range_entropy_auc.py +0 -3
- junifer/markers/complexity/sample_entropy.py +0 -3
- junifer/markers/complexity/tests/test_hurst_exponent.py +3 -11
- junifer/markers/complexity/tests/test_multiscale_entropy_auc.py +3 -11
- junifer/markers/complexity/tests/test_perm_entropy.py +3 -11
- junifer/markers/complexity/tests/test_range_entropy.py +3 -11
- junifer/markers/complexity/tests/test_range_entropy_auc.py +3 -11
- junifer/markers/complexity/tests/test_sample_entropy.py +3 -11
- junifer/markers/complexity/tests/test_weighted_perm_entropy.py +3 -11
- junifer/markers/complexity/weighted_perm_entropy.py +0 -3
- junifer/markers/ets_rss.py +42 -27
- junifer/markers/falff/__init__.py +0 -3
- junifer/markers/falff/_afni_falff.py +2 -5
- junifer/markers/falff/_junifer_falff.py +0 -3
- junifer/markers/falff/falff_base.py +46 -20
- junifer/markers/falff/falff_parcels.py +27 -56
- junifer/markers/falff/falff_spheres.py +29 -60
- junifer/markers/falff/tests/test_falff_parcels.py +23 -39
- junifer/markers/falff/tests/test_falff_spheres.py +23 -39
- junifer/markers/functional_connectivity/__init__.py +0 -9
- junifer/markers/functional_connectivity/crossparcellation_functional_connectivity.py +60 -63
- junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +32 -45
- junifer/markers/functional_connectivity/edge_functional_connectivity_spheres.py +36 -49
- junifer/markers/functional_connectivity/functional_connectivity_base.py +70 -71
- junifer/markers/functional_connectivity/functional_connectivity_parcels.py +25 -34
- junifer/markers/functional_connectivity/functional_connectivity_spheres.py +30 -40
- junifer/markers/functional_connectivity/tests/test_crossparcellation_functional_connectivity.py +7 -11
- junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_parcels.py +7 -27
- junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_spheres.py +12 -28
- junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +11 -35
- junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +62 -36
- junifer/markers/parcel_aggregation.py +61 -47
- junifer/markers/reho/__init__.py +0 -3
- junifer/markers/reho/_afni_reho.py +2 -5
- junifer/markers/reho/_junifer_reho.py +1 -4
- junifer/markers/reho/reho_base.py +27 -8
- junifer/markers/reho/reho_parcels.py +17 -28
- junifer/markers/reho/reho_spheres.py +18 -27
- junifer/markers/reho/tests/test_reho_parcels.py +3 -8
- junifer/markers/reho/tests/test_reho_spheres.py +3 -8
- junifer/markers/sphere_aggregation.py +59 -43
- junifer/markers/temporal_snr/__init__.py +0 -3
- junifer/markers/temporal_snr/temporal_snr_base.py +32 -23
- junifer/markers/temporal_snr/temporal_snr_parcels.py +6 -9
- junifer/markers/temporal_snr/temporal_snr_spheres.py +6 -9
- junifer/markers/temporal_snr/tests/test_temporal_snr_parcels.py +3 -6
- junifer/markers/temporal_snr/tests/test_temporal_snr_spheres.py +3 -6
- junifer/markers/tests/test_collection.py +8 -9
- junifer/markers/tests/test_ets_rss.py +9 -15
- junifer/markers/tests/test_markers_base.py +18 -17
- junifer/markers/tests/test_parcel_aggregation.py +32 -93
- junifer/markers/tests/test_sphere_aggregation.py +19 -72
- junifer/onthefly/__init__.py +1 -4
- junifer/onthefly/read_transform.py +0 -3
- junifer/pipeline/__init__.py +1 -9
- junifer/pipeline/pipeline_step_mixin.py +4 -21
- junifer/pipeline/registry.py +0 -3
- junifer/pipeline/singleton.py +0 -3
- junifer/pipeline/tests/test_registry.py +1 -1
- junifer/pipeline/update_meta_mixin.py +0 -3
- junifer/pipeline/utils.py +1 -67
- junifer/pipeline/workdir_manager.py +0 -3
- junifer/preprocess/__init__.py +2 -9
- 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 +3 -6
- junifer/preprocess/bold_warper.py +265 -0
- junifer/preprocess/confounds/__init__.py +0 -3
- junifer/preprocess/confounds/fmriprep_confound_remover.py +60 -47
- junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py +113 -72
- 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/smoothing/__init__.py +0 -3
- junifer/preprocess/smoothing/_afni_smoothing.py +1 -1
- junifer/preprocess/tests/test_bold_warper.py +159 -0
- junifer/preprocess/warping/__init__.py +0 -3
- junifer/preprocess/warping/_ants_warper.py +0 -3
- junifer/preprocess/warping/_fsl_warper.py +0 -3
- junifer/stats.py +1 -4
- junifer/storage/__init__.py +1 -9
- junifer/storage/base.py +1 -40
- junifer/storage/hdf5.py +9 -71
- junifer/storage/pandas_base.py +0 -3
- junifer/storage/sqlite.py +0 -3
- junifer/storage/tests/test_hdf5.py +10 -82
- junifer/storage/utils.py +0 -9
- junifer/testing/__init__.py +1 -4
- junifer/testing/datagrabbers.py +6 -13
- junifer/testing/tests/test_partlycloudytesting_datagrabber.py +7 -7
- junifer/testing/utils.py +0 -3
- junifer/utils/__init__.py +2 -13
- junifer/utils/fs.py +0 -3
- junifer/utils/helpers.py +1 -32
- junifer/utils/logging.py +4 -33
- junifer/utils/tests/test_logging.py +0 -8
- {junifer-0.0.5.dist-info → junifer-0.0.5.dev24.dist-info}/METADATA +16 -17
- junifer-0.0.5.dev24.dist-info/RECORD +265 -0
- {junifer-0.0.5.dist-info → junifer-0.0.5.dev24.dist-info}/WHEEL +1 -1
- junifer/api/res/freesurfer/mri_binarize +0 -3
- junifer/api/res/freesurfer/mri_mc +0 -3
- junifer/api/res/freesurfer/mri_pretess +0 -3
- junifer/api/res/freesurfer/mris_convert +0 -3
- junifer/api/res/freesurfer/run_freesurfer_docker.sh +0 -61
- junifer/data/masks/ukb/UKB_15K_GM_template.nii.gz +0 -0
- junifer/datagrabber/pattern_validation_mixin.py +0 -388
- junifer/datagrabber/tests/test_pattern_validation_mixin.py +0 -249
- junifer/external/BrainPrint/brainprint/__init__.py +0 -4
- junifer/external/BrainPrint/brainprint/_version.py +0 -3
- junifer/external/BrainPrint/brainprint/asymmetry.py +0 -91
- junifer/external/BrainPrint/brainprint/brainprint.py +0 -441
- junifer/external/BrainPrint/brainprint/surfaces.py +0 -258
- junifer/external/BrainPrint/brainprint/utils/__init__.py +0 -1
- junifer/external/BrainPrint/brainprint/utils/_config.py +0 -112
- junifer/external/BrainPrint/brainprint/utils/utils.py +0 -188
- junifer/external/nilearn/junifer_connectivity_measure.py +0 -483
- junifer/external/nilearn/tests/test_junifer_connectivity_measure.py +0 -1089
- junifer/markers/brainprint.py +0 -459
- junifer/markers/tests/test_brainprint.py +0 -58
- junifer-0.0.5.dist-info/RECORD +0 -275
- {junifer-0.0.5.dist-info → junifer-0.0.5.dev24.dist-info}/AUTHORS.rst +0 -0
- {junifer-0.0.5.dist-info → junifer-0.0.5.dev24.dist-info}/LICENSE.md +0 -0
- {junifer-0.0.5.dist-info → junifer-0.0.5.dev24.dist-info}/entry_points.txt +0 -0
- {junifer-0.0.5.dist-info → junifer-0.0.5.dev24.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,185 @@
|
|
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
|
+
|
24
|
+
|
25
|
+
if TYPE_CHECKING:
|
26
|
+
from nibabel import Nifti1Image
|
27
|
+
|
28
|
+
|
29
|
+
class _AntsApplyTransformsWarper:
|
30
|
+
"""Class for warping NIfTI images via ANTs antsApplyTransforms.
|
31
|
+
|
32
|
+
Warps ANTs ``antsApplyTransforms``.
|
33
|
+
|
34
|
+
Parameters
|
35
|
+
----------
|
36
|
+
reference : str
|
37
|
+
The data type to use as reference for warping.
|
38
|
+
on : str
|
39
|
+
The data type to use for warping.
|
40
|
+
|
41
|
+
Raises
|
42
|
+
------
|
43
|
+
ValueError
|
44
|
+
If a list was passed for ``on``.
|
45
|
+
|
46
|
+
"""
|
47
|
+
|
48
|
+
_EXT_DEPENDENCIES: ClassVar[List[Dict[str, Union[str, List[str]]]]] = [
|
49
|
+
{
|
50
|
+
"name": "ants",
|
51
|
+
"commands": ["ResampleImage", "antsApplyTransforms"],
|
52
|
+
},
|
53
|
+
]
|
54
|
+
|
55
|
+
def __init__(self, reference: str, on: str) -> None:
|
56
|
+
"""Initialize the class."""
|
57
|
+
self.ref = reference
|
58
|
+
# Check only single data type is passed
|
59
|
+
if isinstance(on, list):
|
60
|
+
raise_error("Can only work on single data type, list was passed.")
|
61
|
+
self.on = on
|
62
|
+
|
63
|
+
def _run_apply_transforms(
|
64
|
+
self,
|
65
|
+
input_data: Dict,
|
66
|
+
ref_path: Path,
|
67
|
+
warp_path: Path,
|
68
|
+
) -> Tuple["Nifti1Image", Path]:
|
69
|
+
"""Run ``antsApplyTransforms``.
|
70
|
+
|
71
|
+
Parameters
|
72
|
+
----------
|
73
|
+
input_data : dict
|
74
|
+
The input data.
|
75
|
+
ref_path : pathlib.Path
|
76
|
+
The path to the reference file.
|
77
|
+
warp_path : pathlib.Path
|
78
|
+
The path to the warp file.
|
79
|
+
|
80
|
+
Returns
|
81
|
+
-------
|
82
|
+
Niimg-like object
|
83
|
+
The warped input image.
|
84
|
+
pathlib.Path
|
85
|
+
The path to the resampled reference image.
|
86
|
+
|
87
|
+
"""
|
88
|
+
# Get the min of the voxel sizes from input and use it as the
|
89
|
+
# resolution
|
90
|
+
resolution = np.min(input_data["data"].header.get_zooms()[:3])
|
91
|
+
|
92
|
+
# Create element-specific tempdir for storing post-warping assets
|
93
|
+
tempdir = WorkDirManager().get_element_tempdir(
|
94
|
+
prefix="applytransforms"
|
95
|
+
)
|
96
|
+
|
97
|
+
# Create a tempfile for resampled reference output
|
98
|
+
resample_image_out_path = tempdir / "reference_resampled.nii.gz"
|
99
|
+
# Set ResampleImage command
|
100
|
+
resample_image_cmd = [
|
101
|
+
"ResampleImage",
|
102
|
+
"3", # image dimension
|
103
|
+
f"{ref_path.resolve()}",
|
104
|
+
f"{resample_image_out_path.resolve()}",
|
105
|
+
f"{resolution}x{resolution}x{resolution}",
|
106
|
+
"0", # option for spacing and not size
|
107
|
+
"3 3", # Lanczos windowed sinc
|
108
|
+
]
|
109
|
+
# Call ResampleImage
|
110
|
+
run_ext_cmd(name="ResampleImage", cmd=resample_image_cmd)
|
111
|
+
|
112
|
+
# Create a tempfile for warped output
|
113
|
+
apply_transforms_out_path = tempdir / "input_warped.nii.gz"
|
114
|
+
# Set antsApplyTransforms command
|
115
|
+
apply_transforms_cmd = [
|
116
|
+
"antsApplyTransforms",
|
117
|
+
"-d 3",
|
118
|
+
"-e 3",
|
119
|
+
"-n LanczosWindowedSinc",
|
120
|
+
f"-i {input_data['path'].resolve()}",
|
121
|
+
# use resampled reference
|
122
|
+
f"-r {resample_image_out_path.resolve()}",
|
123
|
+
f"-t {warp_path.resolve()}",
|
124
|
+
f"-o {apply_transforms_out_path.resolve()}",
|
125
|
+
]
|
126
|
+
# Call antsApplyTransforms
|
127
|
+
run_ext_cmd(name="antsApplyTransforms", cmd=apply_transforms_cmd)
|
128
|
+
|
129
|
+
# Load nifti
|
130
|
+
output_img = nib.load(apply_transforms_out_path)
|
131
|
+
|
132
|
+
return output_img, resample_image_out_path # type: ignore
|
133
|
+
|
134
|
+
def preprocess(
|
135
|
+
self,
|
136
|
+
input: Dict[str, Any],
|
137
|
+
extra_input: Optional[Dict[str, Any]] = None,
|
138
|
+
) -> Tuple[str, Dict[str, Any]]:
|
139
|
+
"""Preprocess.
|
140
|
+
|
141
|
+
Parameters
|
142
|
+
----------
|
143
|
+
input : dict
|
144
|
+
A single input from the Junifer Data object in which to preprocess.
|
145
|
+
extra_input : dict, optional
|
146
|
+
The other fields in the Junifer Data object. Must include the
|
147
|
+
``Warp`` and ``ref`` value's keys.
|
148
|
+
|
149
|
+
Returns
|
150
|
+
-------
|
151
|
+
str
|
152
|
+
The key to store the output in the Junifer Data object.
|
153
|
+
dict
|
154
|
+
The computed result as dictionary. This will be stored in the
|
155
|
+
Junifer Data object under the key ``data`` of the data type.
|
156
|
+
|
157
|
+
Raises
|
158
|
+
------
|
159
|
+
ValueError
|
160
|
+
If ``extra_input`` is None.
|
161
|
+
|
162
|
+
"""
|
163
|
+
logger.debug("Warping via ANTs using antsApplyTransforms")
|
164
|
+
# Check for extra inputs
|
165
|
+
if extra_input is None:
|
166
|
+
raise_error(
|
167
|
+
f"No extra input provided, requires `Warp` and `{self.ref}` "
|
168
|
+
"data types in particular."
|
169
|
+
)
|
170
|
+
# Retrieve data type info to warp
|
171
|
+
to_warp_input = input
|
172
|
+
# Retrieve data type info to use as reference
|
173
|
+
ref_input = extra_input[self.ref]
|
174
|
+
# Retrieve Warp data
|
175
|
+
warp = extra_input["Warp"]
|
176
|
+
# Replace original data with warped data and add resampled reference
|
177
|
+
# path
|
178
|
+
input["data"], input["reference_path"] = self._run_apply_transforms(
|
179
|
+
input_data=to_warp_input,
|
180
|
+
ref_path=ref_input["path"],
|
181
|
+
warp_path=warp["path"],
|
182
|
+
)
|
183
|
+
# Use reference input's space as warped input's space
|
184
|
+
input["space"] = ref_input["space"]
|
185
|
+
return self.on, input
|
@@ -0,0 +1,56 @@
|
|
1
|
+
"""Provide tests for AntsApplyTransformsWarper."""
|
2
|
+
|
3
|
+
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
|
4
|
+
# License: AGPL
|
5
|
+
|
6
|
+
import socket
|
7
|
+
|
8
|
+
import pytest
|
9
|
+
|
10
|
+
from junifer.datagrabber import DMCC13Benchmark
|
11
|
+
from junifer.datareader import DefaultDataReader
|
12
|
+
from junifer.pipeline.utils import _check_ants
|
13
|
+
from junifer.preprocess.ants.ants_apply_transforms_warper import (
|
14
|
+
_AntsApplyTransformsWarper,
|
15
|
+
)
|
16
|
+
|
17
|
+
|
18
|
+
def test_AntsApplyTransformsWarper_init() -> None:
|
19
|
+
"""Test AntsApplyTransformsWarper init."""
|
20
|
+
ants_apply_transforms_warper = _AntsApplyTransformsWarper(
|
21
|
+
reference="T1w", on="BOLD"
|
22
|
+
)
|
23
|
+
assert ants_apply_transforms_warper.ref == "T1w"
|
24
|
+
assert ants_apply_transforms_warper.on == "BOLD"
|
25
|
+
|
26
|
+
|
27
|
+
@pytest.mark.skipif(
|
28
|
+
_check_ants() is False, reason="requires ANTs to be in PATH"
|
29
|
+
)
|
30
|
+
@pytest.mark.skipif(
|
31
|
+
socket.gethostname() != "juseless",
|
32
|
+
reason="only for juseless",
|
33
|
+
)
|
34
|
+
def test_AntsApplyTransformsWarper_preprocess() -> None:
|
35
|
+
"""Test AntsApplyTransformsWarper preprocess."""
|
36
|
+
with DMCC13Benchmark(
|
37
|
+
types=["BOLD", "T1w", "Warp"],
|
38
|
+
sessions=["ses-wave1bas"],
|
39
|
+
tasks=["Rest"],
|
40
|
+
phase_encodings=["AP"],
|
41
|
+
runs=["1"],
|
42
|
+
native_t1w=True,
|
43
|
+
) as dg:
|
44
|
+
# Read data
|
45
|
+
element_data = DefaultDataReader().fit_transform(
|
46
|
+
dg[("sub-f9057kp", "ses-wave1bas", "Rest", "AP", "1")]
|
47
|
+
)
|
48
|
+
# Preprocess data
|
49
|
+
data_type, data = _AntsApplyTransformsWarper(
|
50
|
+
reference="T1w", on="BOLD"
|
51
|
+
).preprocess(
|
52
|
+
input=element_data["BOLD"],
|
53
|
+
extra_input=element_data,
|
54
|
+
)
|
55
|
+
assert isinstance(data_type, str)
|
56
|
+
assert isinstance(data, dict)
|
junifer/preprocess/base.py
CHANGED
@@ -11,9 +11,6 @@ from ..pipeline import PipelineStepMixin, UpdateMetaMixin
|
|
11
11
|
from ..utils import logger, raise_error
|
12
12
|
|
13
13
|
|
14
|
-
__all__ = ["BasePreprocessor"]
|
15
|
-
|
16
|
-
|
17
14
|
class BasePreprocessor(ABC, PipelineStepMixin, UpdateMetaMixin):
|
18
15
|
"""Abstract base class for all preprocessors.
|
19
16
|
|
@@ -139,7 +136,7 @@ class BasePreprocessor(ABC, PipelineStepMixin, UpdateMetaMixin):
|
|
139
136
|
A single input from the Junifer Data object to preprocess.
|
140
137
|
extra_input : dict, optional
|
141
138
|
The other fields in the Junifer Data object. Useful for accessing
|
142
|
-
other data
|
139
|
+
other data kind that needs to be used in the computation. For
|
143
140
|
example, the confound removers can make use of the
|
144
141
|
confounds if available (default None).
|
145
142
|
|
@@ -149,8 +146,8 @@ class BasePreprocessor(ABC, PipelineStepMixin, UpdateMetaMixin):
|
|
149
146
|
The computed result as dictionary.
|
150
147
|
dict or None
|
151
148
|
Extra "helper" data types as dictionary to add to the Junifer Data
|
152
|
-
object.
|
153
|
-
be passed.
|
149
|
+
object. For example, computed BOLD mask can be passed via this.
|
150
|
+
If no new "helper" data types is created, None is to be passed.
|
154
151
|
|
155
152
|
"""
|
156
153
|
raise_error(
|
@@ -0,0 +1,265 @@
|
|
1
|
+
"""Provide class for warping BOLD to other template spaces."""
|
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
|
+
Optional,
|
12
|
+
Tuple,
|
13
|
+
Type,
|
14
|
+
Union,
|
15
|
+
)
|
16
|
+
|
17
|
+
import nibabel as nib
|
18
|
+
from templateflow import api as tflow
|
19
|
+
|
20
|
+
from ..api.decorators import register_preprocessor
|
21
|
+
from ..data import get_template, get_xfm
|
22
|
+
from ..pipeline import WorkDirManager
|
23
|
+
from ..utils import logger, raise_error, run_ext_cmd
|
24
|
+
from .ants.ants_apply_transforms_warper import _AntsApplyTransformsWarper
|
25
|
+
from .base import BasePreprocessor
|
26
|
+
from .fsl.apply_warper import _ApplyWarper
|
27
|
+
|
28
|
+
|
29
|
+
@register_preprocessor
|
30
|
+
class BOLDWarper(BasePreprocessor):
|
31
|
+
"""Class for warping BOLD NIfTI images.
|
32
|
+
|
33
|
+
.. deprecated:: 0.0.3
|
34
|
+
`BOLDWarper` will be removed in v0.0.4, it is replaced by
|
35
|
+
`SpaceWarper` because the latter works also with T1w data.
|
36
|
+
|
37
|
+
Parameters
|
38
|
+
----------
|
39
|
+
using : {"fsl", "ants"}
|
40
|
+
Implementation to use for warping:
|
41
|
+
|
42
|
+
* "fsl" : Use FSL's ``applywarp``
|
43
|
+
* "afni" : Use ANTs' ``antsApplyTransforms``
|
44
|
+
|
45
|
+
reference : str
|
46
|
+
The data type to use as reference for warping, can be either a data
|
47
|
+
type like "T1w" or a template space like "MNI152NLin2009cAsym".
|
48
|
+
|
49
|
+
Raises
|
50
|
+
------
|
51
|
+
ValueError
|
52
|
+
If ``using`` is invalid or
|
53
|
+
if ``reference`` is invalid.
|
54
|
+
|
55
|
+
Notes
|
56
|
+
-----
|
57
|
+
If you are setting ``reference`` to a template space like
|
58
|
+
"MNI152NLin2009cAsym", make sure ANTs is available for the
|
59
|
+
transformation else it will fail during runtime. It is tricky to validate
|
60
|
+
this beforehand and difficult to enforce this as a requirement, hence the
|
61
|
+
heads-up.
|
62
|
+
|
63
|
+
"""
|
64
|
+
|
65
|
+
_CONDITIONAL_DEPENDENCIES: ClassVar[List[Dict[str, Union[str, Type]]]] = [
|
66
|
+
{
|
67
|
+
"using": "fsl",
|
68
|
+
"depends_on": _ApplyWarper,
|
69
|
+
},
|
70
|
+
{
|
71
|
+
"using": "ants",
|
72
|
+
"depends_on": _AntsApplyTransformsWarper,
|
73
|
+
},
|
74
|
+
]
|
75
|
+
|
76
|
+
def __init__(self, using: str, reference: str) -> None:
|
77
|
+
"""Initialize the class."""
|
78
|
+
# Validate `using` parameter
|
79
|
+
valid_using = [dep["using"] for dep in self._CONDITIONAL_DEPENDENCIES]
|
80
|
+
if using not in valid_using:
|
81
|
+
raise_error(
|
82
|
+
f"Invalid value for `using`, should be one of: {valid_using}"
|
83
|
+
)
|
84
|
+
self.using = using
|
85
|
+
self.ref = reference
|
86
|
+
# Initialize superclass based on reference
|
87
|
+
if self.ref == "T1w":
|
88
|
+
super().__init__(
|
89
|
+
on="BOLD", required_data_types=["BOLD", self.ref, "Warp"]
|
90
|
+
)
|
91
|
+
elif self.ref in tflow.templates():
|
92
|
+
super().__init__(on="BOLD", required_data_types=["BOLD"])
|
93
|
+
else:
|
94
|
+
raise_error(f"Unknown reference: {self.ref}")
|
95
|
+
|
96
|
+
def get_valid_inputs(self) -> List[str]:
|
97
|
+
"""Get valid data types for input.
|
98
|
+
|
99
|
+
Returns
|
100
|
+
-------
|
101
|
+
list of str
|
102
|
+
The list of data types that can be used as input for this
|
103
|
+
preprocessor.
|
104
|
+
|
105
|
+
"""
|
106
|
+
return ["BOLD"]
|
107
|
+
|
108
|
+
def get_output_type(self, input_type: str) -> str:
|
109
|
+
"""Get output type.
|
110
|
+
|
111
|
+
Parameters
|
112
|
+
----------
|
113
|
+
input_type : str
|
114
|
+
The data type input to the preprocessor.
|
115
|
+
|
116
|
+
Returns
|
117
|
+
-------
|
118
|
+
str
|
119
|
+
The data type output by the preprocessor.
|
120
|
+
|
121
|
+
"""
|
122
|
+
# Does not add any new keys
|
123
|
+
return input_type
|
124
|
+
|
125
|
+
def preprocess(
|
126
|
+
self,
|
127
|
+
input: Dict[str, Any],
|
128
|
+
extra_input: Optional[Dict[str, Any]] = None,
|
129
|
+
) -> Tuple[Dict[str, Any], Optional[Dict[str, Dict[str, Any]]]]:
|
130
|
+
"""Preprocess.
|
131
|
+
|
132
|
+
Parameters
|
133
|
+
----------
|
134
|
+
input : dict
|
135
|
+
The BOLD input from the Junifer Data object.
|
136
|
+
extra_input : dict, optional
|
137
|
+
The other fields in the Junifer Data object. Must include the
|
138
|
+
``Warp`` and ``ref`` value's keys if native space transformation is
|
139
|
+
needed.
|
140
|
+
|
141
|
+
Returns
|
142
|
+
-------
|
143
|
+
dict
|
144
|
+
The computed result as dictionary.
|
145
|
+
None
|
146
|
+
Extra "helper" data types as dictionary to add to the Junifer Data
|
147
|
+
object.
|
148
|
+
|
149
|
+
Raises
|
150
|
+
------
|
151
|
+
ValueError
|
152
|
+
If ``extra_input`` is None when transforming to native space
|
153
|
+
i.e., using "T1w" as reference.
|
154
|
+
RuntimeError
|
155
|
+
If warp / transformation file extension is not ".mat" or ".h5"
|
156
|
+
when transforming to native space or
|
157
|
+
if the BOLD data is in the correct space and does not require
|
158
|
+
warping.
|
159
|
+
|
160
|
+
"""
|
161
|
+
logger.info(f"Warping BOLD to {self.ref} space using BOLDWarper")
|
162
|
+
# Transform to native space
|
163
|
+
if self.ref == "T1w":
|
164
|
+
# Check for extra inputs
|
165
|
+
if extra_input is None:
|
166
|
+
raise_error(
|
167
|
+
"No extra input provided, requires `Warp` and "
|
168
|
+
f"`{self.ref}` data types in particular."
|
169
|
+
)
|
170
|
+
# Check for warp file type to use correct tool
|
171
|
+
warp_file_ext = extra_input["Warp"]["path"].suffix
|
172
|
+
if warp_file_ext == ".mat":
|
173
|
+
logger.debug("Using FSL with BOLDWarper")
|
174
|
+
# Initialize ApplyWarper for computation
|
175
|
+
apply_warper = _ApplyWarper(reference=self.ref, on="BOLD")
|
176
|
+
# Replace original BOLD data with warped BOLD data
|
177
|
+
_, input = apply_warper.preprocess(
|
178
|
+
input=input,
|
179
|
+
extra_input=extra_input,
|
180
|
+
)
|
181
|
+
elif warp_file_ext == ".h5":
|
182
|
+
logger.debug("Using ANTs with BOLDWarper")
|
183
|
+
# Initialize AntsApplyTransformsWarper for computation
|
184
|
+
ants_apply_transforms_warper = _AntsApplyTransformsWarper(
|
185
|
+
reference=self.ref, on="BOLD"
|
186
|
+
)
|
187
|
+
# Replace original BOLD data with warped BOLD data
|
188
|
+
_, input = ants_apply_transforms_warper.preprocess(
|
189
|
+
input=input,
|
190
|
+
extra_input=extra_input,
|
191
|
+
)
|
192
|
+
else:
|
193
|
+
raise_error(
|
194
|
+
msg=(
|
195
|
+
"Unknown warp / transformation file extension: "
|
196
|
+
f"{warp_file_ext}"
|
197
|
+
),
|
198
|
+
klass=RuntimeError,
|
199
|
+
)
|
200
|
+
# Transform to template space
|
201
|
+
else:
|
202
|
+
# Check pre-requirements for space manipulation
|
203
|
+
if self.ref == input["space"]:
|
204
|
+
raise_error(
|
205
|
+
(
|
206
|
+
f"Skipped warping as the BOLD data is in {self.ref} "
|
207
|
+
"space which would mean that you can remove the "
|
208
|
+
"BOLDWarper from the preprocess step."
|
209
|
+
),
|
210
|
+
klass=RuntimeError,
|
211
|
+
)
|
212
|
+
else:
|
213
|
+
# Get xfm file
|
214
|
+
xfm_file_path = get_xfm(src=input["space"], dst=self.ref)
|
215
|
+
# Get template space image
|
216
|
+
template_space_img = get_template(
|
217
|
+
space=self.ref,
|
218
|
+
target_data=input,
|
219
|
+
extra_input=None,
|
220
|
+
)
|
221
|
+
|
222
|
+
# Create component-scoped tempdir
|
223
|
+
tempdir = WorkDirManager().get_tempdir(prefix="boldwarper")
|
224
|
+
# Create element-scoped tempdir so that warped BOLD is
|
225
|
+
# available later as nibabel stores file path reference for
|
226
|
+
# loading on computation
|
227
|
+
element_tempdir = WorkDirManager().get_element_tempdir(
|
228
|
+
prefix="boldwarper"
|
229
|
+
)
|
230
|
+
|
231
|
+
# Save template
|
232
|
+
template_space_img_path = tempdir / f"{self.ref}_T1w.nii.gz"
|
233
|
+
nib.save(template_space_img, template_space_img_path)
|
234
|
+
|
235
|
+
# Create a tempfile for warped output
|
236
|
+
warped_bold_path = (
|
237
|
+
element_tempdir
|
238
|
+
/ f"bold_warped_from_{input['space']}_to_{self.ref}.nii.gz"
|
239
|
+
)
|
240
|
+
|
241
|
+
logger.debug(
|
242
|
+
f"Using ANTs to warp BOLD "
|
243
|
+
f"from {input['space']} to {self.ref}"
|
244
|
+
)
|
245
|
+
# Set antsApplyTransforms command
|
246
|
+
apply_transforms_cmd = [
|
247
|
+
"antsApplyTransforms",
|
248
|
+
"-d 3",
|
249
|
+
"-e 3",
|
250
|
+
"-n LanczosWindowedSinc",
|
251
|
+
f"-i {input['path'].resolve()}",
|
252
|
+
f"-r {template_space_img_path.resolve()}",
|
253
|
+
f"-t {xfm_file_path.resolve()}",
|
254
|
+
f"-o {warped_bold_path.resolve()}",
|
255
|
+
]
|
256
|
+
# Call antsApplyTransforms
|
257
|
+
run_ext_cmd(
|
258
|
+
name="antsApplyTransforms", cmd=apply_transforms_cmd
|
259
|
+
)
|
260
|
+
|
261
|
+
# Modify target data
|
262
|
+
input["data"] = nib.load(warped_bold_path)
|
263
|
+
input["space"] = self.ref
|
264
|
+
|
265
|
+
return input, None
|