From 708f41b8cf818cfbae0d20a065c60ad0ae2ae085 Mon Sep 17 00:00:00 2001 From: Ross Markello Date: Tue, 9 Oct 2018 15:14:37 -0400 Subject: [PATCH 1/5] ENH: Parse Boutiques output structure Adds support for dynamic parsing of boutiques output-files spec. Unfortunately, boutiques does not support specification of which input options will lead to the generation of specific output files, so _all_ output files are being specified. This will likely lead to some problems down the road, and might be worth suggesting to the boutiques team as a future enhancement of their schema. --- nipype/interfaces/base/boutiques.py | 84 +++++++++++++++++++++++++---- 1 file changed, 74 insertions(+), 10 deletions(-) diff --git a/nipype/interfaces/base/boutiques.py b/nipype/interfaces/base/boutiques.py index 9e1cfb2ac2..cc3f7830fe 100644 --- a/nipype/interfaces/base/boutiques.py +++ b/nipype/interfaces/base/boutiques.py @@ -48,19 +48,19 @@ def __init__(self, boutique_spec, **inputs): self._load_groups(boutique_spec.get('groups', [])) self._populate_input_spec(boutique_spec.get('inputs', [])) - #self._populate_output_spec(boutique_spec.get('output-files', [])) + self._populate_output_spec(boutique_spec.get('output-files', [])) self.inputs.trait_set(trait_change_notify=False, **inputs) def _load_groups(self, groups): for group in groups: members = group['members'] - if group['all-or-none']: + if group.get('all-or-none'): for member in members: self._requires[member].extend(members) - elif group['mutually-exclusive']: + elif group.get('mutually-exclusive'): for member in members: self._xors[member].extend(members) - elif group['one-is-required']: + elif group.get('one-is-required'): for member in members: self._one_required[member].extend(members) @@ -73,7 +73,6 @@ def _populate_input_spec(self, input_list): metadata = {} # Establish trait type - typestr = input_dict['type'] if 'value-choices' in input_dict: ttype = traits.Enum @@ -100,7 +99,7 @@ def _populate_input_spec(self, input_list): metadata['usedefault'] = True if input_dict.get('list'): - if args: + if len(args) > 0: ttype = ttype(*args) args = [] metadata['trait'] = ttype @@ -146,7 +145,7 @@ def _populate_input_spec(self, input_list): []).extend(self._requires[trait_name]) if trait_name in self._xors: metadata.setdefault('xor', - []).extend(self._xor[trait_name]) + []).extend(self._xors[trait_name]) trait = ttype(*args, **metadata) self.inputs.add_trait(trait_name, trait) @@ -158,13 +157,78 @@ def _populate_input_spec(self, input_list): **undefined_traits) self.value_keys = value_keys + def _populate_output_spec(self, output_list): + self.outputs = self.output_spec() + value_keys = {} + for output_dict in output_list: + trait_name = output_dict['id'] + ttype = traits.File + metadata = {} + args = [Undefined] + + if output_dict.get('list'): + metadata['trait'] = ttype + ttype = traits.List + args = [] + + if output_dict.get('description') is not None: + metadata['desc'] = output_dict['description'] + + metadata['mandatory'] = not output_dict.get('optional', False) + + if 'command-line-flag' in output_dict: + argstr = output_dict['command-line-flag'] + argstr += output_dict.get('command-line-flag-separator', ' ') + argstr += '%s' # May be insufficient for some + metadata['argstr'] = argstr + + trait = ttype(*args, **metadata) + self.outputs.add_trait(trait_name, trait) + + if 'value-key' in output_dict: + value_keys[output_dict['value-key']] = trait_name + + self.value_keys.update(value_keys) + + def _list_outputs(self): + # NOTE + # currently, boutiques doesn't provide a mechanism to determine which + # input parameters will cause generation of output files if the outputs + # are set as optional. this makes handling which outputs should be + # defined a bit difficult... for now, i'm defining everything and will + # think about a better way to handle this in the future + output_list = self.boutique_spec.get('output-files', []) + outputs = self.outputs.get() + + for n, out in enumerate([f['id'] for f in output_list]): + output_dict = output_list[n] + # get path template + stripped extensions + output_filename = output_dict['path-template'] + strip = output_dict.get('path-template-stripped-extensions', []) + + # replace all value-keys in output name + for valkey, name in self.value_keys.items(): + repl = self.inputs.trait_get().get(name) + # if input is specified, strip extensions + replace in output + if repl is not None and isdefined(repl): + for ext in strip: + repl = repl[:-len(ext)] if repl.endswith(ext) else repl + output_filename = output_filename.replace(valkey, repl) + + # TODO: check whether output should actually be defined (see above) + outputs[out] = output_filename + + return outputs + def cmdline(self): args = self._argspec - inputs = self.inputs.trait_get() + inputs = {**self.inputs.trait_get(), **self._list_outputs()} for valkey, name in self.value_keys.items(): - spec = self.inputs.traits()[name] + try: + spec = self.inputs.traits()[name] + except KeyError: + spec = self.outputs.traits()[name] value = inputs[name] - if not isdefined(value): value = '' elif spec.argstr: From cb8342c68ce9275b83cef1bb98c18972fd9c3faa Mon Sep 17 00:00:00 2001 From: Ross Markello Date: Tue, 9 Oct 2018 17:01:49 -0400 Subject: [PATCH 2/5] REF: Generates specs for boutique interfaces Rather than assigning to self.inputs or self.outputs, BoutiqueInterface will actually generate subclasses of DynamicTraitedSpec (so that things like .help() might eventually work). --- nipype/interfaces/base/boutiques.py | 51 ++++++++++++++++++----------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/nipype/interfaces/base/boutiques.py b/nipype/interfaces/base/boutiques.py index cc3f7830fe..c998d4b63f 100644 --- a/nipype/interfaces/base/boutiques.py +++ b/nipype/interfaces/base/boutiques.py @@ -15,7 +15,6 @@ class BoutiqueInterface(CommandLine): """Convert Boutique specification to Nipype interface - """ input_spec = DynamicTraitedSpec @@ -40,16 +39,20 @@ def __init__(self, boutique_spec, **inputs): self._cmd = split_cmd[0] self._argspec = split_cmd[1] if len(split_cmd) > 1 else None - super().__init__() - self._xors = defaultdict(list) self._requires = defaultdict(list) self._one_required = defaultdict(list) - self._load_groups(boutique_spec.get('groups', [])) + # we're going to actually generate input_spec and output_spec classes + # so we want this to occur before the super().__init__() call + self._load_groups(boutique_spec.get('groups', [])) self._populate_input_spec(boutique_spec.get('inputs', [])) self._populate_output_spec(boutique_spec.get('output-files', [])) - self.inputs.trait_set(trait_change_notify=False, **inputs) + + super().__init__() + + # now set all the traits that don't have defaults/inputs to undefined + self._set_undefined(**inputs) def _load_groups(self, groups): for group in groups: @@ -64,9 +67,16 @@ def _load_groups(self, groups): for member in members: self._one_required[member].extend(members) + def _set_undefined(self, **inputs): + usedefault = self.inputs.traits(usedefault=True) + undefined = {k: Undefined for k in + set(self.inputs.get()) - set(usedefault)} + self.inputs.trait_set(trait_change_notify=False, **undefined) + self.inputs.trait_set(trait_change_notify=False, **inputs) + def _populate_input_spec(self, input_list): + input_spec = {} value_keys = {} - undefined_traits = {} for input_dict in input_list: trait_name = input_dict['id'] args = [] @@ -148,17 +158,19 @@ def _populate_input_spec(self, input_list): []).extend(self._xors[trait_name]) trait = ttype(*args, **metadata) - self.inputs.add_trait(trait_name, trait) - if not trait.usedefault: - undefined_traits[trait_name] = Undefined + input_spec[trait_name] = trait + value_keys[input_dict['value-key']] = trait_name - self.inputs.trait_set(trait_change_notify=False, - **undefined_traits) + self.input_spec = type('{}InputSpec'.format(self._cmd), + (self.input_spec,), + input_spec) + + # TODO: value-keys aren't necessarily mutually exclusive; use id as key self.value_keys = value_keys def _populate_output_spec(self, output_list): - self.outputs = self.output_spec() + output_spec = {} value_keys = {} for output_dict in output_list: trait_name = output_dict['id'] @@ -183,22 +195,21 @@ def _populate_output_spec(self, output_list): metadata['argstr'] = argstr trait = ttype(*args, **metadata) - self.outputs.add_trait(trait_name, trait) + output_spec[trait_name] = trait if 'value-key' in output_dict: value_keys[output_dict['value-key']] = trait_name + # reassign output spec class based on compiled outputs + self.output_spec = type('{}OutputSpec'.format(self._cmd), + (self.output_spec,), + output_spec) + self.value_keys.update(value_keys) def _list_outputs(self): - # NOTE - # currently, boutiques doesn't provide a mechanism to determine which - # input parameters will cause generation of output files if the outputs - # are set as optional. this makes handling which outputs should be - # defined a bit difficult... for now, i'm defining everything and will - # think about a better way to handle this in the future output_list = self.boutique_spec.get('output-files', []) - outputs = self.outputs.get() + outputs = self.output_spec().get() for n, out in enumerate([f['id'] for f in output_list]): output_dict = output_list[n] From 788d4f54b9d9e4155b98e8aec6df95576f97dcb7 Mon Sep 17 00:00:00 2001 From: Ross Markello Date: Tue, 9 Oct 2018 17:10:05 -0400 Subject: [PATCH 3/5] ENH: Adds help for BoutiqueInterface instances Generating an instance of BoutiqueInterface now allows for .help() to generate pretty help text using standard nipype machinery. --- nipype/interfaces/base/boutiques.py | 69 +++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/nipype/interfaces/base/boutiques.py b/nipype/interfaces/base/boutiques.py index c998d4b63f..df3189e28f 100644 --- a/nipype/interfaces/base/boutiques.py +++ b/nipype/interfaces/base/boutiques.py @@ -9,6 +9,7 @@ from ..base import ( traits, File, Str, isdefined, Undefined, DynamicTraitedSpec, CommandLine) +from ...utils.misc import trim iflogger = logging.getLogger('nipype.interface') @@ -246,3 +247,71 @@ def cmdline(self): value = self._format_arg(name, spec, value) args = args.replace(valkey, value) return self._cmd + ' ' + args + + def help(self, returnhelp=False): + """ Prints interface help + """ + + docs = self.boutique_spec.get('description') + if docs is not None: + docstring = trim(docs).split('\n') + [''] + else: + docstring = [self.__class__.__doc__] + + allhelp = '\n'.join(docstring + + self._inputs_help() + [''] + + self._outputs_help() + [''] + + self._refs_help() + ['']) + if returnhelp: + return allhelp + else: + print(allhelp) + + def _inputs_help(self): + """ Prints description for input parameters + """ + helpstr = ['Inputs::'] + + inputs = self.input_spec() + if len(list(inputs.traits(transient=None).items())) == 0: + helpstr += ['', '\tNone'] + return helpstr + + manhelpstr = ['', '\t[Mandatory]'] + mandatory_items = inputs.traits(mandatory=True) + for name, spec in sorted(mandatory_items.items()): + manhelpstr += self.__class__._get_trait_desc(inputs, name, spec) + + opthelpstr = ['', '\t[Optional]'] + for name, spec in sorted(inputs.traits(transient=None).items()): + if name in mandatory_items: + continue + opthelpstr += self.__class__._get_trait_desc(inputs, name, spec) + + if manhelpstr: + helpstr += manhelpstr + if opthelpstr: + helpstr += opthelpstr + return helpstr + + def _outputs_help(self): + """ Prints description for output parameters + """ + helpstr = ['Outputs::', ''] + if self.output_spec: + outputs = self.output_spec() + for name, spec in sorted(outputs.traits(transient=None).items()): + helpstr += self.__class__._get_trait_desc(outputs, name, spec) + if len(helpstr) == 2: + helpstr += ['\tNone'] + return helpstr + + def _refs_help(self): + """ Prints interface references. + """ + if self.boutique_spec.get('tool-doi') is None: + return [] + + helpstr = ['References::', self.boutique_spec.get('tool-doi')] + + return helpstr From 8c1d3439778581c09de375d8431b5a51c206623d Mon Sep 17 00:00:00 2001 From: Ross Markello Date: Tue, 9 Oct 2018 18:18:22 -0400 Subject: [PATCH 4/5] REF: Fixes BoutiqueInterface file handling Better file handling (existence checking, output aggregating) for BoutiqueInterface --- nipype/interfaces/base/boutiques.py | 68 ++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 15 deletions(-) diff --git a/nipype/interfaces/base/boutiques.py b/nipype/interfaces/base/boutiques.py index df3189e28f..2436cd7ccb 100644 --- a/nipype/interfaces/base/boutiques.py +++ b/nipype/interfaces/base/boutiques.py @@ -1,16 +1,21 @@ # -*- coding: utf-8 -*- # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: -import os -import json + from collections import defaultdict +import glob +from itertools import chain +import json +import os +from .core import CommandLine +from .specs import DynamicTraitedSpec +from .traits_extension import ( + File, isdefined, OutputMultiPath, Str, traits, Undefined) from ... import logging -from ..base import ( - traits, File, Str, isdefined, Undefined, - DynamicTraitedSpec, CommandLine) from ...utils.misc import trim + iflogger = logging.getLogger('nipype.interface') @@ -76,6 +81,8 @@ def _set_undefined(self, **inputs): self.inputs.trait_set(trait_change_notify=False, **inputs) def _populate_input_spec(self, input_list): + """ Generates input specification class + """ input_spec = {} value_keys = {} for input_dict in input_list: @@ -95,6 +102,8 @@ def _populate_input_spec(self, input_list): ttype = traits.Int else: ttype = self.trait_map[typestr] + if typestr == 'File': + metadata['exists'] = True if 'default-value' in input_dict: nipype_key = 'default_value' @@ -163,7 +172,7 @@ def _populate_input_spec(self, input_list): value_keys[input_dict['value-key']] = trait_name - self.input_spec = type('{}InputSpec'.format(self._cmd), + self.input_spec = type('{}InputSpec'.format(self._cmd.capitalize()), (self.input_spec,), input_spec) @@ -171,6 +180,8 @@ def _populate_input_spec(self, input_list): self.value_keys = value_keys def _populate_output_spec(self, output_list): + """ Creates output specification class + """ output_spec = {} value_keys = {} for output_dict in output_list: @@ -179,15 +190,14 @@ def _populate_output_spec(self, output_list): metadata = {} args = [Undefined] - if output_dict.get('list'): - metadata['trait'] = ttype - ttype = traits.List - args = [] - if output_dict.get('description') is not None: metadata['desc'] = output_dict['description'] - metadata['mandatory'] = not output_dict.get('optional', False) + metadata['exists'] = not output_dict.get('optional', True) + + if output_dict.get('list'): + args = [ttype(Undefined, **metadata)] + ttype = OutputMultiPath if 'command-line-flag' in output_dict: argstr = output_dict['command-line-flag'] @@ -202,13 +212,15 @@ def _populate_output_spec(self, output_list): value_keys[output_dict['value-key']] = trait_name # reassign output spec class based on compiled outputs - self.output_spec = type('{}OutputSpec'.format(self._cmd), + self.output_spec = type('{}OutputSpec'.format(self._cmd.capitalize()), (self.output_spec,), output_spec) self.value_keys.update(value_keys) def _list_outputs(self): + """ Generate list of predicted outputs based on defined inputs + """ output_list = self.boutique_spec.get('output-files', []) outputs = self.output_spec().get() @@ -227,12 +239,38 @@ def _list_outputs(self): repl = repl[:-len(ext)] if repl.endswith(ext) else repl output_filename = output_filename.replace(valkey, repl) - # TODO: check whether output should actually be defined (see above) - outputs[out] = output_filename + outputs[out] = os.path.abspath(output_filename) + + if output_dict.get('list'): + outputs[out] = [outputs[out]] + + return outputs + + def aggregate_outputs(self, runtime=None, needed_outputs=None): + """ Collate expected outputs and check for existence + """ + outputs = super().aggregate_outputs(runtime=runtime, + needed_outputs=needed_outputs) + for key, val in outputs.get().items(): + # since we can't know if the output will be generated based on the + # boutiques spec, reset all non-existent outputs to undefined + if isinstance(val, list): + # glob expected output path template and flatten list + val = list(chain.from_iterable([glob.glob(v) for v in val])) + if len(val) == 0: + val = Undefined + setattr(outputs, key, val) + else: + if not os.path.exists(val): + val = Undefined + if not os.path.exists(val): + setattr(outputs, key, Undefined) return outputs def cmdline(self): + """ Prints command line with all specified arguments + """ args = self._argspec inputs = {**self.inputs.trait_get(), **self._list_outputs()} for valkey, name in self.value_keys.items(): From 15f9f1093d05bfa6a6b17d45eb68946064577d5b Mon Sep 17 00:00:00 2001 From: Ross Markello Date: Tue, 9 Oct 2018 18:45:07 -0400 Subject: [PATCH 5/5] ENH: Adds BoutiqueInterface doc example Adds example to BoutiqueInterface and makes some minor updates to input spec handling. --- nipype/interfaces/base/boutiques.py | 23 +- nipype/testing/data/boutiques_fslbet.json | 370 ++++++++++++++++++++++ 2 files changed, 387 insertions(+), 6 deletions(-) create mode 100644 nipype/testing/data/boutiques_fslbet.json diff --git a/nipype/interfaces/base/boutiques.py b/nipype/interfaces/base/boutiques.py index 2436cd7ccb..ef9a20f7b5 100644 --- a/nipype/interfaces/base/boutiques.py +++ b/nipype/interfaces/base/boutiques.py @@ -21,6 +21,17 @@ class BoutiqueInterface(CommandLine): """Convert Boutique specification to Nipype interface + + Examples + -------- + >>> from nipype.interfaces.base import BoutiqueInterface + >>> bet = BoutiqueInterface('boutiques_fslbet.json') + >>> bet.inputs.in_file = 'structural.nii' + >>> bet.inputs.frac = 0.7 + >>> bet.inputs.out_file = 'brain_anat.nii' + >>> bet.cmdline + 'bet structural.nii brain_anat.nii -f 0.70' + >>> res = bet.run() # doctest: +SKIP """ input_spec = DynamicTraitedSpec @@ -66,10 +77,10 @@ def _load_groups(self, groups): if group.get('all-or-none'): for member in members: self._requires[member].extend(members) - elif group.get('mutually-exclusive'): + if group.get('mutually-exclusive'): for member in members: self._xors[member].extend(members) - elif group.get('one-is-required'): + if group.get('one-is-required'): for member in members: self._one_required[member].extend(members) @@ -170,7 +181,7 @@ def _populate_input_spec(self, input_list): trait = ttype(*args, **metadata) input_spec[trait_name] = trait - value_keys[input_dict['value-key']] = trait_name + value_keys[trait_name] = input_dict['value-key'] self.input_spec = type('{}InputSpec'.format(self._cmd.capitalize()), (self.input_spec,), @@ -209,7 +220,7 @@ def _populate_output_spec(self, output_list): output_spec[trait_name] = trait if 'value-key' in output_dict: - value_keys[output_dict['value-key']] = trait_name + value_keys[trait_name] = output_dict['value-key'] # reassign output spec class based on compiled outputs self.output_spec = type('{}OutputSpec'.format(self._cmd.capitalize()), @@ -231,7 +242,7 @@ def _list_outputs(self): strip = output_dict.get('path-template-stripped-extensions', []) # replace all value-keys in output name - for valkey, name in self.value_keys.items(): + for name, valkey in self.value_keys.items(): repl = self.inputs.trait_get().get(name) # if input is specified, strip extensions + replace in output if repl is not None and isdefined(repl): @@ -273,7 +284,7 @@ def cmdline(self): """ args = self._argspec inputs = {**self.inputs.trait_get(), **self._list_outputs()} - for valkey, name in self.value_keys.items(): + for name, valkey in self.value_keys.items(): try: spec = self.inputs.traits()[name] except KeyError: diff --git a/nipype/testing/data/boutiques_fslbet.json b/nipype/testing/data/boutiques_fslbet.json new file mode 100644 index 0000000000..22e0a5f306 --- /dev/null +++ b/nipype/testing/data/boutiques_fslbet.json @@ -0,0 +1,370 @@ +{ + "tool-version": "1.0.0", + "name": "fsl_bet", + "command-line": "bet [INPUT_FILE] [OUT_FILE] [FRACTIONAL_INTENSITY] [VERTICAL_GRADIENT] [CENTER_OF_GRAVITY] [OVERLAY_FLAG] [BINARY_MASK_FLAG] [APPROX_SKULL_FLAG] [NO_SEG_OUTPUT_FLAG] [VTK_VIEW_FLAG] [HEAD_RADIUS] [THRESHOLDING_FLAG] [ROBUST_ITERS_FLAG] [RES_OPTIC_CLEANUP_FLAG] [REDUCE_BIAS_FLAG] [SLICE_PADDING_FLAG] [MASK_WHOLE_SET_FLAG] [ADD_SURFACES_FLAG] [ADD_SURFACES_T2] [VERBOSE_FLAG] [DEBUG_FLAG]", + "inputs": [ + { + "description": "Input image to be skullstripped (e.g. img.nii.gz)", + "value-key": "[INPUT_FILE]", + "type": "File", + "optional": false, + "id": "in_file", + "name": "Input file" + }, + { + "description": "Name of generated, skulltripped image (e.g. img_bet.nii.gz)", + "value-key": "[OUT_FILE]", + "type": "File", + "optional": false, + "id": "out_file", + "name": "Output file" + }, + { + "command-line-flag": "-f", + "description": "Fractional intensity threshold (0->1); default=0.5; smaller values give larger brain outline estimates", + "value-key": "[FRACTIONAL_INTENSITY]", + "type": "Number", + "maximum": 1, + "minimum": 0, + "integer": false, + "optional": true, + "id": "fractional_intensity", + "name": "Fractional intensity threshold" + }, + { + "command-line-flag": "-g", + "description": "Vertical gradient in fractional intensity threshold (-1->1); default=0; positive values give larger brain outline at bottom, smaller at top", + "value-key": "[VERTICAL_GRADIENT]", + "type": "Number", + "maximum": 1, + "minimum": -1, + "integer": false, + "optional": true, + "id": "vg_fractional_intensity", + "name": "Vertical gradient fractional intensity threshold" + }, + { + "command-line-flag": "-c", + "description": "The xyz coordinates of the center of gravity (voxels, not mm) of initial mesh surface. Must have exactly three numerical entries in the list (3-vector).", + "value-key": "[CENTER_OF_GRAVITY]", + "type": "Number", + "list": true, + "max-list-entries": 3, + "optional": true, + "id": "center_of_gravity", + "min-list-entries": 3, + "name": "Center of gravity vector" + }, + { + "command-line-flag": "-o", + "description": "Generate brain surface outline overlaid onto original image", + "value-key": "[OVERLAY_FLAG]", + "type": "Flag", + "optional": true, + "id": "overlay_flag", + "name": "Overlay flag" + }, + { + "command-line-flag": "-m", + "description": "Generate binary brain mask", + "value-key": "[BINARY_MASK_FLAG]", + "type": "Flag", + "optional": true, + "id": "binary_mask_flag", + "name": "Binary mask flag" + }, + { + "command-line-flag": "-s", + "description": "Generate rough skull image (not as clean as betsurf)", + "value-key": "[APPROX_SKULL_FLAG]", + "type": "Flag", + "optional": true, + "id": "approx_skull_flag", + "name": "Approximate skull flag" + }, + { + "command-line-flag": "-n", + "description": "Don't generate segmented brain image output", + "value-key": "[NO_SEG_OUTPUT_FLAG]", + "type": "Flag", + "optional": true, + "id": "no_seg_output_flag", + "name": "No segmented brain image flag" + }, + { + "command-line-flag": "-e", + "description": "Generate brain surface as mesh in .vtk format", + "value-key": "[VTK_VIEW_FLAG]", + "type": "Flag", + "optional": true, + "id": "vtk_mesh", + "name": "VTK format brain surface mesh flag" + }, + { + "command-line-flag": "-r", + "description": "head radius (mm not voxels); initial surface sphere is set to half of this", + "value-key": "[HEAD_RADIUS]", + "type": "Number", + "optional": true, + "id": "head_radius", + "name": "Head Radius" + }, + { + "command-line-flag": "-t", + "description": "Apply thresholding to segmented brain image and mask", + "value-key": "[THRESHOLDING_FLAG]", + "type": "Flag", + "optional": true, + "id": "thresholding_flag", + "name": "Threshold segmented image flag" + }, + { + "command-line-flag": "-R", + "description": "More robust brain center estimation, by iterating BET with a changing center-of-gravity.", + "value-key": "[ROBUST_ITERS_FLAG]", + "type": "Flag", + "optional": true, + "id": "robust_iters_flag", + "name": "Robust iterations flag" + }, + { + "command-line-flag": "-S", + "description": "This attempts to cleanup residual eye and optic nerve voxels which bet2 can sometimes leave behind. This can be useful when running SIENA or SIENAX, for example. Various stages involving standard-space masking, morphpological operations and thresholdings are combined to produce a result which can often give better results than just running bet2.", + "value-key": "[RES_OPTIC_CLEANUP_FLAG]", + "type": "Flag", + "optional": true, + "id": "residual_optic_cleanup_flag", + "name": "Residual optic cleanup flag" + }, + { + "command-line-flag": "-B", + "description": "This attempts to reduce image bias, and residual neck voxels. This can be useful when running SIENA or SIENAX, for example. Various stages involving FAST segmentation-based bias field removal and standard-space masking are combined to produce a result which can often give better results than just running bet2.", + "value-key": "[REDUCE_BIAS_FLAG]", + "type": "Flag", + "optional": true, + "id": "reduce_bias_flag", + "name": "Bias reduction flag" + }, + { + "command-line-flag": "-Z", + "description": "This can improve the brain extraction if only a few slices are present in the data (i.e., a small field of view in the Z direction). This is achieved by padding the end slices in both directions, copying the end slices several times, running bet2 and then removing the added slices.", + "value-key": "[SLICE_PADDING_FLAG]", + "type": "Flag", + "optional": true, + "id": "slice_padding_flag", + "name": "Slice padding flag" + }, + { + "command-line-flag": "-F", + "description": "This option uses bet2 to determine a brain mask on the basis of the first volume in a 4D data set, and applies this to the whole data set. This is principally intended for use on FMRI data, for example to remove eyeballs. Because it is normally important (in this application) that masking be liberal (ie that there be little risk of cutting out valid brain voxels) the -f threshold is reduced to 0.3, and also the brain mask is \"dilated\" slightly before being used.", + "value-key": "[MASK_WHOLE_SET_FLAG]", + "type": "Flag", + "optional": true, + "id": "whole_set_mask_flag", + "name": "Mask-whole-set flag" + }, + { + "command-line-flag": "-A", + "description": "This runs both bet2 and betsurf programs in order to get the additional skull and scalp surfaces created by betsurf. This involves registering to standard space in order to allow betsurf to find the standard space masks it needs.", + "value-key": "[ADD_SURFACES_FLAG]", + "type": "Flag", + "optional": true, + "id": "additional_surfaces_flag", + "name": "Additional surfaces flag" + }, + { + "command-line-flag": "-A2", + "description": "This is the same as -A except that a T2 image is also input, to further improve the estimated skull and scalp surfaces. As well as carrying out the standard space registration this also registers the T2 to the T1 input image.", + "value-key": "[ADD_SURFACES_T2]", + "type": "File", + "optional": true, + "id": "additional_surfaces_t2", + "name": "Additional surfaces with T2" + }, + { + "command-line-flag": "-v", + "description": "Switch on diagnostic messages", + "value-key": "[VERBOSE_FLAG]", + "type": "Flag", + "optional": true, + "id": "verbose_flag", + "name": "Verbose Flag" + }, + { + "command-line-flag": "-d", + "description": "Don't delete temporary intermediate images", + "value-key": "[DEBUG_FLAG]", + "type": "Flag", + "optional": true, + "id": "debug_flag", + "name": "Debug Flag" + } + ], + "schema-version": "0.5", + "groups": [ + { + "description": "Specify parameters that alter the default BET functionality", + "id": "optional_params_group", + "members": [ + "fractional_intensity", + "vg_fractional_intensity", + "center_of_gravity", + "overlay_flag", + "binary_mask_flag", + "approx_skull_flag", + "no_seg_output_flag", + "vtk_mesh", + "head_radius", + "thresholding_flag" + ], + "name": "Main Program Parameters" + }, + { + "description": "Mutually exclusive options that specify variations on how BET should be run.", + "mutually-exclusive": true, + "id": "variational_params_group", + "members": [ + "robust_iters_flag", + "residual_optic_cleanup_flag", + "reduce_bias_flag", + "slice_padding_flag", + "whole_set_mask_flag", + "additional_surfaces_flag", + "additional_surfaces_t2" + ], + "name": "Variations on Default Functionality" + }, + { + "description": "Optional miscellaneous parameters when running BET", + "id": "miscellaneous_params_group", + "members": [ + "verbose_flag", + "debug_flag" + ], + "name": "Miscellaneous Parameters" + } + ], + "output-files": [ + { + "path-template": "[OUT_FILE].nii.gz", + "description": "Default skullstripped image generated by BET", + "optional": true, + "id": "outfile", + "name": "Output mask file", + "path-template-stripped-extensions": [".nii.gz", ".nii"] + }, + { + "path-template": "[OUT_FILE]_mask.nii.gz", + "description": "Binary mask file (from -m option)", + "optional": true, + "id": "binary_mask", + "name": "Output binary mask file", + "path-template-stripped-extensions": [".nii.gz", ".nii"] + }, + { + "path-template": "[OUT_FILE]_overlay.nii.gz", + "description": "Overlaid brain surface onto original image", + "optional": true, + "id": "overlay_file", + "name": "Surface overlay file", + "path-template-stripped-extensions": [".nii.gz", ".nii"] + }, + { + "path-template": "[OUT_FILE]_skull.nii.gz", + "description": "Approximate skull image file", + "optional": true, + "id": "approx_skull_img", + "name": "Approximate skull file", + "path-template-stripped-extensions": [".nii.gz", ".nii"] + }, + { + "path-template": "[OUT_FILE]_mesh.vtk", + "description": "Mesh in VTK format", + "optional": true, + "id": "output_vtk_mesh", + "name": "VTK mesh", + "path-template-stripped-extensions": [".nii.gz", ".nii"] + }, + { + "path-template": "[OUT_FILE]_skull_mask.nii.gz", + "description": "Output mask for skull image", + "optional": true, + "id": "skull_mask", + "name": "Skull mask image", + "path-template-stripped-extensions": [".nii.gz", ".nii"] + }, + { + "path-template": "[OUT_FILE]_inskull_mask.nii.gz", + "description": "The in-skull mask file from betsurf (from -A or -A2)", + "optional": true, + "id": "out_inskull_mask", + "name": "Output in-skull mask file", + "path-template-stripped-extensions": [".nii.gz", ".nii"] + }, + { + "path-template": "[OUT_FILE]_inskull_mesh.nii.gz", + "description": "The in-skull mesh file from betsurf (from -A or -A2)", + "optional": true, + "id": "out_inskull_mesh", + "name": "Output in-skull mesh file", + "path-template-stripped-extensions": [".nii.gz", ".nii"] + }, + { + "path-template": "[OUT_FILE]_inskull_mesh.off", + "description": "The in-skull mesh .off file from betsurf (from -A or -A2)", + "optional": true, + "id": "out_inskull_off", + "name": "Output in-skull mesh off file", + "path-template-stripped-extensions": [".nii.gz", ".nii"] + }, + { + "path-template": "[OUT_FILE]_outskin_mask.nii.gz", + "description": "The out-skin mask file from betsurf (from -A or -A2)", + "optional": true, + "id": "out_outskin_mask", + "name": "Output out-skin mask file", + "path-template-stripped-extensions": [".nii.gz", ".nii"] + }, + { + "path-template": "[OUT_FILE]_outskin_mesh.nii.gz", + "description": "The out-skin mesh file from betsurf (from -A or -A2)", + "optional": true, + "id": "out_outskin_mesh", + "name": "Output out-skin mesh file", + "path-template-stripped-extensions": [".nii.gz", ".nii"] + }, + { + "path-template": "[OUT_FILE]_outskin_mesh.off", + "description": "The out-skin mesh .off file from betsurf (from -A or -A2)", + "optional": true, + "id": "out_outskin_off", + "name": "Output out-skin mesh off file", + "path-template-stripped-extensions": [".nii.gz", ".nii"] + }, + { + "path-template": "[OUT_FILE]_outskull_mask.nii.gz", + "description": "The out-skull mask file from betsurf (from -A or -A2)", + "optional": true, + "id": "out_outskull_mask", + "name": "Output out-skull mask file", + "path-template-stripped-extensions": [".nii.gz", ".nii"] + }, + { + "path-template": "[OUT_FILE]_outskull_mesh.nii.gz", + "description": "The out-skull mesh file from betsurf (from -A or -A2)", + "optional": true, + "id": "out_outskull_mesh", + "name": "Output out-skull mesh file", + "path-template-stripped-extensions": [".nii.gz", ".nii"] + }, + { + "path-template": "[OUT_FILE]_outskull_mesh.off", + "description": "The out-skull mesh .off file from betsurf (from -A or -A2)", + "optional": true, + "id": "out_outskull_off", + "name": "Output out-skull mesh off file", + "path-template-stripped-extensions": [".nii.gz", ".nii"] + } + ], + "description": "Automated brain extraction tool for FSL" +}