Commit 3c57660e authored by Paul McCarthy's avatar Paul McCarthy 🚵
Browse files

Merge branch 'rf/bids' into 'master'

Rf/bids

See merge request fsl/fslpy!287
parents 33f85a0a 02a84b76
...@@ -13,6 +13,8 @@ Changed ...@@ -13,6 +13,8 @@ Changed
* The :class:`.TaskThread` now allows an error handler function to be * The :class:`.TaskThread` now allows an error handler function to be
specified, which is run on the :mod:`.idle` loop. specified, which is run on the :mod:`.idle` loop.
* The :func:`.bids.loadMetadata` function no long resolves sym-links when
determining whether a file is contained within a BIDS data set.
Deprecated Deprecated
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
All of the other functions in this module should not be considered part of the All of the other functions in this module should not be considered part of the
public API. public API.
.. see:: https://bids-specification.readthedocs.io/en/stable/
.. note:: The `pybids <https://bids-standard.github.io/pybids/>`_ library is .. note:: The `pybids <https://bids-standard.github.io/pybids/>`_ library is
a more suitable choice if you are after a more robust and featured a more suitable choice if you are after a more robust and featured
...@@ -36,8 +37,8 @@ import fsl.utils.path as fslpath ...@@ -36,8 +37,8 @@ import fsl.utils.path as fslpath
class BIDSFile(object): class BIDSFile(object):
"""The ``BIDSFile`` class parses and stores the entities and suffix contained """The ``BIDSFile`` class parses and stores the entities and suffix
in a BIDS file. See the :func:`parseFilename` function. contained in a BIDS file. See the :func:`parseFilename` function.
The :meth:`match` method can be used to compare two ``BIDSFile`` instances. The :meth:`match` method can be used to compare two ``BIDSFile`` instances.
...@@ -91,15 +92,9 @@ class BIDSFile(object): ...@@ -91,15 +92,9 @@ class BIDSFile(object):
def parseFilename(filename): def parseFilename(filename):
"""Parses a BIDS-like file name. The file name must consist of zero or more """Parses a BIDS-like file name, returning the entities and suffix encoded
"entities" (alpha-numeric ``name-value`` pairs), a "suffix", all separated in the name. See the :func:`isBIDSFile` function for an explanation of
by underscores, and a regular file extension. For example, the following what is considered to be a valid BIDS file name.
file::
sub-01_ses-01_task-stim_bold.nii.gz
has suffix ``bold``, entities ``sub=01``, ``ses=01`` and ``task=stim``, and
extension ``.nii.gz``.
.. note:: This function assumes that no period (``.``) characters occur in .. note:: This function assumes that no period (``.``) characters occur in
the body of a BIDS filename. the body of a BIDS filename.
...@@ -161,11 +156,24 @@ def inBIDSDir(filename): ...@@ -161,11 +156,24 @@ def inBIDSDir(filename):
def isBIDSFile(filename, strict=True): def isBIDSFile(filename, strict=True):
"""Returns ``True`` if ``filename`` looks like a BIDS image or JSON file. """Returns ``True`` if ``filename`` looks like a BIDS image or JSON file.
A BIDS file name must consist of zero or more "entities" (alpha-numeric
``name-value`` pairs), a "suffix", all separated by underscores, and a
regular file extension. For example, the following file::
sub-01_ses-01_task-stim_bold.nii.gz
has suffix ``bold``, entities ``sub=01``, ``ses=01`` and ``task=stim``, and
extension ``.nii.gz``.
:arg filename: Name of file to check :arg filename: Name of file to check
:arg strict: If ``True`` (the default), the file must be within a BIDS :arg strict: If ``True`` (the default), the file must be within a BIDS
dataset directory, as defined by :func:`inBIDSDir`. dataset directory, as defined by :func:`inBIDSDir`.
""" """
# Zero or more entities because sidecar files
# do not necessarily need to contain any
# entities (e.g. ``T1w.json`` is valid)
name = op.basename(filename) name = op.basename(filename)
pattern = r'([a-z0-9]+-[a-z0-9]+_)*([a-z0-9])+\.(.+)' pattern = r'([a-z0-9]+-[a-z0-9]+_)*([a-z0-9])+\.(.+)'
flags = re.ASCII | re.IGNORECASE flags = re.ASCII | re.IGNORECASE
...@@ -189,7 +197,7 @@ def loadMetadata(filename): ...@@ -189,7 +197,7 @@ def loadMetadata(filename):
``filename`` ``filename``
""" """
filename = op.realpath(op.abspath(filename)) filename = op.abspath(filename)
bfile = BIDSFile(filename) bfile = BIDSFile(filename)
dirname = op.dirname(filename) dirname = op.dirname(filename)
prevdir = filename prevdir = filename
......
...@@ -18,8 +18,11 @@ import fsl.utils.bids as fslbids ...@@ -18,8 +18,11 @@ import fsl.utils.bids as fslbids
def test_parseFilename(): def test_parseFilename():
with pytest.raises(ValueError): badtests = ['bad_file.txt']
fslbids.parseFilename('bad_file.txt')
for test in badtests:
with pytest.raises(ValueError):
fslbids.parseFilename(test)
tests = [ tests = [
('sub-01_ses-01_t1w.nii.gz', ('sub-01_ses-01_t1w.nii.gz',
...@@ -105,3 +108,41 @@ def test_loadMetadata(): ...@@ -105,3 +108,41 @@ def test_loadMetadata():
assert fslbids.loadMetadata(t1) == {**meta2, **meta1} assert fslbids.loadMetadata(t1) == {**meta2, **meta1}
json4.write_text(json.dumps(meta4)) json4.write_text(json.dumps(meta4))
assert fslbids.loadMetadata(t1) == {**meta4, **meta2, **meta1} assert fslbids.loadMetadata(t1) == {**meta4, **meta2, **meta1}
def test_loadMetadata_symlinked():
ddreal = Path('a')
t1real = Path('b')
j1real = Path('c')
j2real = Path('d')
j3real = Path('e')
j4real = Path('f')
dd = Path('data/dataset_description.json')
t1 = Path('data/sub-01/func/sub-01_task-stim_bold.nii.gz')
json1 = Path('data/sub-01/func/sub-01_task-stim_bold.json')
json2 = Path('data/sub-01/sub-01_bold.json')
json3 = Path('data/sub-01_t1w.json')
json4 = Path('data/sub-01/task-stim_bold.json')
meta1 = {'a' : '1', 'b' : '2'}
meta2 = {'a' : '10', 'c' : '3'}
meta3 = {'a' : '109', 'b' : '99'}
meta4 = {'c' : '9', 'd' : '5'}
with tempdir():
ddreal.touch()
t1real.touch()
j1real.write_text(json.dumps(meta1))
j2real.write_text(json.dumps(meta2))
j3real.write_text(json.dumps(meta3))
j4real.write_text(json.dumps(meta4))
Path(op.dirname(t1)).mkdir(parents=True)
dd .symlink_to(op.join('..', ddreal))
t1 .symlink_to(op.join('..', '..', '..', t1real))
json1.symlink_to(op.join('..', '..', '..', j1real))
json2.symlink_to(op.join('..', '..', j2real))
json3.symlink_to(op.join('..', j3real))
json4.symlink_to(op.join('..', '..', j4real))
assert fslbids.loadMetadata(t1) == {**meta4, **meta2, **meta1}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment