Repository: bids-standard/pybids

Issue:
Add get_fieldmap() method
Option 1 (simple):

```
>> layout = BIDSLayout("/data/ds")
>> layout.get_fieldmap("sub-01/func/sub-01_task-rest_bold.nii.gz")
[
 "/data/ds/sub-01/fmap/sub-01_magnitude1.nii.gz", 
 "/data/ds/sub-01/fmap/sub-01_magnitude2.nii.gz", 
 "/data/ds/sub-01/fmap/sub-01_phasediff.nii.gz"
]
```

Option 2 (extensive):

```
>> layout = BIDSLayout("/data/ds")
>> layout.get_fieldmap("sub-01/func/sub-01_task-rest_bold.nii.gz")
{
'type': 'phasediff',
'metadata': {
                    "EchoTime1" : 0.00600,
                    "EchoTime2" : 0.00746,
                    "IntendedFor" : "func/sub­01_task­motor_bold.nii.gz" 
                    },
'data' : [
             "/data/ds/sub-01/fmap/sub-01_magnitude1.nii.gz", 
             "/data/ds/sub-01/fmap/sub-01_magnitude2.nii.gz", 
             "/data/ds/sub-01/fmap/sub-01_phasediff.nii.gz"
            ]
}
```

WDYT @rwblair @oesteban @yarikoptic @tyarkoni @satra?



Discussion:
I like the idea of giving the user the option to return both; maybe a `metadata=False` flag, that returns the first by default?

I'll also spend a bit of time thinking about ways we could generalize this idea in grabbit, so that it becomes easy to define rules that specify how to find the metadata corresponding to a given entity, and which fields to return. Unless you've already implemented something like that. :)

And actually that would work well with the existing interface, because 'metadata' could become another valid value for `return_type` in the automatically-added `get_{}` methods. 

I have not thought about generalizing this (prefer to start with and refactor when I see overlaps). Adding `metadata=True` seems like a good idea.

+1 for optional metadata


Resolving patch:
diff --git a/bids/grabbids/bids_layout.py b/bids/grabbids/bids_layout.py
--- a/bids/grabbids/bids_layout.py
+++ b/bids/grabbids/bids_layout.py
@@ -18,7 +18,7 @@ def __init__(self, path, config=None):
         if config is None:
             root = dirname(realpath(__file__))
             config = pathjoin(root, 'config', 'bids.json')
-        super(BIDSLayout, self).__init__(path, config)
+        super(BIDSLayout, self).__init__(path, config, dynamic_getters=True)
 
     def get_metadata(self, path):
         sidecarJSON = path.replace(".nii.gz", ".json").replace(".nii", ".json")
@@ -34,21 +34,21 @@ def get_metadata(self, path):
             keyword_components = filename_components[2:-1]
 
         potentialJSONs = []
-        for prefixes, conditional in (  # Levels
-                (tuple(), True),        # top
-                ((sub,), True),         # subject
-                ((sub, ses), ses)       # session
+        for prefixes, midlayer, conditional in (  # Levels
+                (tuple(), tuple(), True),        # top
+                ((sub,), tuple(), True),         # subject
+                ((sub, ), (pathsplit(path_components[-2])[-1],), True),
+                ((sub, ses), tuple(), ses),  # session
+                ((sub, ses), (pathsplit(path_components[-2])[-1],), ses)
         ):
             if not conditional:
                 continue
             for k in range(len(keyword_components) + 1):
-                # print(k)
                 for components in combinations(keyword_components, k):
-                    # print(components)
                     potentialJSONs.append(
                         pathjoin(
                             self.root,
-                            *(prefixes +
+                            *(prefixes + midlayer +
                               ("_".join(prefixes + components + (suffix,)),))))
 
         merged_param_dict = {}
@@ -59,6 +59,45 @@ def get_metadata(self, path):
 
         return merged_param_dict
 
+    def get_fieldmap(self, path):
+        sub = os.path.split(path)[1].split("_")[0].split("sub-")[1]
+        fieldmap_set = {}
+        for file in self.get(subject=sub,
+                             type='(phase1|phase2|phasediff|epi|fieldmap)',
+                             extensions=['nii.gz', 'nii']):
+            metadata = self.get_metadata(file.filename)
+            if metadata and "IntendedFor" in metadata.keys():
+                if path.endswith(metadata["IntendedFor"]):
+                    if file.type == "phasediff":
+                        fieldmap_set = {"phasediff": file.filename,
+                                        "magnitude1": file.filename.replace(
+                                            "phasediff", "magnitude1"),
+                                        "magnitude2": file.filename.replace(
+                                            "phasediff", "magnitude2"),
+                                        "type": "phasediff"}
+                        break
+                    elif file.type == "phase1":
+                        fieldmap_set["phase1"] = file.filename
+                        fieldmap_set["magnitude1"] = \
+                            file.filename.replace("phase1", "magnitude1")
+                        fieldmap_set["type"] = "phase"
+                    elif file.type == "phase2":
+                        fieldmap_set["phase2"] = file.filename
+                        fieldmap_set["magnitude2"] = \
+                            file.filename.replace("phase2", "magnitude2")
+                        fieldmap_set["type"] = "phase"
+                    elif file.type == "epi":
+                        if "epi" not in fieldmap_set.keys():
+                            fieldmap_set["epi"] = []
+                        fieldmap_set["epi"].append(file.filename)
+                        fieldmap_set["type"] = "epi"
+                    elif file.type == "fieldmap":
+                        fieldmap_set["fieldmap"] = file.filename
+                        fieldmap_set["magnitude"] = \
+                            file.filename.replace("fieldmap", "magnitude")
+                        fieldmap_set["type"] = "fieldmap"
+        return fieldmap_set
+
     def find_match(self, target, source=None):
 
         # Try to take the easy way out
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -23,6 +23,7 @@
             platforms=PLATFORMS,
             version=VERSION,
             packages=PACKAGES,
+            package_data={'bids': ['grabbids/config/bids.json']},
             requires=REQUIRES,
             install_requires=REQUIRES)
 


Test patch:
diff --git a/bids/grabbids/tests/test_grabbids.py b/bids/grabbids/tests/test_grabbids.py
--- a/bids/grabbids/tests/test_grabbids.py
+++ b/bids/grabbids/tests/test_grabbids.py
@@ -18,3 +18,29 @@ def test_get_metadata():
              'rest_acq-fullbrain_run-2_bold.nii.gz'
     result = layout.get_metadata(join(data_dir, target))
     assert result['RepetitionTime'] == 3.0
+
+
+def test_get_metadata2():
+    data_dir = join(dirname(__file__), 'data', '7t_trt')
+    layout = BIDSLayout(data_dir)
+    target = 'sub-03/ses-2/fmap/sub-03_ses-1_run-1_phasediff.nii.gz'
+    result = layout.get_metadata(join(data_dir, target))
+    assert result['EchoTime1'] == 0.006
+
+
+def test_get_subjects():
+    data_dir = join(dirname(__file__), 'data', '7t_trt')
+    layout = BIDSLayout(data_dir)
+    result = layout.get_subjects()
+    predicted = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10']
+    assert predicted == result
+
+
+def test_get_fieldmap():
+    data_dir = join(dirname(__file__), 'data', '7t_trt')
+    layout = BIDSLayout(data_dir)
+    target = 'sub-03/ses-2/func/sub-03_ses-2_task-' \
+             'rest_acq-fullbrain_run-1_bold.nii.gz'
+    result = layout.get_fieldmap(join(data_dir, target))
+    assert result["type"] == "phasediff"
+    assert result["phasediff"].endswith('sub-03_ses-2_run-1_phasediff.nii.gz')