diff --git a/nipype/interfaces/base/__init__.py b/nipype/interfaces/base/__init__.py index 2284c1763a..2c28326875 100644 --- a/nipype/interfaces/base/__init__.py +++ b/nipype/interfaces/base/__init__.py @@ -8,6 +8,8 @@ This module defines the API of all nipype interfaces. """ +from .boutiques import (BoutiqueInterface) + from .core import (Interface, BaseInterface, SimpleInterface, CommandLine, StdOutCommandLine, MpiCommandLine, SEMLikeCommandLine, LibraryBaseInterface, PackageInfo) diff --git a/nipype/interfaces/base/boutiques.py b/nipype/interfaces/base/boutiques.py new file mode 100644 index 0000000000..8a80465ea2 --- /dev/null +++ b/nipype/interfaces/base/boutiques.py @@ -0,0 +1,367 @@ +# -*- 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: + +from collections import defaultdict +import glob +from itertools import chain +import json +import os +import re + +from .core import CommandLine +from .specs import DynamicTraitedSpec +from .traits_extension import ( + File, isdefined, OutputMultiPath, Str, traits, Undefined) +from ... import logging +from ...utils.misc import trim + + +iflogger = logging.getLogger('nipype.interface') + + +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 + output_spec = DynamicTraitedSpec + + # Subclasses may override the trait_map to provide better traits + # for various types + trait_map = { + 'File': File, + 'String': Str, + 'Number': traits.Float, + 'Flag': traits.Bool, + } + + def __init__(self, boutique_spec, **inputs): + if os.path.exists(boutique_spec): + with open(boutique_spec, 'r') as fobj: + boutique_spec = json.load(fobj) + + self.boutique_spec = boutique_spec + split_cmd = boutique_spec['command-line'].split(None, 1) + self._cmd = split_cmd[0] + self._argspec = split_cmd[1] if len(split_cmd) > 1 else None + + self._xors = defaultdict(list) + self._requires = defaultdict(list) + self._one_required = defaultdict(list) + + # 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', [])) + + 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: + members = group['members'] + if group.get('all-or-none'): + for member in members: + self._requires[member].extend(members) + if group.get('mutually-exclusive'): + for member in members: + self._xors[member].extend(members) + if group.get('one-is-required'): + 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): + """ Generates input specification class + """ + input_spec = {} + value_keys = {} + for input_dict in input_list: + trait_name = input_dict['id'] + args = [] + metadata = {} + + # Establish trait type + typestr = input_dict['type'] + if 'value-choices' in input_dict: + ttype = traits.Enum + args = input_dict['value-choices'] + elif typestr == 'Number' and ('maximum' in input_dict or + 'minimum' in input_dict): + ttype = traits.Range + elif typestr == 'Number' and input_dict.get('integer'): + 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' + default_value = input_dict['default-value'] + if ttype in (traits.Range, traits.List): + nipype_key = 'value' + elif ttype is traits.Enum: + args.remove(default_value) + args.insert(0, default_value) + + if ttype is not traits.Enum: + metadata[nipype_key] = default_value + metadata['usedefault'] = True + + if input_dict.get('list'): + if len(args) > 0: + ttype = ttype(*args) + args = [] + metadata['trait'] = ttype + ttype = traits.List + + # Complete metadata + if 'command-line-flag' in input_dict: + argstr = input_dict['command-line-flag'] + if typestr != 'Flag': + argstr += input_dict.get('command-line-flag-separator', + ' ') + argstr += '%s' # May be insufficient for some + metadata['argstr'] = argstr + + direct_mappings = { + # Boutiques: Nipype + 'description': 'desc', + 'disables-inputs': 'xor', + 'exclusive-maximum': 'exclude_high', + 'exclusive-minimum': 'exclude_low', + 'max-list-entries': 'maxlen', + 'maximum': 'high', + 'min-list-entries': 'minlen', + 'minimum': 'low', + 'requires-inputs': 'requires', + } + + for boutique_key, nipype_key in direct_mappings.items(): + if boutique_key in input_dict: + metadata[nipype_key] = input_dict[boutique_key] + + # Unsupported: + # * uses-absolute-path + # * value-disables + # * value-requires + + metadata['mandatory'] = not input_dict.get('optional', False) + + # This is a little weird and hacky, and could probably be done + # better. + if trait_name in self._requires: + metadata.setdefault('requires', + []).extend(self._requires[trait_name]) + if trait_name in self._xors: + metadata.setdefault('xor', + []).extend(self._xors[trait_name]) + + trait = ttype(*args, **metadata) + input_spec[trait_name] = trait + + value_keys[trait_name] = input_dict['value-key'] + + self.input_spec = type('{}InputSpec'.format(self._cmd.capitalize()), + (self.input_spec,), + input_spec) + + 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: + trait_name = output_dict['id'] + ttype = traits.File + metadata = {} + args = [Undefined] + + if output_dict.get('description') is not None: + metadata['desc'] = output_dict['description'] + + 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'] + argstr += output_dict.get('command-line-flag-separator', ' ') + argstr += '%s' # May be insufficient for some + metadata['argstr'] = argstr + + trait = ttype(*args, **metadata) + output_spec[trait_name] = trait + + if 'value-key' in output_dict: + 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()), + (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() + + 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 name, valkey in self.value_keys.items(): + if valkey not in output_filename: + continue + repl = self.inputs.trait_get().get(name) + # if input is specified, strip ext + replace value 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) + + 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) + if isdefined(val): + if not os.path.exists(val): + setattr(outputs, key, Undefined) + + return outputs + + @property + def cmdline(self): + """ Prints command line with all specified arguments + """ + args = self._argspec + inputs = {**self.inputs.trait_get(), **self._list_outputs()} + for name, valkey in self.value_keys.items(): + try: + spec = self.inputs.traits()[name] + except KeyError: + spec = self.outputs.traits()[name] + value = inputs[name] + if not isdefined(value): + value = '' + elif spec.argstr: + value = self._format_arg(name, spec, value) + args = args.replace(valkey, value) + # normalize excessive whitespace before returning + return re.sub(r'\s\s+', ' ', self._cmd + ' ' + args).strip() + + 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 diff --git a/nipype/interfaces/base/tests/test_boutiques.py b/nipype/interfaces/base/tests/test_boutiques.py new file mode 100644 index 0000000000..8d996183ea --- /dev/null +++ b/nipype/interfaces/base/tests/test_boutiques.py @@ -0,0 +1,44 @@ +# -*- 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: + +from ...base import BoutiqueInterface +from ....testing import example_data + + +EXAMPLE_SPECS = [ + dict( + spec=example_data('boutiques_fslbet.json'), + inputs=[ + 'additional_surfaces_flag', 'additional_surfaces_t2', + 'approx_skull_flag', 'binary_mask_flag', 'center_of_gravity', + 'debug_flag', 'fractional_intensity', 'head_radius', 'in_file', + 'no_seg_output_flag', 'out_file', 'overlay_flag', + 'reduce_bias_flag', 'residual_optic_cleanup_flag', + 'robust_iters_flag', 'slice_padding_flag', 'thresholding_flag', + 'verbose_flag', 'vg_fractional_intensity', 'vtk_mesh', + 'whole_set_mask_flag' + ], + outputs=[ + 'approx_skull_img_file', 'binary_skull_file', 'inskull_mask_file', + 'inskull_mesh_file', 'inskull_off_file', 'output_file', + 'outskin_mask_file', 'outskin_mesh_file', 'outskin_off_file', + 'outskull_mask_file', 'outskull_mesh_file', 'outskull_off_file', + 'overlay_file', 'skull_mask_file', 'vtk_mesh_file' + ] + ) +] + + +def test_BoutiqueInterface(): + for example in EXAMPLE_SPECS: + interface = BoutiqueInterface(example['spec']) + + # check interface input/output spec generation + inputs = list(interface.input_spec().get()) + outputs = list(interface.output_spec().get()) + assert all([f in inputs for f in example['inputs']]) + assert all([f in outputs for f in example['outputs']]) + + # confirm help works + assert isinstance(interface.help(True), str) diff --git a/nipype/testing/data/boutiques_fslbet.json b/nipype/testing/data/boutiques_fslbet.json new file mode 100644 index 0000000000..e2077b3b6c --- /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": "String", + "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": "output_file", + "name": "Output skullstripped 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_skull_file", + "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_file", + "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": "vtk_mesh_file", + "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_file", + "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": "inskull_mask_file", + "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": "inskull_mesh_file", + "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": "inskull_off_file", + "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": "outskin_mask_file", + "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": "outskin_mesh_file", + "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": "outskin_off_file", + "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": "outskull_mask_file", + "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": "outskull_mesh_file", + "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": "outskull_off_file", + "name": "Output out-skull mesh off file", + "path-template-stripped-extensions": [".nii.gz", ".nii"] + } + ], + "description": "Automated brain extraction tool for FSL" +}