|
| 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 |
0 commit comments