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