Skip to content

Commit 2e2884f

Browse files
authored
Merge pull request #2174 from adelavega/bids_grabber
ENH: BIDS Data Grabber
2 parents ada8c56 + 0d877ee commit 2e2884f

File tree

7 files changed

+185
-2
lines changed

7 files changed

+185
-2
lines changed

.travis.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,12 @@ before_install:
3636
conda install python=${TRAVIS_PYTHON_VERSION} &&
3737
conda config --add channels conda-forge &&
3838
conda install -y nipype icu &&
39-
rm -r ${CONDA_HOME}/lib/python${TRAVIS_PYTHON_VERSION}/site-packages/nipype*; }
39+
rm -r ${CONDA_HOME}/lib/python${TRAVIS_PYTHON_VERSION}/site-packages/nipype*;
40+
pushd $HOME;
41+
git clone https://github.com/INCF/pybids.git;
42+
cd pybids;
43+
pip install -e .;
44+
popd; }
4045
# Add install of vtk and mayavi to test mesh (disabled): conda install -y vtk mayavi
4146
- travis_retry apt_inst
4247
- travis_retry conda_inst

.zenodo.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,11 @@
528528
"affiliation": "Vrije Universiteit, Amsterdam",
529529
"name": "Gilles de Hollander",
530530
"orcid": "0000-0003-1988-5091"
531+
},
532+
{
533+
"affiliation": "University of Texas at Austin",
534+
"name": "De La Vega, Alejandro",
535+
"orcid": "0000-0001-9062-3778"
531536
}
532537
],
533538
"keywords": [

Dockerfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ COPY requirements.txt requirements.txt
8787
RUN pip install -r requirements.txt && \
8888
rm -rf ~/.cache/pip
8989

90+
RUN git clone https://github.com/INCF/pybids.git && \
91+
cd pybids && python setup.py develop
92+
9093
# Installing nipype
9194
COPY . /src/nipype
9295
RUN cd /src/nipype && \

docker/base.Dockerfile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,4 +149,3 @@ ENV MATLABCMD="/opt/mcr/v85/toolbox/matlab" \
149149
FORCE_SPMMCR=1
150150

151151
WORKDIR /work
152-

nipype/info.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ def get_nipype_gitversion():
162162
'profiler': ['psutil'],
163163
'duecredit': ['duecredit'],
164164
'xvfbwrapper': ['xvfbwrapper'],
165+
'pybids' : ['pybids']
165166
# 'mesh': ['mayavi'] # Enable when it works
166167
}
167168

nipype/interfaces/bids_utils.py

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
# -*- coding: utf-8 -*-
2+
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
3+
# vi: set ft=python sts=4 ts=4 sw=4 et:
4+
""" Set of interfaces that allow interaction with BIDS data. Currently
5+
available interfaces are:
6+
7+
BIDSDataGrabber: Query data from BIDS dataset using pybids grabbids.
8+
9+
Change directory to provide relative paths for doctests
10+
>>> import os
11+
>>> import bids
12+
>>> filepath = os.path.realpath(os.path.dirname(bids.__file__))
13+
>>> datadir = os.path.realpath(os.path.join(filepath, 'grabbids/tests/data/'))
14+
>>> os.chdir(datadir)
15+
16+
"""
17+
from os.path import join, dirname
18+
from .. import logging
19+
from .base import (traits,
20+
DynamicTraitedSpec,
21+
Directory,
22+
BaseInterface,
23+
isdefined,
24+
Str,
25+
Undefined)
26+
27+
try:
28+
from bids import grabbids as gb
29+
import json
30+
except ImportError:
31+
have_pybids = False
32+
else:
33+
have_pybids = True
34+
35+
LOGGER = logging.getLogger('workflows')
36+
37+
class BIDSDataGrabberInputSpec(DynamicTraitedSpec):
38+
base_dir = Directory(exists=True,
39+
desc='Path to BIDS Directory.',
40+
mandatory=True)
41+
output_query = traits.Dict(key_trait=Str,
42+
value_trait=traits.Dict,
43+
desc='Queries for outfield outputs')
44+
raise_on_empty = traits.Bool(True, usedefault=True,
45+
desc='Generate exception if list is empty '
46+
'for a given field')
47+
return_type = traits.Enum('file', 'namedtuple', usedefault=True)
48+
49+
50+
class BIDSDataGrabber(BaseInterface):
51+
52+
""" BIDS datagrabber module that wraps around pybids to allow arbitrary
53+
querying of BIDS datasets.
54+
55+
Examples
56+
--------
57+
58+
>>> from nipype.interfaces.bids_utils import BIDSDataGrabber
59+
>>> from os.path import basename
60+
61+
By default, the BIDSDataGrabber fetches anatomical and functional images
62+
from a project, and makes BIDS entities (e.g. subject) available for
63+
filtering outputs.
64+
65+
>>> bg = BIDSDataGrabber()
66+
>>> bg.inputs.base_dir = 'ds005/'
67+
>>> bg.inputs.subject = '01'
68+
>>> results = bg.run()
69+
>>> basename(results.outputs.anat[0]) # doctest: +ALLOW_UNICODE
70+
'sub-01_T1w.nii.gz'
71+
72+
>>> basename(results.outputs.func[0]) # doctest: +ALLOW_UNICODE
73+
'sub-01_task-mixedgamblestask_run-01_bold.nii.gz'
74+
75+
76+
Dynamically created, user-defined output fields can also be defined to
77+
return different types of outputs from the same project. All outputs
78+
are filtered on common entities, which can be explicitly defined as
79+
infields.
80+
81+
>>> bg = BIDSDataGrabber(infields = ['subject'], outfields = ['dwi'])
82+
>>> bg.inputs.base_dir = 'ds005/'
83+
>>> bg.inputs.subject = '01'
84+
>>> bg.inputs.output_query['dwi'] = dict(modality='dwi')
85+
>>> results = bg.run()
86+
>>> basename(results.outputs.dwi[0]) # doctest: +ALLOW_UNICODE
87+
'sub-01_dwi.nii.gz'
88+
89+
"""
90+
input_spec = BIDSDataGrabberInputSpec
91+
output_spec = DynamicTraitedSpec
92+
_always_run = True
93+
94+
def __init__(self, infields=None, outfields=None, **kwargs):
95+
"""
96+
Parameters
97+
----------
98+
infields : list of str
99+
Indicates the input fields to be dynamically created
100+
101+
outfields: list of str
102+
Indicates output fields to be dynamically created.
103+
If no matching items, returns Undefined.
104+
"""
105+
super(BIDSDataGrabber, self).__init__(**kwargs)
106+
if not have_pybids:
107+
raise ImportError("The BIDSEventsGrabber interface requires pybids."
108+
" Please make sure it is installed.")
109+
110+
# If outfields is None use anat and func as default
111+
if outfields is None:
112+
outfields = ['func', 'anat']
113+
self.inputs.output_query = {
114+
"func": {"modality": "func"},
115+
"anat": {"modality": "anat"}}
116+
else:
117+
self.inputs.output_query = {}
118+
119+
# If infields is None, use all BIDS entities
120+
if infields is None:
121+
bids_config = join(dirname(gb.__file__), 'config', 'bids.json')
122+
bids_config = json.load(open(bids_config, 'r'))
123+
infields = [i['name'] for i in bids_config['entities']]
124+
125+
self._infields = infields
126+
self._outfields = outfields
127+
128+
# used for mandatory inputs check
129+
undefined_traits = {}
130+
for key in infields:
131+
self.inputs.add_trait(key, traits.Any)
132+
undefined_traits[key] = Undefined
133+
134+
self.inputs.trait_set(trait_change_notify=False, **undefined_traits)
135+
136+
def _run_interface(self, runtime):
137+
return runtime
138+
139+
def _list_outputs(self):
140+
layout = gb.BIDSLayout(self.inputs.base_dir)
141+
142+
for key in self._outfields:
143+
if key not in self.inputs.output_query:
144+
raise ValueError("Define query for all outputs")
145+
146+
# If infield is not given nm input value, silently ignore
147+
filters = {}
148+
for key in self._infields:
149+
value = getattr(self.inputs, key)
150+
if isdefined(value):
151+
filters[key] = value
152+
153+
outputs = {}
154+
for key, query in self.inputs.output_query.items():
155+
args = query.copy()
156+
args.update(filters)
157+
filelist = layout.get(return_type=self.inputs.return_type,
158+
**args)
159+
if len(filelist) == 0:
160+
msg = 'Output key: %s returned no files' % (
161+
key)
162+
if self.inputs.raise_on_empty:
163+
raise IOError(msg)
164+
else:
165+
LOGGER.warning(msg)
166+
filelist = Undefined
167+
168+
outputs[key] = filelist
169+
return outputs

nipype/interfaces/io.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
from .base import (
4141
TraitedSpec, traits, Str, File, Directory, BaseInterface, InputMultiPath,
4242
isdefined, OutputMultiPath, DynamicTraitedSpec, Undefined, BaseInterfaceInputSpec)
43+
from .bids_utils import BIDSDataGrabber
4344

4445
try:
4546
import pyxnat

0 commit comments

Comments
 (0)