Skip to content

Commit f0a11b8

Browse files
committed
added methods to generate command line flag and generate a boutiques output from a Nipype input spec, code style fixes, some refactoring and improvements
1 parent 54259d8 commit f0a11b8

File tree

1 file changed

+103
-66
lines changed

1 file changed

+103
-66
lines changed

nipype/utils/nipype2boutiques.py

Lines changed: 103 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
# Limitations:
1010
# * Optional outputs, i.e. outputs that not always produced, may not be detected. They will, however, still be listed
1111
# with a placeholder for the path template (either a value key or the output ID) that should be verified and corrected.
12+
# * Still need to add some fields to the descriptor manually, e.g. url, descriptor-url, path-template-stripped-extensions, etc.
1213

1314
import os
1415
import sys
@@ -77,28 +78,34 @@ def generate_boutiques_descriptor(
7778

7879
# Generates tool inputs
7980
for name, spec in sorted(interface.inputs.traits(transient=None).items()):
80-
inp = get_boutiques_input(inputs, interface, name, spec, verbose, ignore_inputs=ignore_inputs)
81-
# Handle compound inputs (inputs that can be of multiple types and are mutually exclusive)
82-
if inp is None:
81+
# Skip ignored inputs
82+
if ignore_inputs is not None and name in ignore_inputs:
8383
continue
84-
if isinstance(inp, list):
85-
mutex_group_members = []
86-
tool_desc['command-line'] += inp[0]['value-key'] + " "
87-
for i in inp:
88-
tool_desc['inputs'].append(i)
89-
mutex_group_members.append(i['id'])
90-
if verbose:
91-
print("-> Adding input " + i['name'])
92-
# Put inputs into a mutually exclusive group
93-
tool_desc['groups'].append({'id': inp[0]['id'] + "_group",
94-
'name': inp[0]['name'] + " group",
95-
'members': mutex_group_members,
96-
'mutually-exclusive': True})
84+
# If spec has a name source, this means it actually represents an output, so create a
85+
# Boutiques output from it
86+
elif spec.name_source and spec.name_template:
87+
tool_desc['output-files'].append(get_boutiques_output_from_inp(inputs, spec, name))
9788
else:
98-
tool_desc['inputs'].append(inp)
99-
tool_desc['command-line'] += inp['value-key'] + " "
100-
if verbose:
101-
print("-> Adding input " + inp['name'])
89+
inp = get_boutiques_input(inputs, interface, name, spec, verbose, ignore_inputs=ignore_inputs)
90+
# Handle compound inputs (inputs that can be of multiple types and are mutually exclusive)
91+
if isinstance(inp, list):
92+
mutex_group_members = []
93+
tool_desc['command-line'] += inp[0]['value-key'] + " "
94+
for i in inp:
95+
tool_desc['inputs'].append(i)
96+
mutex_group_members.append(i['id'])
97+
if verbose:
98+
print("-> Adding input " + i['name'])
99+
# Put inputs into a mutually exclusive group
100+
tool_desc['groups'].append({'id': inp[0]['id'] + "_group",
101+
'name': inp[0]['name'] + " group",
102+
'members': mutex_group_members,
103+
'mutually-exclusive': True})
104+
else:
105+
tool_desc['inputs'].append(inp)
106+
tool_desc['command-line'] += inp['value-key'] + " "
107+
if verbose:
108+
print("-> Adding input " + inp['name'])
102109

103110
# Generates input groups
104111
tool_desc['groups'] += get_boutiques_groups(interface.inputs.traits(transient=None).items())
@@ -148,7 +155,7 @@ def generate_boutiques_descriptor(
148155

149156
# Save descriptor to a file
150157
if save:
151-
path = save_path if save_path is not None else os.path.join(os.getcwd(), interface_name + '.json')
158+
path = save_path or os.path.join(os.getcwd(), interface_name + '.json')
152159
with open(path, 'w') as outfile:
153160
json.dump(tool_desc, outfile, indent=4, separators=(',', ': '))
154161
if verbose:
@@ -200,15 +207,9 @@ def get_boutiques_input(inputs, interface, input_name, spec, verbose, handler=No
200207
Assumes that:
201208
* Input names are unique.
202209
"""
203-
204-
# If spec has a name source, means it's an output, so skip it here.
205-
# Also skip any ignored inputs
206-
if spec.name_source or ignore_inputs is not None and input_name in ignore_inputs:
207-
return None
208-
209210
inp = {}
210211

211-
if input_number is not None and input_number != 0: # No need to append a number to the first of a list of compound inputs
212+
if input_number: # No need to append a number to the first of a list of compound inputs
212213
inp['id'] = input_name + "_" + str(input_number + 1)
213214
else:
214215
inp['id'] = input_name
@@ -302,20 +303,18 @@ def get_boutiques_input(inputs, interface, input_name, spec, verbose, handler=No
302303
if handler_type == "InputMultiObject":
303304
inp['type'] = "File"
304305
inp['list'] = True
306+
if spec.sep:
307+
inp['list-separator'] = spec.sep
305308

306309
inp['value-key'] = "[" + input_name.upper(
307310
) + "]" # assumes that input names are unique
308311

309-
# Add the command line flag specified by argstr
310-
# If no argstr is provided and input type is Flag, create a flag from the name
311-
if spec.argstr:
312-
if "=" in spec.argstr:
313-
inp['command-line-flag'] = spec.argstr.split("=")[0].strip()
314-
inp['command-line-flag-separator'] = "="
315-
elif spec.argstr.split("%")[0]:
316-
inp['command-line-flag'] = spec.argstr.split("%")[0].strip()
317-
elif inp['type'] == "Flag":
318-
inp['command-line-flag'] = ("--%s" % input_name + " ").strip()
312+
flag, flag_sep = get_command_line_flag(spec, inp['type'] == "Flag", input_name)
313+
314+
if flag is not None:
315+
inp['command-line-flag'] = flag
316+
if flag_sep is not None:
317+
inp['command-line-flag-separator'] = flag_sep
319318

320319
inp['description'] = get_description_from_spec(inputs, input_name, spec)
321320
if not (hasattr(spec, "mandatory") and spec.mandatory):
@@ -384,42 +383,32 @@ def get_boutiques_output(outputs, name, spec, interface, tool_inputs):
384383
output_value = None
385384
except AttributeError:
386385
output_value = None
386+
except KeyError:
387+
output_value = None
387388

388389
# Handle multi-outputs
389-
if isinstance(output_value, list):
390+
if isinstance(output_value, list) or type(spec.handler).__name__ == "OutputMultiObject":
390391
output['list'] = True
391-
# Check if all extensions are the same
392-
extensions = []
393-
for val in output_value:
394-
extensions.append(os.path.splitext(val)[1])
395-
# If extensions all the same, set path template as wildcard + extension
396-
# Otherwise just use a wildcard
397-
if len(set(extensions)) == 1:
398-
output['path-template'] = "*" + extensions[0]
399-
else:
400-
output['path-template'] = "*"
401-
return output
392+
if output_value:
393+
# Check if all extensions are the same
394+
extensions = []
395+
for val in output_value:
396+
extensions.append(os.path.splitext(val)[1])
397+
# If extensions all the same, set path template as wildcard + extension
398+
# Otherwise just use a wildcard
399+
if len(set(extensions)) == 1:
400+
output['path-template'] = "*" + extensions[0]
401+
else:
402+
output['path-template'] = "*"
403+
return output
402404

403405
# If an output value is defined, use its relative path, if one exists.
404-
# If no relative path, look for an input with the same name containing a name source
405-
# and name template. Otherwise, put blank string as placeholder and try to fill it on
406+
# Otherwise, put blank string as placeholder and try to fill it on
406407
# another iteration.
407-
output['path-template'] = ""
408-
409408
if output_value:
410409
output['path-template'] = os.path.relpath(output_value)
411410
else:
412-
for inp_name, inp_spec in sorted(interface.inputs.traits(transient=None).items()):
413-
if inp_name == name and inp_spec.name_source and inp_spec.name_template:
414-
if isinstance(inp_spec.name_source, list):
415-
source = inp_spec.name_source[0]
416-
else:
417-
source = inp_spec.name_source
418-
output['path-template'] = inp_spec.name_template.replace("%s", "[" + source.upper() + "]")
419-
output['value-key'] = "[" + name.upper() + "]"
420-
if inp_spec.argstr and inp_spec.argstr.split("%")[0]:
421-
output['command-line-flag'] = inp_spec.argstr.split("%")[0].strip()
422-
break
411+
output['path-template'] = ""
423412

424413
return output
425414

@@ -535,4 +524,52 @@ def reorder_cmd_line_args(cmd_line, interface, ignore_inputs=None):
535524
continue
536525
positional_args.append(item[1])
537526

538-
return interface_name + " " + " ".join(positional_args) + " " + ((last_arg + " ") if last_arg else "") + " ".join(non_positional_args)
527+
return interface_name + " " +\
528+
((" ".join(positional_args) + " ") if len(positional_args) > 0 else "") +\
529+
((last_arg + " ") if last_arg else "") +\
530+
" ".join(non_positional_args)
531+
532+
533+
def get_command_line_flag(input_spec, is_flag_type=False, input_name=None):
534+
'''
535+
Generates the command line flag for a given input
536+
'''
537+
flag, flag_sep = None, None
538+
if input_spec.argstr:
539+
if "=" in input_spec.argstr:
540+
flag = input_spec.argstr.split("=")[0].strip()
541+
flag_sep = "="
542+
elif input_spec.argstr.split("%")[0]:
543+
flag = input_spec.argstr.split("%")[0].strip()
544+
elif is_flag_type:
545+
flag = ("--%s" % input_name + " ").strip()
546+
return flag, flag_sep
547+
548+
549+
def get_boutiques_output_from_inp(inputs, inp_spec, inp_name):
550+
'''
551+
Takes a Nipype input representing an output file and generates a Boutiques output for it
552+
'''
553+
output = {}
554+
output['name'] = inp_name.replace('_', ' ').capitalize()
555+
output['id'] = inp_name
556+
output['optional'] = True
557+
output['description'] = get_description_from_spec(inputs, inp_name, inp_spec)
558+
if not (hasattr(inp_spec, "mandatory") and inp_spec.mandatory):
559+
output['optional'] = True
560+
else:
561+
output['optional'] = False
562+
if inp_spec.usedefault:
563+
output['default-value'] = inp_spec.default_value()[1]
564+
if isinstance(inp_spec.name_source, list):
565+
source = inp_spec.name_source[0]
566+
else:
567+
source = inp_spec.name_source
568+
output['path-template'] = inp_spec.name_template.replace("%s", "[" + source.upper() + "]")
569+
output['value-key'] = "[" + inp_name.upper() + "]"
570+
flag, flag_sep = get_command_line_flag(inp_spec)
571+
if flag is not None:
572+
output['command-line-flag'] = flag
573+
if flag_sep is not None:
574+
output['command-line-flag-separator'] = flag_sep
575+
return output

0 commit comments

Comments
 (0)