junifer 0.0.5__py3-none-any.whl → 0.0.5.dev11__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 -10
- 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/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.dev11.dist-info}/METADATA +16 -17
- junifer-0.0.5.dev11.dist-info/RECORD +259 -0
- {junifer-0.0.5.dist-info → junifer-0.0.5.dev11.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/preprocess/smoothing/__init__.py +0 -9
- junifer/preprocess/smoothing/_afni_smoothing.py +0 -119
- junifer/preprocess/smoothing/_fsl_smoothing.py +0 -116
- junifer/preprocess/smoothing/_nilearn_smoothing.py +0 -69
- junifer/preprocess/smoothing/smoothing.py +0 -174
- junifer/preprocess/smoothing/tests/test_smoothing.py +0 -94
- junifer-0.0.5.dist-info/RECORD +0 -275
- {junifer-0.0.5.dist-info → junifer-0.0.5.dev11.dist-info}/AUTHORS.rst +0 -0
- {junifer-0.0.5.dist-info → junifer-0.0.5.dev11.dist-info}/LICENSE.md +0 -0
- {junifer-0.0.5.dist-info → junifer-0.0.5.dev11.dist-info}/entry_points.txt +0 -0
- {junifer-0.0.5.dist-info → junifer-0.0.5.dev11.dist-info}/top_level.txt +0 -0
@@ -27,9 +27,6 @@ from ...utils import logger, raise_error
|
|
27
27
|
from ..base import BasePreprocessor
|
28
28
|
|
29
29
|
|
30
|
-
__all__ = ["fMRIPrepConfoundRemover"]
|
31
|
-
|
32
|
-
|
33
30
|
FMRIPREP_BASICS = {
|
34
31
|
"motion": [
|
35
32
|
"trans_x",
|
@@ -206,7 +203,9 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
|
|
206
203
|
"include it in the future",
|
207
204
|
klass=ValueError,
|
208
205
|
)
|
209
|
-
super().__init__(
|
206
|
+
super().__init__(
|
207
|
+
on="BOLD", required_data_types=["BOLD", "BOLD_confounds"]
|
208
|
+
)
|
210
209
|
|
211
210
|
def get_valid_inputs(self) -> List[str]:
|
212
211
|
"""Get valid data types for input.
|
@@ -362,7 +361,7 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
|
|
362
361
|
Parameters
|
363
362
|
----------
|
364
363
|
input : dict
|
365
|
-
Dictionary containing the ``
|
364
|
+
Dictionary containing the ``BOLD_confounds`` value from the
|
366
365
|
Junifer Data object.
|
367
366
|
|
368
367
|
Returns
|
@@ -371,6 +370,7 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
|
|
371
370
|
Dataframe containing the relevant confounds.
|
372
371
|
|
373
372
|
"""
|
373
|
+
|
374
374
|
confounds_format = input["format"]
|
375
375
|
if confounds_format == "adhoc":
|
376
376
|
self._map_adhoc_to_fmriprep(input)
|
@@ -416,42 +416,50 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
|
|
416
416
|
def _validate_data(
|
417
417
|
self,
|
418
418
|
input: Dict[str, Any],
|
419
|
+
extra_input: Optional[Dict[str, Any]] = None,
|
419
420
|
) -> None:
|
420
421
|
"""Validate input data.
|
421
422
|
|
422
423
|
Parameters
|
423
424
|
----------
|
424
425
|
input : dict
|
425
|
-
Dictionary containing the ``BOLD``
|
426
|
+
Dictionary containing the ``BOLD`` value from the
|
426
427
|
Junifer Data object.
|
428
|
+
extra_input : dict, optional
|
429
|
+
Dictionary containing the rest of the Junifer Data object. Must
|
430
|
+
include the ``BOLD_confounds`` key.
|
427
431
|
|
428
432
|
Raises
|
429
433
|
------
|
430
434
|
ValueError
|
431
|
-
If ``
|
432
|
-
if ``"
|
433
|
-
if ``"
|
435
|
+
If ``extra_input`` is None or
|
436
|
+
if ``"BOLD_confounds"`` is not found in ``extra_input`` or
|
437
|
+
if ``"data"`` key is not found in ``"BOLD_confounds"`` or
|
438
|
+
if ``"data"`` is not pandas.DataFrame or
|
434
439
|
if image time series and confounds have different lengths or
|
435
|
-
if ``format
|
436
|
-
``"
|
437
|
-
|
438
|
-
|
439
|
-
if invalid confounds format is found.
|
440
|
+
if ``"format"`` is not found in ``"BOLD_confounds"`` or
|
441
|
+
if ``format = "adhoc"`` and ``"mappings"`` key or ``"fmriprep"``
|
442
|
+
key or correct fMRIPrep mappings or required fMRIPrep mappings are
|
443
|
+
not found or if invalid confounds format is found.
|
440
444
|
|
441
445
|
"""
|
442
446
|
# BOLD must be 4D niimg
|
443
447
|
check_niimg_4d(input["data"])
|
444
|
-
# Check for
|
445
|
-
if
|
446
|
-
raise_error(
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
448
|
+
# Check for extra inputs
|
449
|
+
if extra_input is None:
|
450
|
+
raise_error(
|
451
|
+
"No extra input provided, requires `BOLD_confounds` data type "
|
452
|
+
"in particular"
|
453
|
+
)
|
454
|
+
if "BOLD_confounds" not in extra_input:
|
455
|
+
raise_error("`BOLD_confounds` data type not provided")
|
456
|
+
if "data" not in extra_input["BOLD_confounds"]:
|
457
|
+
raise_error("`BOLD_confounds.data` not provided")
|
458
|
+
# Confounds must be a pandas.DataFrame
|
459
|
+
if not isinstance(extra_input["BOLD_confounds"]["data"], pd.DataFrame):
|
460
|
+
raise_error("`BOLD_confounds.data` must be a `pandas.DataFrame`")
|
461
|
+
|
462
|
+
confound_df = extra_input["BOLD_confounds"]["data"]
|
455
463
|
bold_img = input["data"]
|
456
464
|
if bold_img.get_fdata().shape[3] != len(confound_df):
|
457
465
|
raise_error(
|
@@ -461,19 +469,23 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
|
|
461
469
|
)
|
462
470
|
|
463
471
|
# Check format
|
464
|
-
|
472
|
+
if "format" not in extra_input["BOLD_confounds"]:
|
473
|
+
raise_error("`BOLD_confounds.format` not provided")
|
474
|
+
t_format = extra_input["BOLD_confounds"]["format"]
|
465
475
|
if t_format == "adhoc":
|
466
|
-
if "mappings" not in
|
476
|
+
if "mappings" not in extra_input["BOLD_confounds"]:
|
467
477
|
raise_error(
|
468
|
-
"`
|
469
|
-
"`
|
478
|
+
"`BOLD_confounds.mappings` need to be set when "
|
479
|
+
"`BOLD_confounds.format == 'adhoc'`"
|
470
480
|
)
|
471
|
-
if "fmriprep" not in
|
481
|
+
if "fmriprep" not in extra_input["BOLD_confounds"]["mappings"]:
|
472
482
|
raise_error(
|
473
|
-
"`
|
474
|
-
"`
|
483
|
+
"`BOLD_confounds.mappings.fmriprep` need to be set when "
|
484
|
+
"`BOLD_confounds.format == 'adhoc'`"
|
475
485
|
)
|
476
|
-
fmriprep_mappings =
|
486
|
+
fmriprep_mappings = extra_input["BOLD_confounds"]["mappings"][
|
487
|
+
"fmriprep"
|
488
|
+
]
|
477
489
|
wrong_names = [
|
478
490
|
x
|
479
491
|
for x in fmriprep_mappings.values()
|
@@ -513,22 +525,22 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
|
|
513
525
|
input : dict
|
514
526
|
A single input from the Junifer Data object to preprocess.
|
515
527
|
extra_input : dict, optional
|
516
|
-
The other fields in the Junifer Data object.
|
528
|
+
The other fields in the Junifer Data object. Must include the
|
529
|
+
``BOLD_confounds`` key.
|
517
530
|
|
518
531
|
Returns
|
519
532
|
-------
|
520
533
|
dict
|
521
|
-
The computed result as dictionary.
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
object.
|
534
|
+
The computed result as dictionary.
|
535
|
+
dict or None
|
536
|
+
If `self.masks` is not None, then the target data computed mask is
|
537
|
+
returned else None.
|
526
538
|
|
527
539
|
"""
|
528
540
|
# Validate data
|
529
|
-
self._validate_data(input)
|
541
|
+
self._validate_data(input, extra_input)
|
530
542
|
# Pick confounds
|
531
|
-
confounds_df = self._pick_confounds(
|
543
|
+
confounds_df = self._pick_confounds(extra_input["BOLD_confounds"]) # type: ignore
|
532
544
|
# Get BOLD data
|
533
545
|
bold_img = input["data"]
|
534
546
|
# Set t_r
|
@@ -541,6 +553,7 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
|
|
541
553
|
)
|
542
554
|
# Set mask data
|
543
555
|
mask_img = None
|
556
|
+
bold_mask_dict = None
|
544
557
|
if self.masks is not None:
|
545
558
|
logger.debug(f"Masking with {self.masks}")
|
546
559
|
mask_img = get_mask(
|
@@ -548,15 +561,15 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
|
|
548
561
|
)
|
549
562
|
# Return the BOLD mask and link it to the BOLD data type dict;
|
550
563
|
# this allows to use "inherit" down the pipeline
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
564
|
+
if extra_input is not None:
|
565
|
+
logger.debug("Setting `BOLD.mask_item`")
|
566
|
+
input["mask_item"] = "BOLD_mask"
|
567
|
+
bold_mask_dict = {
|
568
|
+
"BOLD_mask": {
|
555
569
|
"data": mask_img,
|
556
570
|
"space": input["space"],
|
557
571
|
}
|
558
572
|
}
|
559
|
-
)
|
560
573
|
# Clean image
|
561
574
|
logger.info("Cleaning image using nilearn")
|
562
575
|
logger.debug(f"\tdetrend: {self.detrend}")
|
@@ -574,4 +587,4 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
|
|
574
587
|
mask_img=mask_img,
|
575
588
|
)
|
576
589
|
|
577
|
-
return input,
|
590
|
+
return input, bold_mask_dict
|
@@ -20,7 +20,6 @@ from junifer.testing import get_testing_data
|
|
20
20
|
from junifer.testing.datagrabbers import (
|
21
21
|
OasisVBMTestingDataGrabber,
|
22
22
|
PartlyCloudyTestingDataGrabber,
|
23
|
-
SPMAuditoryTestingDataGrabber,
|
24
23
|
)
|
25
24
|
|
26
25
|
|
@@ -43,10 +42,35 @@ def test_fMRIPrepConfoundRemover_init() -> None:
|
|
43
42
|
@pytest.mark.parametrize(
|
44
43
|
"input_",
|
45
44
|
[
|
45
|
+
["T1w"],
|
46
46
|
["BOLD"],
|
47
47
|
["T1w", "BOLD"],
|
48
48
|
],
|
49
49
|
)
|
50
|
+
def test_fMRIPrepConfoundRemover_validate_input_errors(
|
51
|
+
input_: List[str],
|
52
|
+
) -> None:
|
53
|
+
"""Test errors for fMRIPrepConfoundRemover validate_input.
|
54
|
+
|
55
|
+
Parameters
|
56
|
+
----------
|
57
|
+
input_ : list of str
|
58
|
+
The input data types.
|
59
|
+
|
60
|
+
"""
|
61
|
+
confound_remover = fMRIPrepConfoundRemover()
|
62
|
+
|
63
|
+
with pytest.raises(ValueError, match="not have the required data"):
|
64
|
+
confound_remover.validate_input(input_)
|
65
|
+
|
66
|
+
|
67
|
+
@pytest.mark.parametrize(
|
68
|
+
"input_",
|
69
|
+
[
|
70
|
+
["BOLD", "BOLD_confounds"],
|
71
|
+
["T1w", "BOLD", "BOLD_confounds"],
|
72
|
+
],
|
73
|
+
)
|
50
74
|
def test_fMRIPrepConfoundRemover_validate_input(input_: List[str]) -> None:
|
51
75
|
"""Test fMRIPrepConfoundRemover validate_input.
|
52
76
|
|
@@ -278,13 +302,13 @@ def test_fMRIPRepConfoundRemover__pick_confounds_fmriprep() -> None:
|
|
278
302
|
with PartlyCloudyTestingDataGrabber() as dg:
|
279
303
|
input = dg["sub-01"]
|
280
304
|
input = reader.fit_transform(input)
|
281
|
-
out1 = confound_remover._pick_confounds(input["
|
305
|
+
out1 = confound_remover._pick_confounds(input["BOLD_confounds"])
|
282
306
|
assert set(out1.columns) == {*fmriprep_all_vars, "spike"}
|
283
307
|
|
284
308
|
with PartlyCloudyTestingDataGrabber(reduce_confounds=False) as dg:
|
285
309
|
input = dg["sub-01"]
|
286
310
|
input = reader.fit_transform(input)
|
287
|
-
out2 = confound_remover._pick_confounds(input["
|
311
|
+
out2 = confound_remover._pick_confounds(input["BOLD_confounds"])
|
288
312
|
assert set(out2.columns) == {*fmriprep_all_vars, "spike"}
|
289
313
|
|
290
314
|
assert_frame_equal(out1, out2)
|
@@ -324,106 +348,123 @@ def test_fMRIPRepConfoundRemover__pick_confounds_fmriprep_compute() -> None:
|
|
324
348
|
def test_fMRIPrepConfoundRemover__validate_data() -> None:
|
325
349
|
"""Test fMRIPrepConfoundRemover validate data."""
|
326
350
|
confound_remover = fMRIPrepConfoundRemover(strategy={"wm_csf": "full"})
|
327
|
-
|
351
|
+
|
328
352
|
with OasisVBMTestingDataGrabber() as dg:
|
329
353
|
element_data = DefaultDataReader().fit_transform(dg["sub-01"])
|
330
354
|
vbm = element_data["VBM_GM"]
|
331
355
|
with pytest.raises(
|
332
356
|
DimensionError, match="incompatible dimensionality"
|
333
357
|
):
|
334
|
-
confound_remover._validate_data(vbm)
|
335
|
-
|
336
|
-
with
|
358
|
+
confound_remover._validate_data(vbm, None)
|
359
|
+
|
360
|
+
with PartlyCloudyTestingDataGrabber(reduce_confounds=False) as dg:
|
337
361
|
element_data = DefaultDataReader().fit_transform(dg["sub-01"])
|
338
362
|
bold = element_data["BOLD"]
|
339
|
-
|
363
|
+
|
364
|
+
with pytest.raises(ValueError, match="No extra input"):
|
365
|
+
confound_remover._validate_data(bold, None)
|
340
366
|
with pytest.raises(
|
341
|
-
ValueError, match="`
|
367
|
+
ValueError, match="`BOLD_confounds` data type not provided"
|
342
368
|
):
|
343
|
-
confound_remover._validate_data(bold)
|
344
|
-
# Test confound data
|
345
|
-
bold["confounds"] = {}
|
369
|
+
confound_remover._validate_data(bold, {})
|
346
370
|
with pytest.raises(
|
347
|
-
ValueError, match="`
|
371
|
+
ValueError, match="`BOLD_confounds.data` not provided"
|
348
372
|
):
|
349
|
-
confound_remover._validate_data(bold)
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
373
|
+
confound_remover._validate_data(bold, {"BOLD_confounds": {}})
|
374
|
+
|
375
|
+
extra_input = {
|
376
|
+
"BOLD_confounds": {"data": "wrong"},
|
377
|
+
}
|
378
|
+
msg = "must be a `pandas.DataFrame`"
|
379
|
+
with pytest.raises(ValueError, match=msg):
|
380
|
+
confound_remover._validate_data(bold, extra_input)
|
381
|
+
|
382
|
+
extra_input = {"BOLD_confounds": {"data": pd.DataFrame()}}
|
356
383
|
with pytest.raises(ValueError, match="Image time series and"):
|
357
|
-
confound_remover._validate_data(bold)
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
# Test format
|
362
|
-
modified_bold = {
|
363
|
-
"data": element_data["BOLD"]["data"],
|
364
|
-
"confounds": {
|
365
|
-
"data": element_data["BOLD"]["confounds"]["data"],
|
366
|
-
"format": "adhoc",
|
367
|
-
},
|
384
|
+
confound_remover._validate_data(bold, extra_input)
|
385
|
+
|
386
|
+
extra_input = {
|
387
|
+
"BOLD_confounds": {"data": element_data["BOLD_confounds"]["data"]}
|
368
388
|
}
|
369
|
-
# Test incorrect format
|
370
|
-
modified_bold["confounds"].update({"format": "wrong"})
|
371
|
-
with pytest.raises(ValueError, match="Invalid confounds format"):
|
372
|
-
confound_remover._validate_data(modified_bold)
|
373
|
-
# Test missing mappings for adhoc
|
374
|
-
modified_bold["confounds"].update({"format": "adhoc"})
|
375
389
|
with pytest.raises(
|
376
|
-
ValueError, match="`
|
390
|
+
ValueError, match="`BOLD_confounds.format` not provided"
|
377
391
|
):
|
378
|
-
confound_remover._validate_data(
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
392
|
+
confound_remover._validate_data(bold, extra_input)
|
393
|
+
|
394
|
+
extra_input = {
|
395
|
+
"BOLD_confounds": {
|
396
|
+
"data": element_data["BOLD_confounds"]["data"],
|
397
|
+
"format": "wrong",
|
398
|
+
}
|
399
|
+
}
|
400
|
+
with pytest.raises(ValueError, match="Invalid confounds format"):
|
401
|
+
confound_remover._validate_data(bold, extra_input)
|
402
|
+
|
403
|
+
extra_input = {
|
404
|
+
"BOLD_confounds": {
|
405
|
+
"data": element_data["BOLD_confounds"]["data"],
|
406
|
+
"format": "adhoc",
|
407
|
+
}
|
408
|
+
}
|
409
|
+
with pytest.raises(ValueError, match="need to be set"):
|
410
|
+
confound_remover._validate_data(bold, extra_input)
|
411
|
+
|
412
|
+
extra_input = {
|
413
|
+
"BOLD_confounds": {
|
414
|
+
"data": element_data["BOLD_confounds"]["data"],
|
415
|
+
"format": "adhoc",
|
416
|
+
"mappings": {},
|
417
|
+
}
|
418
|
+
}
|
419
|
+
with pytest.raises(ValueError, match="need to be set"):
|
420
|
+
confound_remover._validate_data(bold, extra_input)
|
421
|
+
|
422
|
+
extra_input = {
|
423
|
+
"BOLD_confounds": {
|
424
|
+
"data": element_data["BOLD_confounds"]["data"],
|
425
|
+
"format": "adhoc",
|
389
426
|
"mappings": {
|
390
427
|
"fmriprep": {
|
391
428
|
"rot_x": "wrong",
|
392
429
|
"rot_y": "rot_z",
|
393
430
|
"rot_z": "rot_y",
|
394
|
-
}
|
395
|
-
}
|
431
|
+
}
|
432
|
+
},
|
396
433
|
}
|
397
|
-
|
434
|
+
}
|
398
435
|
with pytest.raises(ValueError, match=r"names: \['wrong'\]"):
|
399
|
-
confound_remover._validate_data(
|
400
|
-
|
401
|
-
|
402
|
-
{
|
436
|
+
confound_remover._validate_data(bold, extra_input)
|
437
|
+
|
438
|
+
extra_input = {
|
439
|
+
"BOLD_confounds": {
|
440
|
+
"data": element_data["BOLD_confounds"]["data"],
|
441
|
+
"format": "adhoc",
|
403
442
|
"mappings": {
|
404
443
|
"fmriprep": {
|
405
444
|
"wrong": "rot_x",
|
406
445
|
"rot_y": "rot_z",
|
407
446
|
"rot_z": "rot_y",
|
408
|
-
}
|
409
|
-
}
|
447
|
+
}
|
448
|
+
},
|
410
449
|
}
|
411
|
-
|
450
|
+
}
|
412
451
|
with pytest.raises(ValueError, match=r"Missing columns: \['wrong'\]"):
|
413
|
-
confound_remover._validate_data(
|
414
|
-
|
415
|
-
|
416
|
-
{
|
452
|
+
confound_remover._validate_data(bold, extra_input)
|
453
|
+
|
454
|
+
extra_input = {
|
455
|
+
"BOLD_confounds": {
|
456
|
+
"data": element_data["BOLD_confounds"]["data"],
|
457
|
+
"format": "adhoc",
|
417
458
|
"mappings": {
|
418
459
|
"fmriprep": {
|
419
460
|
"rot_x": "rot_x",
|
420
461
|
"rot_y": "rot_z",
|
421
462
|
"rot_z": "rot_y",
|
422
|
-
}
|
423
|
-
}
|
463
|
+
}
|
464
|
+
},
|
424
465
|
}
|
425
|
-
|
426
|
-
confound_remover._validate_data(
|
466
|
+
}
|
467
|
+
confound_remover._validate_data(bold, extra_input)
|
427
468
|
|
428
469
|
|
429
470
|
def test_fMRIPrepConfoundRemover_preprocess() -> None:
|
@@ -435,9 +476,7 @@ def test_fMRIPrepConfoundRemover_preprocess() -> None:
|
|
435
476
|
element_data = DefaultDataReader().fit_transform(dg["sub-01"])
|
436
477
|
orig_bold = element_data["BOLD"]["data"].get_fdata().copy()
|
437
478
|
pre_input = element_data["BOLD"]
|
438
|
-
pre_extra_input = {
|
439
|
-
"BOLD": {"confounds": element_data["BOLD"]["confounds"]}
|
440
|
-
}
|
479
|
+
pre_extra_input = {"BOLD_confounds": element_data["BOLD_confounds"]}
|
441
480
|
output, _ = confound_remover.preprocess(pre_input, pre_extra_input)
|
442
481
|
trans_bold = output["data"].get_fdata()
|
443
482
|
# Transformation is in place
|
@@ -491,7 +530,7 @@ def test_fMRIPrepConfoundRemover_fit_transform() -> None:
|
|
491
530
|
assert t_meta["t_r"] is None
|
492
531
|
assert t_meta["masks"] is None
|
493
532
|
|
494
|
-
assert "
|
533
|
+
assert "BOLD_mask" not in output
|
495
534
|
|
496
535
|
assert "dependencies" in output["BOLD"]["meta"]
|
497
536
|
dependencies = output["BOLD"]["meta"]["dependencies"]
|
@@ -543,7 +582,9 @@ def test_fMRIPrepConfoundRemover_fit_transform_masks() -> None:
|
|
543
582
|
assert "threshold" in t_meta["masks"]["compute_brain_mask"]
|
544
583
|
assert t_meta["masks"]["compute_brain_mask"]["threshold"] == 0.2
|
545
584
|
|
546
|
-
assert "
|
585
|
+
assert "BOLD_mask" in output
|
586
|
+
assert "mask_item" in output["BOLD"]
|
587
|
+
assert output["BOLD"]["mask_item"] == "BOLD_mask"
|
547
588
|
|
548
589
|
assert "dependencies" in output["BOLD"]["meta"]
|
549
590
|
dependencies = output["BOLD"]["meta"]["dependencies"]
|
@@ -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
|