3131import subprocess
3232import re
3333import tempfile
34+ from os .path import join , dirname
3435from warnings import warn
3536
3637import sqlite3
3738
3839from .. import config , logging
3940from ..utils .filemanip import copyfile , list_to_filename , filename_to_list
4041from ..utils .misc import human_order_sorted , str2bool
41- from .base import (TraitedSpec , traits , Str , File , Directory , BaseInterface ,
42- InputMultiPath , isdefined , OutputMultiPath ,
43- DynamicTraitedSpec , Undefined , BaseInterfaceInputSpec )
44- from .bids_utils import BIDSDataGrabber
42+ from .base import (
43+ TraitedSpec , traits , Str , File , Directory , BaseInterface , InputMultiPath ,
44+ isdefined , OutputMultiPath , DynamicTraitedSpec , Undefined , BaseInterfaceInputSpec )
45+
46+ have_pybids = True
47+ try :
48+ from bids import grabbids as gb
49+ except ImportError :
50+ have_pybids = False
4551
4652try :
4753 import pyxnat
@@ -2717,3 +2723,121 @@ def _list_outputs(self):
27172723 outputs = self .output_spec ().get ()
27182724 outputs ['out_file' ] = out_file
27192725 return outputs
2726+
2727+
2728+ class BIDSDataGrabberInputSpec (DynamicTraitedSpec ):
2729+ base_dir = Directory (exists = True ,
2730+ desc = 'Path to BIDS Directory.' ,
2731+ mandatory = True )
2732+ output_query = traits .Dict (key_trait = Str ,
2733+ value_trait = traits .Dict ,
2734+ desc = 'Queries for outfield outputs' )
2735+ raise_on_empty = traits .Bool (True , usedefault = True ,
2736+ desc = 'Generate exception if list is empty '
2737+ 'for a given field' )
2738+ return_type = traits .Enum ('file' , 'namedtuple' , usedefault = True )
2739+
2740+
2741+ class BIDSDataGrabber (IOBase ):
2742+
2743+ """ BIDS datagrabber module that wraps around pybids to allow arbitrary
2744+ querying of BIDS datasets.
2745+
2746+ Examples
2747+ --------
2748+
2749+ By default, the BIDSDataGrabber fetches anatomical and functional images
2750+ from a project, and makes BIDS entities (e.g. subject) available for
2751+ filtering outputs.
2752+
2753+ >>> bg = BIDSDataGrabber()
2754+ >>> bg.inputs.base_dir = 'ds005/'
2755+ >>> bg.inputs.subject = '01'
2756+ >>> results = bg.run() # doctest: +SKIP
2757+
2758+
2759+ Dynamically created, user-defined output fields can also be defined to
2760+ return different types of outputs from the same project. All outputs
2761+ are filtered on common entities, which can be explicitly defined as
2762+ infields.
2763+
2764+ >>> bg = BIDSDataGrabber(infields = ['subject'], outfields = ['dwi'])
2765+ >>> bg.inputs.base_dir = 'ds005/'
2766+ >>> bg.inputs.subject = '01'
2767+ >>> bg.inputs.output_query['dwi'] = dict(modality='dwi')
2768+ >>> results = bg.run() # doctest: +SKIP
2769+
2770+ """
2771+ input_spec = BIDSDataGrabberInputSpec
2772+ output_spec = DynamicTraitedSpec
2773+ _always_run = True
2774+
2775+ def __init__ (self , infields = None , outfields = None , ** kwargs ):
2776+ """
2777+ Parameters
2778+ ----------
2779+ infields : list of str
2780+ Indicates the input fields to be dynamically created
2781+
2782+ outfields: list of str
2783+ Indicates output fields to be dynamically created.
2784+ If no matching items, returns Undefined.
2785+ """
2786+ super (BIDSDataGrabber , self ).__init__ (** kwargs )
2787+
2788+ if not isdefined (self .inputs .output_query ):
2789+ self .inputs .output_query = {"func" : {"modality" : "func" },
2790+ "anat" : {"modality" : "anat" }}
2791+
2792+ # If infields is empty, use all BIDS entities
2793+ if infields is None and have_pybids :
2794+ bids_config = join (dirname (gb .__file__ ), 'config' , 'bids.json' )
2795+ bids_config = json .load (open (bids_config , 'r' ))
2796+ infields = [i ['name' ] for i in bids_config ['entities' ]]
2797+ # if outfields is not set, return those defined in infields
2798+ if infields and outfields is None :
2799+ outfields = [infield for infield in infields ]
2800+
2801+ self ._infields = infields or []
2802+ self ._outfields = outfields or []
2803+
2804+ # used for mandatory inputs check
2805+ undefined_traits = {}
2806+ for key in self ._infields :
2807+ self .inputs .add_trait (key , traits .Any )
2808+ undefined_traits [key ] = kwargs [key ] if key in kwargs else Undefined
2809+
2810+ self .inputs .trait_set (trait_change_notify = False , ** undefined_traits )
2811+
2812+ def _run_interface (self , runtime ):
2813+ if not have_pybids :
2814+ raise ImportError (
2815+ "The BIDSEventsGrabber interface requires pybids."
2816+ " Please make sure it is installed." )
2817+ return runtime
2818+
2819+ def _list_outputs (self ):
2820+ layout = gb .BIDSLayout (self .inputs .base_dir )
2821+
2822+ # If infield is not given nm input value, silently ignore
2823+ filters = {}
2824+ for key in self ._infields :
2825+ value = getattr (self .inputs , key )
2826+ if isdefined (value ):
2827+ filters [key ] = value
2828+
2829+ outputs = {}
2830+ for key , query in self .inputs .output_query .items ():
2831+ args = query .copy ()
2832+ args .update (filters )
2833+ filelist = layout .get (return_type = self .inputs .return_type , ** args )
2834+ if len (filelist ) == 0 :
2835+ msg = 'Output key: %s returned no files' % key
2836+ if self .inputs .raise_on_empty :
2837+ raise IOError (msg )
2838+ else :
2839+ iflogger .warning (msg )
2840+ filelist = Undefined
2841+
2842+ outputs [key ] = filelist
2843+ return outputs
0 commit comments