Repository: mne-tools/mne-bids

Issue:
get list of entities
Here's my Saturday night hack:

```py
# Authors: Mainak Jas <mainakjas@gmail.com>

import re
from pathlib import Path


def get_list_of_entity(bids_root, key='sub'):
    """Get list of a particular entity."""
    if key not in ('sub', 'ses', 'run', 'acq'):
        raise ValueError('Key must be one of. Got %s' % key)

    p = re.compile(r'%s-(.*?)_' % key)
    entities = list()
    for filename in Path(bids_root).rglob('*%s-*_*' % key):
        match = p.search(filename.stem)
        entity = match.group(1)
        if entity not in entities:
            entities.append(entity)
    return entities


bids_root = '/home/mainak/data/ds000248/'
print(get_list_of_entity(bids_root, 'sub'))
print(get_list_of_entity(bids_root, 'run'))
print(get_list_of_entity(bids_root, 'acq'))
```

you will get

```py
['01', 'emptyroom']
['01']
['flipangle05', 'flipangle30', 'epi']
```

can help us avoid `pybids` dependency


Discussion:
to be put in mne_bids.utils to avoid dependency on pybids

nice hack @jasmainak :)

+1, do you want to make the PR @jasmainak ?

Feel free to take charge as my day job as postdoc takes precedence now. My job was to show the way ;-)

Resolving patch:
diff --git a/mne_bids/utils.py b/mne_bids/utils.py
--- a/mne_bids/utils.py
+++ b/mne_bids/utils.py
@@ -16,6 +16,7 @@
 import re
 from datetime import datetime
 from collections import defaultdict
+from pathlib import Path
 
 import numpy as np
 from mne import read_events, find_events, events_from_annotations
@@ -28,6 +29,45 @@
 from mne_bids.tsv_handler import _to_tsv, _tsv_to_str
 
 
+def get_values_for_key(bids_root, key):
+    """Get list of values for a key in a BIDS dataset.
+
+    BIDS file names are organized by key-value pairs called "entities" [1]_.
+
+    Parameters
+    ----------
+    bids_root : str
+        Path to the root of the BIDS directory.
+    key : str
+        The name of the key to search for. Can be one of
+        ['sub', 'ses', 'run', 'acq'].
+
+    Returns
+    -------
+    value_list : list of str
+        List of the values associated with a `key` in the BIDS dataset pointed
+        to by `bids_root`.
+
+    References
+    ----------
+    .. [1] https://bids-specification.rtfd.io/en/latest/02-common-principles.html#file-name-structure  # noqa: E501
+
+    """
+    accepted_keys = ('sub', 'ses', 'run', 'acq')
+    if key not in accepted_keys:
+        raise ValueError('`key` must be one of "{}". Got "{}"'
+                         .format(accepted_keys, key))
+
+    p = re.compile(r'{}-(.*?)_'.format(key))
+    value_list = list()
+    for filename in Path(bids_root).rglob('*{}-*_*'.format(key)):
+        match = p.search(filename.stem)
+        value = match.group(1)
+        if value not in value_list:
+            value_list.append(value)
+    return sorted(value_list)
+
+
 def _get_ch_type_mapping(fro='mne', to='bids'):
     """Map between BIDS and MNE nomenclatures for channel types.
 


Test patch:
diff --git a/mne_bids/tests/test_utils.py b/mne_bids/tests/test_utils.py
--- a/mne_bids/tests/test_utils.py
+++ b/mne_bids/tests/test_utils.py
@@ -19,14 +19,14 @@
                             _infer_eeg_placement_scheme, _handle_kind,
                             _find_matching_sidecar, _parse_ext,
                             _get_ch_type_mapping, _parse_bids_filename,
-                            _find_best_candidates)
+                            _find_best_candidates, get_values_for_key)
 
 
 base_path = op.join(op.dirname(mne.__file__), 'io')
 subject_id = '01'
 session_id = '01'
 run = '01'
-acq = '01'
+acq = None
 task = 'testing'
 
 bids_basename = make_bids_basename(
@@ -34,6 +34,45 @@
     task=task)
 
 
+@pytest.fixture(scope='session')
+def return_bids_test_dir(tmpdir_factory):
+    """Return path to a written test BIDS dir."""
+    output_path = str(tmpdir_factory.mktemp('mnebids_utils_test_bids_ds'))
+    data_path = testing.data_path()
+    raw_fname = op.join(data_path, 'MEG', 'sample',
+                        'sample_audvis_trunc_raw.fif')
+
+    event_id = {'Auditory/Left': 1, 'Auditory/Right': 2, 'Visual/Left': 3,
+                'Visual/Right': 4, 'Smiley': 5, 'Button': 32}
+    events_fname = op.join(data_path, 'MEG', 'sample',
+                           'sample_audvis_trunc_raw-eve.fif')
+
+    raw = mne.io.read_raw_fif(raw_fname)
+    # Write multiple runs for test_purposes
+    bids_basename2 = bids_basename.replace('run-{}'.format(run), 'run-02')
+    for name in [bids_basename,
+                 bids_basename2,
+                 ]:
+        with pytest.warns(UserWarning, match='No line frequency'):
+            write_raw_bids(raw, name, output_path,
+                           events_data=events_fname, event_id=event_id,
+                           overwrite=True)
+
+    return output_path
+
+
+def test_get_values_for_key(return_bids_test_dir):
+    """Test getting a list of entities."""
+    bids_root = return_bids_test_dir
+    with pytest.raises(ValueError, match='`key` must be one of'):
+        get_values_for_key(bids_root, key='bogus')
+
+    assert get_values_for_key(bids_root, 'sub') == [subject_id]
+    assert get_values_for_key(bids_root, 'ses') == [session_id]
+    assert get_values_for_key(bids_root, 'run') == [run, '02']
+    assert get_values_for_key(bids_root, 'acq') == []
+
+
 def test_get_ch_type_mapping():
     """Test getting a correct channel mapping."""
     with pytest.raises(ValueError, match='specified from "bogus" to "mne"'):
@@ -239,30 +278,15 @@ def test_find_best_candidates(candidate_list, best_candidates):
     assert _find_best_candidates(params, candidate_list) == best_candidates
 
 
-def test_find_matching_sidecar():
+def test_find_matching_sidecar(return_bids_test_dir):
     """Test finding a sidecar file from a BIDS dir."""
-    # First write a BIDS dir
-    output_path = _TempDir()
-    data_path = testing.data_path()
-    raw_fname = op.join(data_path, 'MEG', 'sample',
-                        'sample_audvis_trunc_raw.fif')
-
-    event_id = {'Auditory/Left': 1, 'Auditory/Right': 2, 'Visual/Left': 3,
-                'Visual/Right': 4, 'Smiley': 5, 'Button': 32}
-    events_fname = op.join(data_path, 'MEG', 'sample',
-                           'sample_audvis_trunc_raw-eve.fif')
-
-    raw = mne.io.read_raw_fif(raw_fname)
-    with pytest.warns(UserWarning, match='No line frequency'):
-        write_raw_bids(raw, bids_basename, output_path,
-                       events_data=events_fname, event_id=event_id,
-                       overwrite=False)
+    output_path = return_bids_test_dir
 
     # Now find a sidecar
     sidecar_fname = _find_matching_sidecar(bids_basename, output_path,
                                            'coordsystem.json')
     expected_file = op.join('sub-01', 'ses-01', 'meg',
-                            'sub-01_ses-01_acq-01_coordsystem.json')
+                            'sub-01_ses-01_coordsystem.json')
     assert sidecar_fname.endswith(expected_file)
 
     # Find multiple sidecars, tied in score, triggering an error