From 7c9f3a5831fb879bce94dd36e9092a776f736b89 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Tue, 26 Oct 2021 11:49:02 +0200 Subject: [PATCH 1/4] FIX: Implement ``B0FieldIdentifier`` / ``B0FieldSource`` Start with the implementation of the new BIDS metadata entries, to facilitate more reliable estimation and correction with fieldmaps. Resolves: #246. --- sdcflows/utils/wrangler.py | 104 ++++++++++++++++++++++--------------- 1 file changed, 61 insertions(+), 43 deletions(-) diff --git a/sdcflows/utils/wrangler.py b/sdcflows/utils/wrangler.py index 9d119b3f98..db603c5c4b 100644 --- a/sdcflows/utils/wrangler.py +++ b/sdcflows/utils/wrangler.py @@ -217,6 +217,7 @@ def find_estimators(*, layout, subject, fmapless=True, force_fmapless=False): """ from .. import fieldmaps as fm from bids.layout import Query + from bids.exceptions import BIDSEntityError base_entities = { "subject": subject, @@ -232,57 +233,74 @@ def find_estimators(*, layout, subject, fmapless=True, force_fmapless=False): estimators = [] - # Set up B0 fieldmap strategies: - for fmap in layout.get(suffix=["fieldmap", "phasediff", "phase1"], **base_entities): - e = fm.FieldmapEstimation( - fm.FieldmapFile(fmap.path, metadata=fmap.get_metadata()) - ) - estimators.append(e) - - # A bunch of heuristics to select EPI fieldmaps - sessions = layout.get_sessions() - acqs = tuple(layout.get_acquisitions(suffix="epi") + [None]) - contrasts = tuple(layout.get_ceagents(suffix="epi") + [None]) + b0_ids = None + with suppress(BIDSEntityError): + b0_ids = layout.get_B0FieldIdentifiers() - for ses, acq, ce in product(sessions or (None,), acqs, contrasts): + for b0_id in b0_ids: + # Found B0FieldIdentifier metadata entries entities = base_entities.copy() - entities.update( - {"suffix": "epi", "session": ses, "acquisition": acq, "ceagent": ce} - ) - dirs = layout.get_directions(**entities) - if len(dirs) > 1: + entities["B0FieldIdentifier"] = b0_id + + _e = fm.FieldmapEstimation([ + fm.FieldmapFile(fmap.path, metadata=fmap.get_metadata()) + for fmap in layout.get(**entities) + ]) + estimators.append(_e) + + # As no B0FieldIdentifier(s) were found, try several heuristics + if not estimators: + # Set up B0 fieldmap strategies: + for fmap in layout.get(suffix=["fieldmap", "phasediff", "phase1"], **base_entities): e = fm.FieldmapEstimation( - [ - fm.FieldmapFile(fmap.path, metadata=fmap.get_metadata()) - for fmap in layout.get(direction=dirs, **entities) - ] + fm.FieldmapFile(fmap.path, metadata=fmap.get_metadata()) ) estimators.append(e) - # At this point, only single-PE _epi files WITH ``IntendedFor`` can be automatically processed - # (this will be easier with bids-standard/bids-specification#622 in). - has_intended = tuple() - with suppress(ValueError): - has_intended = layout.get(suffix="epi", IntendedFor=Query.ANY, **base_entities) - - for epi_fmap in has_intended: - if epi_fmap.path in fm._estimators.sources: - continue # skip EPI images already considered above - - targets = [epi_fmap] + [ - layout.get_file(str(subject_root / intent)) - for intent in epi_fmap.get_metadata()["IntendedFor"] - ] - - epi_sources = [] - for fmap in targets: - with suppress(fm.MetadataError): - epi_sources.append( - fm.FieldmapFile(fmap.path, metadata=fmap.get_metadata()) + # A bunch of heuristics to select EPI fieldmaps + sessions = layout.get_sessions() + acqs = tuple(layout.get_acquisitions(suffix="epi") + [None]) + contrasts = tuple(layout.get_ceagents(suffix="epi") + [None]) + + for ses, acq, ce in product(sessions or (None,), acqs, contrasts): + entities = base_entities.copy() + entities.update( + {"suffix": "epi", "session": ses, "acquisition": acq, "ceagent": ce} + ) + dirs = layout.get_directions(**entities) + if len(dirs) > 1: + e = fm.FieldmapEstimation( + [ + fm.FieldmapFile(fmap.path, metadata=fmap.get_metadata()) + for fmap in layout.get(direction=dirs, **entities) + ] ) + estimators.append(e) + + # At this point, only single-PE _epi files WITH ``IntendedFor`` can + # be automatically processed. + has_intended = tuple() + with suppress(ValueError): + has_intended = layout.get(suffix="epi", IntendedFor=Query.ANY, **base_entities) + + for epi_fmap in has_intended: + if epi_fmap.path in fm._estimators.sources: + continue # skip EPI images already considered above + + targets = [epi_fmap] + [ + layout.get_file(str(subject_root / intent)) + for intent in epi_fmap.get_metadata()["IntendedFor"] + ] + + epi_sources = [] + for fmap in targets: + with suppress(fm.MetadataError): + epi_sources.append( + fm.FieldmapFile(fmap.path, metadata=fmap.get_metadata()) + ) - with suppress(ValueError, TypeError): - estimators.append(fm.FieldmapEstimation(epi_sources)) + with suppress(ValueError, TypeError): + estimators.append(fm.FieldmapEstimation(epi_sources)) if estimators and not force_fmapless: fmapless = False From c87e5a3e0ce9a21a80c32b6612b1c710806685cc Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Tue, 26 Oct 2021 13:23:11 +0200 Subject: [PATCH 2/4] fix: none is not iterable --- sdcflows/utils/wrangler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdcflows/utils/wrangler.py b/sdcflows/utils/wrangler.py index db603c5c4b..93582fd12a 100644 --- a/sdcflows/utils/wrangler.py +++ b/sdcflows/utils/wrangler.py @@ -233,7 +233,7 @@ def find_estimators(*, layout, subject, fmapless=True, force_fmapless=False): estimators = [] - b0_ids = None + b0_ids = tuple() with suppress(BIDSEntityError): b0_ids = layout.get_B0FieldIdentifiers() From 82fd9b2ee80578afda04de91846c7d78dac8a450 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Tue, 26 Oct 2021 18:30:23 +0200 Subject: [PATCH 3/4] enh: add doctests to exercise the B0FieldIdentifier implementation --- sdcflows/conftest.py | 8 ++++-- .../tests/data/dsC/dataset_description.json | 11 ++++++++ .../data/dsC/sub-01/anat/sub-01_FLAIR.nii.gz | 0 .../data/dsC/sub-01/anat/sub-01_T1w.nii.gz | 0 .../data/dsC/sub-01/anat/sub-01_T2w.nii.gz | 0 .../dsC/sub-01/dwi/sub-01_dir-AP_dwi.nii.gz | 0 .../dsC/sub-01/dwi/sub-01_dir-AP_sbref.json | 4 +++ .../dsC/sub-01/dwi/sub-01_dir-AP_sbref.nii.gz | 0 .../dsC/sub-01/dwi/sub-01_dir-LR_dwi.nii.gz | 0 .../dsC/sub-01/dwi/sub-01_dir-LR_sbref.json | 4 +++ .../dsC/sub-01/dwi/sub-01_dir-LR_sbref.nii.gz | 0 .../dsC/sub-01/dwi/sub-01_dir-PA_dwi.nii.gz | 0 .../dsC/sub-01/dwi/sub-01_dir-PA_sbref.json | 4 +++ .../dsC/sub-01/dwi/sub-01_dir-PA_sbref.nii.gz | 0 .../dsC/sub-01/dwi/sub-01_dir-RL_dwi.nii.gz | 0 .../dsC/sub-01/dwi/sub-01_dir-RL_sbref.json | 4 +++ .../dsC/sub-01/dwi/sub-01_dir-RL_sbref.nii.gz | 0 .../fmap/sub-01_acq-single_dir-PA_epi.json | 10 +++++++ .../fmap/sub-01_acq-single_dir-PA_epi.nii.gz | 0 .../dsC/sub-01/fmap/sub-01_dir-AP_epi.json | 5 ++++ .../dsC/sub-01/fmap/sub-01_dir-AP_epi.nii.gz | 0 .../dsC/sub-01/fmap/sub-01_dir-LR_epi.json | 5 ++++ .../dsC/sub-01/fmap/sub-01_dir-LR_epi.nii.gz | 0 .../dsC/sub-01/fmap/sub-01_dir-PA_epi.json | 5 ++++ .../dsC/sub-01/fmap/sub-01_dir-PA_epi.nii.gz | 0 .../dsC/sub-01/fmap/sub-01_dir-RL_epi.json | 5 ++++ .../dsC/sub-01/fmap/sub-01_dir-RL_epi.nii.gz | 0 .../data/dsC/sub-01/fmap/sub-01_fieldmap.json | 3 ++ .../dsC/sub-01/fmap/sub-01_fieldmap.nii.gz | 0 .../dsC/sub-01/fmap/sub-01_magnitude.nii.gz | 0 .../dsC/sub-01/fmap/sub-01_magnitude1.nii.gz | 0 .../dsC/sub-01/fmap/sub-01_magnitude2.nii.gz | 0 .../data/dsC/sub-01/fmap/sub-01_phase1.json | 3 ++ .../data/dsC/sub-01/fmap/sub-01_phase1.nii.gz | 0 .../data/dsC/sub-01/fmap/sub-01_phase2.json | 3 ++ .../data/dsC/sub-01/fmap/sub-01_phase2.nii.gz | 0 .../dsC/sub-01/fmap/sub-01_phasediff.json | 4 +++ .../dsC/sub-01/fmap/sub-01_phasediff.nii.gz | 0 .../sub-01/func/sub-01_task-rest_bold.nii.gz | 0 .../sub-01/func/sub-01_task-rest_sbref.nii.gz | 0 sdcflows/tests/data/dsC/task-rest_bold.json | 5 ++++ sdcflows/utils/wrangler.py | 28 +++++++++++++++++++ 42 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 sdcflows/tests/data/dsC/dataset_description.json create mode 100644 sdcflows/tests/data/dsC/sub-01/anat/sub-01_FLAIR.nii.gz create mode 100644 sdcflows/tests/data/dsC/sub-01/anat/sub-01_T1w.nii.gz create mode 100644 sdcflows/tests/data/dsC/sub-01/anat/sub-01_T2w.nii.gz create mode 100644 sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-AP_dwi.nii.gz create mode 100644 sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-AP_sbref.json create mode 100644 sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-AP_sbref.nii.gz create mode 100644 sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-LR_dwi.nii.gz create mode 100644 sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-LR_sbref.json create mode 100644 sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-LR_sbref.nii.gz create mode 100644 sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-PA_dwi.nii.gz create mode 100644 sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-PA_sbref.json create mode 100644 sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-PA_sbref.nii.gz create mode 100644 sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-RL_dwi.nii.gz create mode 100644 sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-RL_sbref.json create mode 100644 sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-RL_sbref.nii.gz create mode 100644 sdcflows/tests/data/dsC/sub-01/fmap/sub-01_acq-single_dir-PA_epi.json create mode 100644 sdcflows/tests/data/dsC/sub-01/fmap/sub-01_acq-single_dir-PA_epi.nii.gz create mode 100644 sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-AP_epi.json create mode 100644 sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-AP_epi.nii.gz create mode 100644 sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-LR_epi.json create mode 100644 sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-LR_epi.nii.gz create mode 100644 sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-PA_epi.json create mode 100644 sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-PA_epi.nii.gz create mode 100644 sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-RL_epi.json create mode 100644 sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-RL_epi.nii.gz create mode 100644 sdcflows/tests/data/dsC/sub-01/fmap/sub-01_fieldmap.json create mode 100644 sdcflows/tests/data/dsC/sub-01/fmap/sub-01_fieldmap.nii.gz create mode 100644 sdcflows/tests/data/dsC/sub-01/fmap/sub-01_magnitude.nii.gz create mode 100644 sdcflows/tests/data/dsC/sub-01/fmap/sub-01_magnitude1.nii.gz create mode 100644 sdcflows/tests/data/dsC/sub-01/fmap/sub-01_magnitude2.nii.gz create mode 100644 sdcflows/tests/data/dsC/sub-01/fmap/sub-01_phase1.json create mode 100644 sdcflows/tests/data/dsC/sub-01/fmap/sub-01_phase1.nii.gz create mode 100644 sdcflows/tests/data/dsC/sub-01/fmap/sub-01_phase2.json create mode 100644 sdcflows/tests/data/dsC/sub-01/fmap/sub-01_phase2.nii.gz create mode 100644 sdcflows/tests/data/dsC/sub-01/fmap/sub-01_phasediff.json create mode 100644 sdcflows/tests/data/dsC/sub-01/fmap/sub-01_phasediff.nii.gz create mode 100644 sdcflows/tests/data/dsC/sub-01/func/sub-01_task-rest_bold.nii.gz create mode 100644 sdcflows/tests/data/dsC/sub-01/func/sub-01_task-rest_sbref.nii.gz create mode 100644 sdcflows/tests/data/dsC/task-rest_bold.json diff --git a/sdcflows/conftest.py b/sdcflows/conftest.py index 91e0e91d7f..f7f94daa11 100644 --- a/sdcflows/conftest.py +++ b/sdcflows/conftest.py @@ -40,9 +40,10 @@ } data_dir = Path(__file__).parent / "tests" / "data" - -layouts["dsA"] = BIDSLayout(data_dir / "dsA", validate=False, derivatives=False) -layouts["dsB"] = BIDSLayout(data_dir / "dsB", validate=False, derivatives=False) +layouts.update({ + folder.name: BIDSLayout(folder, validate=False, derivatives=False) + for folder in data_dir.glob("ds*") if folder.is_dir() +}) def pytest_report_header(config): @@ -66,6 +67,7 @@ def add_np(doctest_namespace): doctest_namespace["dsA_dir"] = data_dir / "dsA" doctest_namespace["dsB_dir"] = data_dir / "dsB" + doctest_namespace["dsC_dir"] = data_dir / "dsC" @pytest.fixture diff --git a/sdcflows/tests/data/dsC/dataset_description.json b/sdcflows/tests/data/dsC/dataset_description.json new file mode 100644 index 0000000000..4bba3aaf02 --- /dev/null +++ b/sdcflows/tests/data/dsC/dataset_description.json @@ -0,0 +1,11 @@ +{ + "Name": "Test Dataset A, only empty files", + "BIDSVersion": "", + "License": "CC0", + "Authors": ["Esteban O."], + "Acknowledgements": "", + "HowToAcknowledge": "", + "Funding": "", + "ReferencesAndLinks": [""], + "DatasetDOI": "" +} diff --git a/sdcflows/tests/data/dsC/sub-01/anat/sub-01_FLAIR.nii.gz b/sdcflows/tests/data/dsC/sub-01/anat/sub-01_FLAIR.nii.gz new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sdcflows/tests/data/dsC/sub-01/anat/sub-01_T1w.nii.gz b/sdcflows/tests/data/dsC/sub-01/anat/sub-01_T1w.nii.gz new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sdcflows/tests/data/dsC/sub-01/anat/sub-01_T2w.nii.gz b/sdcflows/tests/data/dsC/sub-01/anat/sub-01_T2w.nii.gz new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-AP_dwi.nii.gz b/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-AP_dwi.nii.gz new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-AP_sbref.json b/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-AP_sbref.json new file mode 100644 index 0000000000..167bad2c08 --- /dev/null +++ b/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-AP_sbref.json @@ -0,0 +1,4 @@ +{ + "PhaseEncodingDirection": "j-", + "TotalReadoutTime": 0.005 +} \ No newline at end of file diff --git a/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-AP_sbref.nii.gz b/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-AP_sbref.nii.gz new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-LR_dwi.nii.gz b/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-LR_dwi.nii.gz new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-LR_sbref.json b/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-LR_sbref.json new file mode 100644 index 0000000000..6fb2e51bd7 --- /dev/null +++ b/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-LR_sbref.json @@ -0,0 +1,4 @@ +{ + "PhaseEncodingDirection": "i", + "TotalReadoutTime": 0.005 +} \ No newline at end of file diff --git a/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-LR_sbref.nii.gz b/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-LR_sbref.nii.gz new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-PA_dwi.nii.gz b/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-PA_dwi.nii.gz new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-PA_sbref.json b/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-PA_sbref.json new file mode 100644 index 0000000000..96539d2557 --- /dev/null +++ b/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-PA_sbref.json @@ -0,0 +1,4 @@ +{ + "PhaseEncodingDirection": "j", + "TotalReadoutTime": 0.005 +} \ No newline at end of file diff --git a/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-PA_sbref.nii.gz b/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-PA_sbref.nii.gz new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-RL_dwi.nii.gz b/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-RL_dwi.nii.gz new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-RL_sbref.json b/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-RL_sbref.json new file mode 100644 index 0000000000..6b40679424 --- /dev/null +++ b/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-RL_sbref.json @@ -0,0 +1,4 @@ +{ + "PhaseEncodingDirection": "i-", + "TotalReadoutTime": 0.005 +} \ No newline at end of file diff --git a/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-RL_sbref.nii.gz b/sdcflows/tests/data/dsC/sub-01/dwi/sub-01_dir-RL_sbref.nii.gz new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_acq-single_dir-PA_epi.json b/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_acq-single_dir-PA_epi.json new file mode 100644 index 0000000000..92f857af8b --- /dev/null +++ b/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_acq-single_dir-PA_epi.json @@ -0,0 +1,10 @@ +{ + "PhaseEncodingDirection": "j", + "TotalReadoutTime": 0.005, + "IntendedFor": [ + "dwi/sub-01_dir-AP_dwi.nii.gz", + "dwi/sub-01_dir-AP_sbref.nii.gz", + "func/sub-01_task-rest_bold.nii.gz", + "func/sub-01_task-rest_sbref.nii.gz" + ] +} \ No newline at end of file diff --git a/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_acq-single_dir-PA_epi.nii.gz b/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_acq-single_dir-PA_epi.nii.gz new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-AP_epi.json b/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-AP_epi.json new file mode 100644 index 0000000000..7130a256c6 --- /dev/null +++ b/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-AP_epi.json @@ -0,0 +1,5 @@ +{ + "B0FieldIdentifier": "pepolar4pe", + "PhaseEncodingDirection": "j-", + "TotalReadoutTime": 0.005 +} \ No newline at end of file diff --git a/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-AP_epi.nii.gz b/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-AP_epi.nii.gz new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-LR_epi.json b/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-LR_epi.json new file mode 100644 index 0000000000..6b83831168 --- /dev/null +++ b/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-LR_epi.json @@ -0,0 +1,5 @@ +{ + "B0FieldIdentifier": "pepolar4pe", + "PhaseEncodingDirection": "i", + "TotalReadoutTime": 0.005 +} \ No newline at end of file diff --git a/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-LR_epi.nii.gz b/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-LR_epi.nii.gz new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-PA_epi.json b/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-PA_epi.json new file mode 100644 index 0000000000..04212c1507 --- /dev/null +++ b/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-PA_epi.json @@ -0,0 +1,5 @@ +{ + "B0FieldIdentifier": "pepolar4pe", + "PhaseEncodingDirection": "j", + "TotalReadoutTime": 0.005 +} \ No newline at end of file diff --git a/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-PA_epi.nii.gz b/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-PA_epi.nii.gz new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-RL_epi.json b/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-RL_epi.json new file mode 100644 index 0000000000..1d8b96155a --- /dev/null +++ b/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-RL_epi.json @@ -0,0 +1,5 @@ +{ + "B0FieldIdentifier": "pepolar4pe", + "PhaseEncodingDirection": "i-", + "TotalReadoutTime": 0.005 +} \ No newline at end of file diff --git a/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-RL_epi.nii.gz b/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_dir-RL_epi.nii.gz new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_fieldmap.json b/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_fieldmap.json new file mode 100644 index 0000000000..e0a52a2bb8 --- /dev/null +++ b/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_fieldmap.json @@ -0,0 +1,3 @@ +{ + "Units": "rad/s" +} \ No newline at end of file diff --git a/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_fieldmap.nii.gz b/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_fieldmap.nii.gz new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_magnitude.nii.gz b/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_magnitude.nii.gz new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_magnitude1.nii.gz b/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_magnitude1.nii.gz new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_magnitude2.nii.gz b/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_magnitude2.nii.gz new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_phase1.json b/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_phase1.json new file mode 100644 index 0000000000..d897a4ffe6 --- /dev/null +++ b/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_phase1.json @@ -0,0 +1,3 @@ +{ + "EchoTime": 0.005 +} \ No newline at end of file diff --git a/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_phase1.nii.gz b/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_phase1.nii.gz new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_phase2.json b/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_phase2.json new file mode 100644 index 0000000000..78bc162aec --- /dev/null +++ b/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_phase2.json @@ -0,0 +1,3 @@ +{ + "EchoTime": 0.00746 +} \ No newline at end of file diff --git a/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_phase2.nii.gz b/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_phase2.nii.gz new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_phasediff.json b/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_phasediff.json new file mode 100644 index 0000000000..41c3cac3dc --- /dev/null +++ b/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_phasediff.json @@ -0,0 +1,4 @@ +{ + "EchoTime1": 0.00500, + "EchoTime2": 0.00746 +} \ No newline at end of file diff --git a/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_phasediff.nii.gz b/sdcflows/tests/data/dsC/sub-01/fmap/sub-01_phasediff.nii.gz new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sdcflows/tests/data/dsC/sub-01/func/sub-01_task-rest_bold.nii.gz b/sdcflows/tests/data/dsC/sub-01/func/sub-01_task-rest_bold.nii.gz new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sdcflows/tests/data/dsC/sub-01/func/sub-01_task-rest_sbref.nii.gz b/sdcflows/tests/data/dsC/sub-01/func/sub-01_task-rest_sbref.nii.gz new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sdcflows/tests/data/dsC/task-rest_bold.json b/sdcflows/tests/data/dsC/task-rest_bold.json new file mode 100644 index 0000000000..843d8f0511 --- /dev/null +++ b/sdcflows/tests/data/dsC/task-rest_bold.json @@ -0,0 +1,5 @@ +{ + "TaskName": "Rest", + "PhaseEncodingDirection": "j-", + "TotalReadoutTime": 0.05 +} \ No newline at end of file diff --git a/sdcflows/utils/wrangler.py b/sdcflows/utils/wrangler.py index 93582fd12a..fb56e32ce1 100644 --- a/sdcflows/utils/wrangler.py +++ b/sdcflows/utils/wrangler.py @@ -214,6 +214,31 @@ def find_estimators(*, layout, subject, fmapless=True, force_fmapless=False): FieldmapEstimation(sources=<2 files>, method=, bids_id='auto_00012')] + When the ``B0FieldIdentifier`` metadata is set for one or more fieldmaps, then + the heuristics that use ``IntendedFor`` are dismissed: + + >>> find_estimators( + ... layout=layouts['dsC'], + ... subject="01", + ... ) # doctest: +ELLIPSIS + [FieldmapEstimation(sources=<5 files>, method=, + bids_id='pepolar4pe')] + + The only exception to the priority of ``B0FieldIdentifier`` is when fieldmaps + are searched with the ``force_fmapless`` argument on: + + >>> fm.clear_registry() # Necessary as `pepolar4pe` is not changing. + >>> find_estimators( + ... layout=layouts['dsC'], + ... subject="01", + ... fmapless=True, + ... force_fmapless=True, + ... ) # doctest: +ELLIPSIS + [FieldmapEstimation(sources=<5 files>, method=, + bids_id='pepolar4pe'), + FieldmapEstimation(sources=<2 files>, method=, + bids_id='auto_00000')] + """ from .. import fieldmaps as fm from bids.layout import Query @@ -313,6 +338,8 @@ def find_estimators(*, layout, subject, fmapless=True, force_fmapless=False): from .epimanip import get_trt + # Sessions may not be defined at this point if some id was found. + sessions = layout.get_sessions() for ses, suffix in sorted(product(sessions or (None,), fmapless)): candidates = layout.get(suffix=suffix, session=ses, **base_entities) @@ -324,6 +351,7 @@ def find_estimators(*, layout, subject, fmapless=True, force_fmapless=False): for candidate in candidates: meta = candidate.get_metadata() pe_dir = meta.get("PhaseEncodingDirection") + if not pe_dir: continue From 6dbfbcb3c94cae1a220f24d5f0ba99526b32aec8 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Tue, 26 Oct 2021 20:17:33 +0200 Subject: [PATCH 4/4] Apply suggestions from code review Co-authored-by: Mathias Goncalves --- sdcflows/utils/wrangler.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sdcflows/utils/wrangler.py b/sdcflows/utils/wrangler.py index fb56e32ce1..8f8cd86c68 100644 --- a/sdcflows/utils/wrangler.py +++ b/sdcflows/utils/wrangler.py @@ -260,7 +260,7 @@ def find_estimators(*, layout, subject, fmapless=True, force_fmapless=False): b0_ids = tuple() with suppress(BIDSEntityError): - b0_ids = layout.get_B0FieldIdentifiers() + b0_ids = layout.get_B0FieldIdentifiers(**base_entities) for b0_id in b0_ids: # Found B0FieldIdentifier metadata entries @@ -283,9 +283,9 @@ def find_estimators(*, layout, subject, fmapless=True, force_fmapless=False): estimators.append(e) # A bunch of heuristics to select EPI fieldmaps - sessions = layout.get_sessions() - acqs = tuple(layout.get_acquisitions(suffix="epi") + [None]) - contrasts = tuple(layout.get_ceagents(suffix="epi") + [None]) + sessions = layout.get_sessions(subject=subject) + acqs = tuple(layout.get_acquisitions(subject=subject, suffix="epi") + [None]) + contrasts = tuple(layout.get_ceagents(subject=subject, suffix="epi") + [None]) for ses, acq, ce in product(sessions or (None,), acqs, contrasts): entities = base_entities.copy() @@ -339,7 +339,7 @@ def find_estimators(*, layout, subject, fmapless=True, force_fmapless=False): from .epimanip import get_trt # Sessions may not be defined at this point if some id was found. - sessions = layout.get_sessions() + sessions = layout.get_sessions(subject=subject) for ses, suffix in sorted(product(sessions or (None,), fmapless)): candidates = layout.get(suffix=suffix, session=ses, **base_entities)