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
@@ -5,9 +5,8 @@
|
|
5
5
|
# Synchon Mandal <s.mandal@fz-juelich.de>
|
6
6
|
# License: AGPL
|
7
7
|
|
8
|
-
import
|
8
|
+
from typing import List
|
9
9
|
|
10
|
-
import nibabel as nib
|
11
10
|
import numpy as np
|
12
11
|
import pandas as pd
|
13
12
|
import pytest
|
@@ -40,39 +39,62 @@ def test_fMRIPrepConfoundRemover_init() -> None:
|
|
40
39
|
fMRIPrepConfoundRemover(strategy={"motion": "wrong"})
|
41
40
|
|
42
41
|
|
43
|
-
|
44
|
-
""
|
45
|
-
|
42
|
+
@pytest.mark.parametrize(
|
43
|
+
"input_",
|
44
|
+
[
|
45
|
+
["T1w"],
|
46
|
+
["BOLD"],
|
47
|
+
["T1w", "BOLD"],
|
48
|
+
],
|
49
|
+
)
|
50
|
+
def test_fMRIPrepConfoundRemover_validate_input_errors(
|
51
|
+
input_: List[str],
|
52
|
+
) -> None:
|
53
|
+
"""Test errors for fMRIPrepConfoundRemover validate_input.
|
46
54
|
|
47
|
-
|
55
|
+
Parameters
|
56
|
+
----------
|
57
|
+
input_ : list of str
|
58
|
+
The input data types.
|
48
59
|
|
49
|
-
|
50
|
-
|
51
|
-
confound_remover.validate_input(input)
|
60
|
+
"""
|
61
|
+
confound_remover = fMRIPrepConfoundRemover()
|
52
62
|
|
53
|
-
input = ["BOLD"]
|
54
63
|
with pytest.raises(ValueError, match="not have the required data"):
|
55
|
-
confound_remover.validate_input(
|
64
|
+
confound_remover.validate_input(input_)
|
56
65
|
|
57
|
-
input = ["BOLD", "T1w"]
|
58
|
-
with pytest.raises(ValueError, match="not have the required data"):
|
59
|
-
confound_remover.validate_input(input)
|
60
66
|
|
61
|
-
|
62
|
-
|
67
|
+
@pytest.mark.parametrize(
|
68
|
+
"input_",
|
69
|
+
[
|
70
|
+
["BOLD", "BOLD_confounds"],
|
71
|
+
["T1w", "BOLD", "BOLD_confounds"],
|
72
|
+
],
|
73
|
+
)
|
74
|
+
def test_fMRIPrepConfoundRemover_validate_input(input_: List[str]) -> None:
|
75
|
+
"""Test fMRIPrepConfoundRemover validate_input.
|
76
|
+
|
77
|
+
Parameters
|
78
|
+
----------
|
79
|
+
input_ : list of str
|
80
|
+
The input data types.
|
81
|
+
|
82
|
+
"""
|
83
|
+
confound_remover = fMRIPrepConfoundRemover()
|
84
|
+
confound_remover.validate_input(input_)
|
85
|
+
|
86
|
+
|
87
|
+
def test_fMRIPrepConfoundRemover_get_valid_inputs() -> None:
|
88
|
+
"""Test fMRIPrepConfoundRemover get_valid_inputs."""
|
89
|
+
confound_remover = fMRIPrepConfoundRemover()
|
90
|
+
assert confound_remover.get_valid_inputs() == ["BOLD"]
|
63
91
|
|
64
92
|
|
65
93
|
def test_fMRIPrepConfoundRemover_get_output_type() -> None:
|
66
|
-
"""Test fMRIPrepConfoundRemover
|
94
|
+
"""Test fMRIPrepConfoundRemover get_output_type."""
|
67
95
|
confound_remover = fMRIPrepConfoundRemover()
|
68
|
-
inputs = [
|
69
|
-
["BOLD", "T1w", "BOLD_confounds"],
|
70
|
-
["BOLD", "VBM_GM", "BOLD_confounds"],
|
71
|
-
["BOLD", "BOLD_confounds"],
|
72
|
-
]
|
73
96
|
# Confound remover works in place
|
74
|
-
|
75
|
-
assert confound_remover.get_output_type(input) == input
|
97
|
+
assert confound_remover.get_output_type("BOLD") == "BOLD"
|
76
98
|
|
77
99
|
|
78
100
|
def test_fMRIPrepConfoundRemover__map_adhoc_to_fmriprep() -> None:
|
@@ -259,7 +281,7 @@ def test_fMRIPrepConfoundRemover__pick_confounds_adhoc() -> None:
|
|
259
281
|
assert set(out.columns) == set(fmriprep_all_vars)
|
260
282
|
|
261
283
|
|
262
|
-
def
|
284
|
+
def test_fMRIPRepConfoundRemover__pick_confounds_fmriprep() -> None:
|
263
285
|
"""Test fMRIPrepConfoundRemover pick confounds on fmriprep confounds."""
|
264
286
|
confound_remover = fMRIPrepConfoundRemover(
|
265
287
|
strategy={"wm_csf": "full"}, spike=0.2
|
@@ -292,7 +314,7 @@ def test_FMRIPRepConfoundRemover__pick_confounds_fmriprep() -> None:
|
|
292
314
|
assert_frame_equal(out1, out2)
|
293
315
|
|
294
316
|
|
295
|
-
def
|
317
|
+
def test_fMRIPRepConfoundRemover__pick_confounds_fmriprep_compute() -> None:
|
296
318
|
"""Test if fmriprep returns the same derivatives/power2 as we compute."""
|
297
319
|
|
298
320
|
confound_remover = fMRIPrepConfoundRemover(strategy={"wm_csf": "full"})
|
@@ -326,78 +348,80 @@ def test_FMRIPRepConfoundRemover__pick_confounds_fmriprep_compute() -> None:
|
|
326
348
|
def test_fMRIPrepConfoundRemover__validate_data() -> None:
|
327
349
|
"""Test fMRIPrepConfoundRemover validate data."""
|
328
350
|
confound_remover = fMRIPrepConfoundRemover(strategy={"wm_csf": "full"})
|
329
|
-
|
351
|
+
|
330
352
|
with OasisVBMTestingDataGrabber() as dg:
|
331
|
-
|
332
|
-
|
333
|
-
new_input = input["VBM_GM"]
|
353
|
+
element_data = DefaultDataReader().fit_transform(dg["sub-01"])
|
354
|
+
vbm = element_data["VBM_GM"]
|
334
355
|
with pytest.raises(
|
335
356
|
DimensionError, match="incompatible dimensionality"
|
336
357
|
):
|
337
|
-
confound_remover._validate_data(
|
358
|
+
confound_remover._validate_data(vbm, None)
|
338
359
|
|
339
360
|
with PartlyCloudyTestingDataGrabber(reduce_confounds=False) as dg:
|
340
|
-
|
341
|
-
|
342
|
-
new_input = input["BOLD"]
|
361
|
+
element_data = DefaultDataReader().fit_transform(dg["sub-01"])
|
362
|
+
bold = element_data["BOLD"]
|
343
363
|
|
344
364
|
with pytest.raises(ValueError, match="No extra input"):
|
345
|
-
confound_remover._validate_data(
|
346
|
-
with pytest.raises(
|
347
|
-
|
365
|
+
confound_remover._validate_data(bold, None)
|
366
|
+
with pytest.raises(
|
367
|
+
ValueError, match="`BOLD_confounds` data type not provided"
|
368
|
+
):
|
369
|
+
confound_remover._validate_data(bold, {})
|
348
370
|
with pytest.raises(
|
349
|
-
ValueError, match="
|
371
|
+
ValueError, match="`BOLD_confounds.data` not provided"
|
350
372
|
):
|
351
|
-
confound_remover._validate_data(
|
373
|
+
confound_remover._validate_data(bold, {"BOLD_confounds": {}})
|
352
374
|
|
353
375
|
extra_input = {
|
354
376
|
"BOLD_confounds": {"data": "wrong"},
|
355
377
|
}
|
356
|
-
msg = "must be a pandas
|
378
|
+
msg = "must be a `pandas.DataFrame`"
|
357
379
|
with pytest.raises(ValueError, match=msg):
|
358
|
-
confound_remover._validate_data(
|
380
|
+
confound_remover._validate_data(bold, extra_input)
|
359
381
|
|
360
382
|
extra_input = {"BOLD_confounds": {"data": pd.DataFrame()}}
|
361
383
|
with pytest.raises(ValueError, match="Image time series and"):
|
362
|
-
confound_remover._validate_data(
|
384
|
+
confound_remover._validate_data(bold, extra_input)
|
363
385
|
|
364
386
|
extra_input = {
|
365
|
-
"BOLD_confounds": {"data":
|
387
|
+
"BOLD_confounds": {"data": element_data["BOLD_confounds"]["data"]}
|
366
388
|
}
|
367
|
-
with pytest.raises(
|
368
|
-
|
389
|
+
with pytest.raises(
|
390
|
+
ValueError, match="`BOLD_confounds.format` not provided"
|
391
|
+
):
|
392
|
+
confound_remover._validate_data(bold, extra_input)
|
369
393
|
|
370
394
|
extra_input = {
|
371
395
|
"BOLD_confounds": {
|
372
|
-
"data":
|
396
|
+
"data": element_data["BOLD_confounds"]["data"],
|
373
397
|
"format": "wrong",
|
374
398
|
}
|
375
399
|
}
|
376
|
-
with pytest.raises(ValueError, match="Invalid confounds format
|
377
|
-
confound_remover._validate_data(
|
400
|
+
with pytest.raises(ValueError, match="Invalid confounds format"):
|
401
|
+
confound_remover._validate_data(bold, extra_input)
|
378
402
|
|
379
403
|
extra_input = {
|
380
404
|
"BOLD_confounds": {
|
381
|
-
"data":
|
405
|
+
"data": element_data["BOLD_confounds"]["data"],
|
382
406
|
"format": "adhoc",
|
383
407
|
}
|
384
408
|
}
|
385
|
-
with pytest.raises(ValueError, match="
|
386
|
-
confound_remover._validate_data(
|
409
|
+
with pytest.raises(ValueError, match="need to be set"):
|
410
|
+
confound_remover._validate_data(bold, extra_input)
|
387
411
|
|
388
412
|
extra_input = {
|
389
413
|
"BOLD_confounds": {
|
390
|
-
"data":
|
414
|
+
"data": element_data["BOLD_confounds"]["data"],
|
391
415
|
"format": "adhoc",
|
392
416
|
"mappings": {},
|
393
417
|
}
|
394
418
|
}
|
395
|
-
with pytest.raises(ValueError, match="
|
396
|
-
confound_remover._validate_data(
|
419
|
+
with pytest.raises(ValueError, match="need to be set"):
|
420
|
+
confound_remover._validate_data(bold, extra_input)
|
397
421
|
|
398
422
|
extra_input = {
|
399
423
|
"BOLD_confounds": {
|
400
|
-
"data":
|
424
|
+
"data": element_data["BOLD_confounds"]["data"],
|
401
425
|
"format": "adhoc",
|
402
426
|
"mappings": {
|
403
427
|
"fmriprep": {
|
@@ -409,11 +433,11 @@ def test_fMRIPrepConfoundRemover__validate_data() -> None:
|
|
409
433
|
}
|
410
434
|
}
|
411
435
|
with pytest.raises(ValueError, match=r"names: \['wrong'\]"):
|
412
|
-
confound_remover._validate_data(
|
436
|
+
confound_remover._validate_data(bold, extra_input)
|
413
437
|
|
414
438
|
extra_input = {
|
415
439
|
"BOLD_confounds": {
|
416
|
-
"data":
|
440
|
+
"data": element_data["BOLD_confounds"]["data"],
|
417
441
|
"format": "adhoc",
|
418
442
|
"mappings": {
|
419
443
|
"fmriprep": {
|
@@ -425,11 +449,11 @@ def test_fMRIPrepConfoundRemover__validate_data() -> None:
|
|
425
449
|
}
|
426
450
|
}
|
427
451
|
with pytest.raises(ValueError, match=r"Missing columns: \['wrong'\]"):
|
428
|
-
confound_remover._validate_data(
|
452
|
+
confound_remover._validate_data(bold, extra_input)
|
429
453
|
|
430
454
|
extra_input = {
|
431
455
|
"BOLD_confounds": {
|
432
|
-
"data":
|
456
|
+
"data": element_data["BOLD_confounds"]["data"],
|
433
457
|
"format": "adhoc",
|
434
458
|
"mappings": {
|
435
459
|
"fmriprep": {
|
@@ -440,51 +464,25 @@ def test_fMRIPrepConfoundRemover__validate_data() -> None:
|
|
440
464
|
},
|
441
465
|
}
|
442
466
|
}
|
443
|
-
confound_remover._validate_data(
|
444
|
-
|
445
|
-
|
446
|
-
def test_fMRIPrepConfoundRemover__remove_confounds() -> None:
|
447
|
-
"""Test fMRIPrepConfoundRemover remove confounds."""
|
448
|
-
confound_remover = fMRIPrepConfoundRemover(
|
449
|
-
strategy={"wm_csf": "full"}, spike=0.2
|
450
|
-
)
|
451
|
-
reader = DefaultDataReader()
|
452
|
-
with PartlyCloudyTestingDataGrabber(reduce_confounds=False) as dg:
|
453
|
-
input = dg["sub-01"]
|
454
|
-
input = reader.fit_transform(input)
|
455
|
-
raw_bold = input["BOLD"]["data"]
|
456
|
-
extra_input = {k: v for k, v in input.items() if k != "BOLD"}
|
457
|
-
clean_bold = confound_remover._remove_confounds(
|
458
|
-
input=input["BOLD"], extra_input=extra_input
|
459
|
-
)
|
460
|
-
clean_bold = typing.cast(nib.Nifti1Image, clean_bold)
|
461
|
-
# TODO: Find a better way to test functionality here
|
462
|
-
assert (
|
463
|
-
clean_bold.header.get_zooms() # type: ignore
|
464
|
-
== raw_bold.header.get_zooms() # type: ignore
|
465
|
-
)
|
466
|
-
assert clean_bold.get_fdata().shape == raw_bold.get_fdata().shape
|
467
|
-
# TODO: Test confound remover with mask, needs #79 to be implemented
|
467
|
+
confound_remover._validate_data(bold, extra_input)
|
468
468
|
|
469
469
|
|
470
470
|
def test_fMRIPrepConfoundRemover_preprocess() -> None:
|
471
471
|
"""Test fMRIPrepConfoundRemover with all confounds present."""
|
472
|
-
|
473
|
-
# need reader for the data
|
474
|
-
reader = DefaultDataReader()
|
475
472
|
# All strategies full, no spike
|
476
473
|
confound_remover = fMRIPrepConfoundRemover()
|
477
474
|
|
478
475
|
with PartlyCloudyTestingDataGrabber(reduce_confounds=False) as dg:
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
key, output = confound_remover.preprocess(pre_input, pre_extra_input)
|
476
|
+
element_data = DefaultDataReader().fit_transform(dg["sub-01"])
|
477
|
+
orig_bold = element_data["BOLD"]["data"].get_fdata().copy()
|
478
|
+
pre_input = element_data["BOLD"]
|
479
|
+
pre_extra_input = {"BOLD_confounds": element_data["BOLD_confounds"]}
|
480
|
+
output, _ = confound_remover.preprocess(pre_input, pre_extra_input)
|
485
481
|
trans_bold = output["data"].get_fdata()
|
486
482
|
# Transformation is in place
|
487
|
-
assert_array_equal(
|
483
|
+
assert_array_equal(
|
484
|
+
trans_bold, element_data["BOLD"]["data"].get_fdata()
|
485
|
+
)
|
488
486
|
|
489
487
|
# Data should have the same shape
|
490
488
|
assert orig_bold.shape == trans_bold.shape
|
@@ -493,25 +491,22 @@ def test_fMRIPrepConfoundRemover_preprocess() -> None:
|
|
493
491
|
assert_raises(
|
494
492
|
AssertionError, assert_array_equal, orig_bold, trans_bold
|
495
493
|
)
|
496
|
-
assert key == "BOLD"
|
497
494
|
|
498
495
|
|
499
496
|
def test_fMRIPrepConfoundRemover_fit_transform() -> None:
|
500
497
|
"""Test fMRIPrepConfoundRemover with all confounds present."""
|
501
|
-
|
502
|
-
# need reader for the data
|
503
|
-
reader = DefaultDataReader()
|
504
498
|
# All strategies full, no spike
|
505
499
|
confound_remover = fMRIPrepConfoundRemover()
|
506
500
|
|
507
501
|
with PartlyCloudyTestingDataGrabber(reduce_confounds=False) as dg:
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
output = confound_remover.fit_transform(input)
|
502
|
+
element_data = DefaultDataReader().fit_transform(dg["sub-01"])
|
503
|
+
orig_bold = element_data["BOLD"]["data"].get_fdata().copy()
|
504
|
+
output = confound_remover.fit_transform(element_data)
|
512
505
|
trans_bold = output["BOLD"]["data"].get_fdata()
|
513
506
|
# Transformation is in place
|
514
|
-
assert_array_equal(
|
507
|
+
assert_array_equal(
|
508
|
+
trans_bold, element_data["BOLD"]["data"].get_fdata()
|
509
|
+
)
|
515
510
|
|
516
511
|
# Data should have the same shape
|
517
512
|
assert orig_bold.shape == trans_bold.shape
|
@@ -535,6 +530,8 @@ def test_fMRIPrepConfoundRemover_fit_transform() -> None:
|
|
535
530
|
assert t_meta["t_r"] is None
|
536
531
|
assert t_meta["masks"] is None
|
537
532
|
|
533
|
+
assert "BOLD_mask" not in output
|
534
|
+
|
538
535
|
assert "dependencies" in output["BOLD"]["meta"]
|
539
536
|
dependencies = output["BOLD"]["meta"]["dependencies"]
|
540
537
|
assert dependencies == {"numpy", "nilearn"}
|
@@ -542,22 +539,20 @@ def test_fMRIPrepConfoundRemover_fit_transform() -> None:
|
|
542
539
|
|
543
540
|
def test_fMRIPrepConfoundRemover_fit_transform_masks() -> None:
|
544
541
|
"""Test fMRIPrepConfoundRemover with all confounds present."""
|
545
|
-
|
546
|
-
# need reader for the data
|
547
|
-
reader = DefaultDataReader()
|
548
542
|
# All strategies full, no spike
|
549
543
|
confound_remover = fMRIPrepConfoundRemover(
|
550
544
|
masks={"compute_brain_mask": {"threshold": 0.2}}
|
551
545
|
)
|
552
546
|
|
553
547
|
with PartlyCloudyTestingDataGrabber(reduce_confounds=False) as dg:
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
output = confound_remover.fit_transform(input)
|
548
|
+
element_data = DefaultDataReader().fit_transform(dg["sub-01"])
|
549
|
+
orig_bold = element_data["BOLD"]["data"].get_fdata().copy()
|
550
|
+
output = confound_remover.fit_transform(element_data)
|
558
551
|
trans_bold = output["BOLD"]["data"].get_fdata()
|
559
552
|
# Transformation is in place
|
560
|
-
assert_array_equal(
|
553
|
+
assert_array_equal(
|
554
|
+
trans_bold, element_data["BOLD"]["data"].get_fdata()
|
555
|
+
)
|
561
556
|
|
562
557
|
# Data should have the same shape
|
563
558
|
assert orig_bold.shape == trans_bold.shape
|
@@ -0,0 +1,179 @@
|
|
1
|
+
"""Provide class for warping via FSL FLIRT."""
|
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 _ApplyWarper:
|
30
|
+
"""Class for warping NIfTI images via FSL FLIRT.
|
31
|
+
|
32
|
+
Wraps FSL FLIRT ``applywarp``.
|
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": "fsl",
|
51
|
+
"commands": ["flirt", "applywarp"],
|
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_applywarp(
|
64
|
+
self,
|
65
|
+
input_data: Dict,
|
66
|
+
ref_path: Path,
|
67
|
+
warp_path: Path,
|
68
|
+
) -> Tuple["Nifti1Image", Path]:
|
69
|
+
"""Run ``applywarp``.
|
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(prefix="applywarp")
|
94
|
+
|
95
|
+
# Create a tempfile for resampled reference output
|
96
|
+
flirt_out_path = tempdir / "reference_resampled.nii.gz"
|
97
|
+
# Set flirt command
|
98
|
+
flirt_cmd = [
|
99
|
+
"flirt",
|
100
|
+
"-interp spline",
|
101
|
+
f"-in {ref_path.resolve()}",
|
102
|
+
f"-ref {ref_path.resolve()}",
|
103
|
+
f"-applyisoxfm {resolution}",
|
104
|
+
f"-out {flirt_out_path.resolve()}",
|
105
|
+
]
|
106
|
+
# Call flirt
|
107
|
+
run_ext_cmd(name="flirt", cmd=flirt_cmd)
|
108
|
+
|
109
|
+
# Create a tempfile for warped output
|
110
|
+
applywarp_out_path = tempdir / "input_warped.nii.gz"
|
111
|
+
# Set applywarp command
|
112
|
+
applywarp_cmd = [
|
113
|
+
"applywarp",
|
114
|
+
"--interp=spline",
|
115
|
+
f"-i {input_data['path'].resolve()}",
|
116
|
+
f"-r {flirt_out_path.resolve()}", # use resampled reference
|
117
|
+
f"-w {warp_path.resolve()}",
|
118
|
+
f"-o {applywarp_out_path.resolve()}",
|
119
|
+
]
|
120
|
+
# Call applywarp
|
121
|
+
run_ext_cmd(name="applywarp", cmd=applywarp_cmd)
|
122
|
+
|
123
|
+
# Load nifti
|
124
|
+
output_img = nib.load(applywarp_out_path)
|
125
|
+
|
126
|
+
return output_img, flirt_out_path # type: ignore
|
127
|
+
|
128
|
+
def preprocess(
|
129
|
+
self,
|
130
|
+
input: Dict[str, Any],
|
131
|
+
extra_input: Optional[Dict[str, Any]] = None,
|
132
|
+
) -> Tuple[str, Dict[str, Any]]:
|
133
|
+
"""Preprocess.
|
134
|
+
|
135
|
+
Parameters
|
136
|
+
----------
|
137
|
+
input : dict
|
138
|
+
A single input from the Junifer Data object in which to preprocess.
|
139
|
+
extra_input : dict, optional
|
140
|
+
The other fields in the Junifer Data object. Must include the
|
141
|
+
``Warp`` and ``ref`` value's keys.
|
142
|
+
|
143
|
+
Returns
|
144
|
+
-------
|
145
|
+
str
|
146
|
+
The key to store the output in the Junifer Data object.
|
147
|
+
dict
|
148
|
+
The computed result as dictionary. This will be stored in the
|
149
|
+
Junifer Data object under the key ``data`` of the data type.
|
150
|
+
|
151
|
+
Raises
|
152
|
+
------
|
153
|
+
ValueError
|
154
|
+
If ``extra_input`` is None.
|
155
|
+
|
156
|
+
"""
|
157
|
+
logger.debug("Warping via FSL using ApplyWarper")
|
158
|
+
# Check for extra inputs
|
159
|
+
if extra_input is None:
|
160
|
+
raise_error(
|
161
|
+
f"No extra input provided, requires `Warp` and `{self.ref}` "
|
162
|
+
"data types in particular."
|
163
|
+
)
|
164
|
+
# Retrieve data type info to warp
|
165
|
+
to_warp_input = input
|
166
|
+
# Retrieve data type info to use as reference
|
167
|
+
ref_input = extra_input[self.ref]
|
168
|
+
# Retrieve Warp data
|
169
|
+
warp = extra_input["Warp"]
|
170
|
+
# Replace original data with warped data and add resampled reference
|
171
|
+
# path
|
172
|
+
input["data"], input["reference_path"] = self._run_applywarp(
|
173
|
+
input_data=to_warp_input,
|
174
|
+
ref_path=ref_input["path"],
|
175
|
+
warp_path=warp["path"],
|
176
|
+
)
|
177
|
+
# Use reference input's space as warped input's space
|
178
|
+
input["space"] = ref_input["space"]
|
179
|
+
return self.on, input
|
@@ -0,0 +1,45 @@
|
|
1
|
+
"""Provide tests for ApplyWarper."""
|
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 DataladHCP1200
|
11
|
+
from junifer.datareader import DefaultDataReader
|
12
|
+
from junifer.pipeline.utils import _check_fsl
|
13
|
+
from junifer.preprocess.fsl.apply_warper import _ApplyWarper
|
14
|
+
|
15
|
+
|
16
|
+
def test_ApplyWarper_init() -> None:
|
17
|
+
"""Test ApplyWarper init."""
|
18
|
+
apply_warper = _ApplyWarper(reference="T1w", on="BOLD")
|
19
|
+
assert apply_warper.ref == "T1w"
|
20
|
+
assert apply_warper.on == "BOLD"
|
21
|
+
|
22
|
+
|
23
|
+
@pytest.mark.skipif(_check_fsl() is False, reason="requires FSL to be in PATH")
|
24
|
+
@pytest.mark.skipif(
|
25
|
+
socket.gethostname() != "juseless",
|
26
|
+
reason="only for juseless",
|
27
|
+
)
|
28
|
+
def test_ApplyWarper_preprocess() -> None:
|
29
|
+
"""Test ApplyWarper preprocess."""
|
30
|
+
with DataladHCP1200(
|
31
|
+
tasks=["REST1"],
|
32
|
+
phase_encodings=["LR"],
|
33
|
+
ica_fix=True,
|
34
|
+
) as dg:
|
35
|
+
# Read data
|
36
|
+
element_data = DefaultDataReader().fit_transform(
|
37
|
+
dg[("100206", "REST1", "LR")]
|
38
|
+
)
|
39
|
+
# Preprocess data
|
40
|
+
data_type, data = _ApplyWarper(reference="T1w", on="BOLD").preprocess(
|
41
|
+
input=element_data["BOLD"],
|
42
|
+
extra_input=element_data,
|
43
|
+
)
|
44
|
+
assert isinstance(data_type, str)
|
45
|
+
assert isinstance(data, dict)
|