|
| 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 | + >>> filepath = os.path.dirname( os.path.realpath( __file__ ) ) |
| 12 | + >>> datadir = os.path.realpath(os.path.join(filepath, '../testing/data')) |
| 13 | + >>> os.chdir(datadir) |
| 14 | +
|
| 15 | +""" |
| 16 | + |
| 17 | +from .base import (traits, |
| 18 | + DynamicTraitedSpec, |
| 19 | + BaseInterface, |
| 20 | + isdefined, |
| 21 | + Str, |
| 22 | + Undefined) |
| 23 | + |
| 24 | +from bids.grabbids import BIDSLayout |
| 25 | + |
| 26 | + |
| 27 | +class BIDSDataGrabberInputSpec(DynamicTraitedSpec): |
| 28 | + base_dir = traits.Directory(exists=True, |
| 29 | + desc='Path to BIDS Directory.', |
| 30 | + mandatory=True) |
| 31 | + output_query = traits.Dict(key_trait=Str, |
| 32 | + value_trait=traits.Dict, |
| 33 | + desc='Queries for outfield outputs') |
| 34 | + return_type = traits.Enum('filename', 'namedtuple', usedefault=True) |
| 35 | + |
| 36 | + |
| 37 | +class BIDSDataGrabber(BaseInterface): |
| 38 | + |
| 39 | + """ BIDS datagrabber module that wraps around pybids to allow arbitrary |
| 40 | + querying of BIDS datasets. |
| 41 | +
|
| 42 | + Examples |
| 43 | + -------- |
| 44 | +
|
| 45 | + >>> from nipype.interfaces.bids import BIDSDataGrabber |
| 46 | + >>> from os.path import basename |
| 47 | + >>> import pprint |
| 48 | +
|
| 49 | + Select all files from a BIDS project |
| 50 | +
|
| 51 | + >>> bg = BIDSDataGrabber() |
| 52 | + >>> bg.inputs.base_dir = 'ds005/' |
| 53 | + >>> results = bg.run() |
| 54 | + >>> pprint.pprint(len(results.outputs.outfield)) # doctest: +ALLOW_UNICODE |
| 55 | + 116 |
| 56 | +
|
| 57 | + Using dynamically created, user-defined input fields, |
| 58 | + filter files based on BIDS entities. |
| 59 | +
|
| 60 | + >>> bg = BIDSDataGrabber(infields = ['subject', 'run']) |
| 61 | + >>> bg.inputs.base_dir = 'ds005/' |
| 62 | + >>> bg.inputs.subject = '01' |
| 63 | + >>> bg.inputs.run = '01' |
| 64 | + >>> results = bg.run() |
| 65 | + >>> basename(results.outputs.outfield[0]) # doctest: +ALLOW_UNICODE |
| 66 | + 'sub-01_task-mixedgamblestask_run-01_bold.nii.gz' |
| 67 | +
|
| 68 | + Using user-defined output fields, return different types of outputs, |
| 69 | + filtered on common entities |
| 70 | + filter files based on BIDS entities. |
| 71 | +
|
| 72 | + >>> bg = BIDSDataGrabber(infields = ['subject'], outfields = ['func', 'anat']) |
| 73 | + >>> bg.inputs.base_dir = 'ds005/' |
| 74 | + >>> bg.inputs.subject = '01' |
| 75 | + >>> bg.inputs.output_query['func'] = dict(modality='func') |
| 76 | + >>> bg.inputs.output_query['anat'] = dict(modality='anat') |
| 77 | + >>> results = bg.run() |
| 78 | + >>> basename(results.outputs.func[0]) # doctest: +ALLOW_UNICODE |
| 79 | + 'sub-01_task-mixedgamblestask_run-01_bold.nii.gz' |
| 80 | +
|
| 81 | + >>> basename(results.outputs.anat[0]) # doctest: +ALLOW_UNICODE |
| 82 | + 'sub-01_T1w.nii.gz' |
| 83 | + """ |
| 84 | + input_spec = BIDSDataGrabberInputSpec |
| 85 | + output_spec = DynamicTraitedSpec |
| 86 | + _always_run = True |
| 87 | + |
| 88 | + def __init__(self, infields=None, outfields=None, **kwargs): |
| 89 | + """ |
| 90 | + Parameters |
| 91 | + ---------- |
| 92 | + infields : list of str |
| 93 | + Indicates the input fields to be dynamically created |
| 94 | +
|
| 95 | + outfields: list of str |
| 96 | + Indicates output fields to be dynamically created |
| 97 | +
|
| 98 | + """ |
| 99 | + if not outfields: |
| 100 | + outfields = [] |
| 101 | + if not infields: |
| 102 | + infields = [] |
| 103 | + |
| 104 | + super(BIDSDataGrabber, self).__init__(**kwargs) |
| 105 | + undefined_traits = {} |
| 106 | + # used for mandatory inputs check |
| 107 | + self._infields = infields |
| 108 | + self._outfields = outfields |
| 109 | + for key in infields: |
| 110 | + self.inputs.add_trait(key, traits.Any) |
| 111 | + undefined_traits[key] = Undefined |
| 112 | + |
| 113 | + if not isdefined(self.inputs.output_query): |
| 114 | + self.inputs.output_query = {} |
| 115 | + |
| 116 | + self.inputs.trait_set(trait_change_notify=False, **undefined_traits) |
| 117 | + |
| 118 | + def _run_interface(self, runtime): |
| 119 | + return runtime |
| 120 | + |
| 121 | + def _list_outputs(self): |
| 122 | + if not self._outfields: |
| 123 | + self._outfields = ['outfield'] |
| 124 | + self.inputs.output_query = {'outfield' : {}} |
| 125 | + else: |
| 126 | + for key in self._outfields: |
| 127 | + if key not in self.inputs.output_query: |
| 128 | + raise ValueError("Define query for all outputs") |
| 129 | + |
| 130 | + for key in self._infields: |
| 131 | + value = getattr(self.inputs, key) |
| 132 | + if not isdefined(value): |
| 133 | + msg = "%s requires a value for input '%s' because" \ |
| 134 | + " it was listed in 'infields'" % \ |
| 135 | + (self.__class__.__name__, key) |
| 136 | + raise ValueError(msg) |
| 137 | + |
| 138 | + layout = BIDSLayout(self.inputs.base_dir) |
| 139 | + |
| 140 | + filters = {i: getattr(self.inputs, i) for i in self._infields} |
| 141 | + |
| 142 | + outputs = {} |
| 143 | + for key, query in self.inputs.output_query.items(): |
| 144 | + outputs[key] = layout.get( |
| 145 | + **dict(query.items() | filters.items()), |
| 146 | + return_type='file') |
| 147 | + return outputs |
0 commit comments