Skip to content

Commit 1110b4f

Browse files
committed
removed input tempvalue stuff and added logic to rerun the interface with various different inputs to try and generate as many outputs as possible
1 parent bb260f8 commit 1110b4f

File tree

1 file changed

+109
-69
lines changed

1 file changed

+109
-69
lines changed

nipype/utils/nipype2boutiques.py

Lines changed: 109 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,19 @@
2525

2626

2727
def generate_boutiques_descriptor(
28-
module, interface_name, ignored_template_inputs, container_image,
29-
container_index, container_type, verbose, ignore_template_numbers, save):
28+
module, interface_name, container_image, container_type, container_index=None,
29+
ignored_template_inputs=(), ignore_template_numbers=False, verbose=False, save=False):
3030
'''
3131
Returns a JSON string containing a JSON Boutiques description of a Nipype interface.
3232
Arguments:
3333
* module: module where the Nipype interface is declared.
3434
* interface_name: name of Nipype interface.
35-
* ignored_template_inputs: a list of input names that should be ignored in the generation of output path templates.
36-
* ignore_template_numbers: True if numbers must be ignored in output path creations.
3735
* container_image: name of the container image where the tool is installed
38-
* container_index: optional index where the image is available
3936
* container_type: type of container image (Docker or Singularity)
37+
* container_index: optional index where the image is available
38+
* ignored_template_inputs: a list of input names that should be ignored in the generation of output path templates.
39+
* ignore_template_numbers: True if numbers must be ignored in output path creations.
40+
* verbose: print information messages
4041
* save: True if you want to save descriptor to a file
4142
'''
4243

@@ -105,23 +106,21 @@ def generate_boutiques_descriptor(
105106
del tool_desc['groups']
106107

107108
# Generates tool outputs
108-
for name, spec in sorted(outputs.traits(transient=None).items()):
109-
output = get_boutiques_output(outputs, name, spec, interface, tool_desc['inputs'],
110-
verbose)
111-
if output['path-template'] != "":
112-
tool_desc['output-files'].append(output)
113-
if verbose:
114-
print("-> Adding output " + output['name'])
115-
elif verbose:
116-
print("xx Skipping output " + output['name'] +
117-
" with no path template.")
118-
if tool_desc['output-files'] == []:
119-
raise Exception("Tool has no output.")
109+
generate_tool_outputs(outputs, interface, tool_desc, verbose, True)
120110

121-
# Removes all temporary values from inputs (otherwise they will
122-
# appear in the JSON output)
123-
for input in tool_desc['inputs']:
124-
del input['tempvalue']
111+
# Generate outputs with various different inputs to try to generate
112+
# as many output values as possible
113+
custom_inputs = generate_custom_inputs(tool_desc['inputs'])
114+
115+
for input_dict in custom_inputs:
116+
interface = getattr(module, interface_name)(**input_dict)
117+
outputs = interface.output_spec()
118+
generate_tool_outputs(outputs, interface, tool_desc, verbose, False)
119+
120+
# Fill in all missing output paths
121+
for output in tool_desc['output-files']:
122+
if output['path-template'] == "":
123+
fill_in_missing_output_path(output, output['name'], tool_desc['inputs'])
125124

126125
# Save descriptor to a file
127126
if save:
@@ -135,6 +134,26 @@ def generate_boutiques_descriptor(
135134
return json.dumps(tool_desc, indent=4, separators=(',', ': '))
136135

137136

137+
def generate_tool_outputs(outputs, interface, tool_desc, verbose, first_run):
138+
for name, spec in sorted(outputs.traits(transient=None).items()):
139+
output = get_boutiques_output(outputs, name, spec, interface, tool_desc['inputs'],
140+
verbose)
141+
# If this is the first time we are generating outputs, add the full output to the descriptor.
142+
# Otherwise, find the existing output and update its path template if it's still undefined.
143+
if first_run:
144+
tool_desc['output-files'].append(output)
145+
else:
146+
for existing_output in tool_desc['output-files']:
147+
if output['id'] == existing_output['id'] and existing_output['path-template'] == "":
148+
existing_output['path-template'] = output['path-template']
149+
break
150+
if verbose:
151+
print("-> Adding output " + output['name'])
152+
153+
if len(tool_desc['output-files']) == 0:
154+
raise Exception("Tool has no output.")
155+
156+
138157
def get_boutiques_input(inputs, interface, input_name, spec,
139158
ignored_template_inputs, verbose,
140159
ignore_template_numbers, handler=None,
@@ -155,7 +174,6 @@ def get_boutiques_input(inputs, interface, input_name, spec,
155174
Assumes that:
156175
* Input names are unique.
157176
"""
158-
spec_info = spec.full_info(inputs, input_name, None)
159177

160178
input = {}
161179

@@ -234,7 +252,6 @@ def get_boutiques_input(inputs, interface, input_name, spec,
234252
elif input['type'] == "Flag":
235253
input['command-line-flag'] = ("--%s" % input_name + " ").strip()
236254

237-
input['tempvalue'] = None
238255
input['description'] = get_description_from_spec(inputs, input_name, spec)
239256
if not (hasattr(spec, "mandatory") and spec.mandatory):
240257
input['optional'] = True
@@ -251,20 +268,7 @@ def get_boutiques_input(inputs, interface, input_name, spec,
251268
if value_choices is not None:
252269
input['value-choices'] = value_choices
253270

254-
# Create unique, temporary value.
255-
temp_value = must_generate_value(input_name, input['type'],
256-
ignored_template_inputs, spec_info, spec,
257-
ignore_template_numbers)
258-
if temp_value:
259-
tempvalue = get_unique_value(input['type'], input_name)
260-
setattr(interface.inputs, input_name, tempvalue)
261-
input['tempvalue'] = tempvalue
262-
if verbose:
263-
print("oo Path-template creation using " + input['id'] + "=" +
264-
str(tempvalue))
265-
266-
# Now that temp values have been generated, set Boolean types to
267-
# Flag (there is no Boolean type in Boutiques)
271+
# Set Boolean types to Flag (there is no Boolean type in Boutiques)
268272
if input['type'] == "Boolean":
269273
input['type'] = "Flag"
270274

@@ -313,38 +317,13 @@ def get_boutiques_output(outputs, name, spec, interface, tool_inputs, verbose=Fa
313317
except TypeError:
314318
output_value = None
315319

316-
# If output value is defined, use its basename
317-
if output_value != "" and isinstance(
318-
output_value,
319-
str): # FIXME: this crashes when there are multiple output values.
320-
# Go find from which input value it was built
321-
for input in tool_inputs:
322-
if not input['tempvalue']:
323-
continue
324-
input_value = input['tempvalue']
325-
if input['type'] == "File":
326-
# Take the base name
327-
input_value = os.path.splitext(
328-
os.path.basename(input_value))[0]
329-
if str(input_value) in output_value:
330-
output_value = os.path.basename(
331-
output_value.replace(input_value,
332-
input['value-key'])
333-
) # FIXME: this only works if output is written in the current directory
334-
output['path-template'] = os.path.basename(output_value)
335-
336-
# If output value is undefined, create a placeholder for the path template
337-
if not output_value:
338-
# Look for an input with the same name and use this as the path template
339-
found = False
340-
for input in tool_inputs:
341-
if input['id'] == name:
342-
output['path-template'] = input['value-key']
343-
found = True
344-
break
345-
# If no input with the same name was found, use the output ID
346-
if not found:
347-
output['path-template'] = output['id']
320+
# If an output value is defined, use its relative path
321+
# Otherwise, put blank string and try to fill it on another iteration
322+
if output_value:
323+
output['path-template'] = os.path.relpath(output_value)
324+
else:
325+
output['path-template'] = ""
326+
348327
return output
349328

350329

@@ -379,6 +358,7 @@ def get_boutiques_groups(input_traits):
379358

380359
return desc_groups
381360

361+
382362
def get_unique_value(type, id):
383363
'''
384364
Returns a unique value of type 'type', for input with id 'id',
@@ -453,3 +433,63 @@ def get_description_from_spec(object, name, spec):
453433
boutiques_description += '.'
454434

455435
return boutiques_description
436+
437+
438+
def fill_in_missing_output_path(output, output_name, tool_inputs):
439+
'''
440+
Creates a path template for outputs that are missing one
441+
This is needed for the descriptor to be valid (path template is required)
442+
'''
443+
# Look for an input with the same name as the output and use its value key
444+
found = False
445+
for input in tool_inputs:
446+
if input['name'] == output_name:
447+
output['path-template'] = input['value-key']
448+
found = True
449+
break
450+
# If no input with the same name was found, use the output ID
451+
if not found:
452+
output['path-template'] = output['id']
453+
return output
454+
455+
456+
457+
def generate_custom_inputs(desc_inputs):
458+
'''
459+
Generates a bunch of custom input dictionaries in order to generate as many outputs as possible
460+
(to get their path templates)
461+
Limitations:
462+
-Does not support String inputs since some interfaces require specific strings
463+
-Does not support File inputs since the file has to actually exist or the interface will fail
464+
-Does not support list inputs yet
465+
'''
466+
custom_input_dicts = []
467+
for desc_input in desc_inputs:
468+
if desc_input.get('list'): # TODO support list inputs
469+
continue
470+
if desc_input['type'] == 'Flag':
471+
custom_input_dicts.append({desc_input['id']: True})
472+
elif desc_input['type'] == 'Number':
473+
custom_input_dicts.append({desc_input['id']: generate_random_number_input(desc_input)})
474+
elif desc_input.get('value-choices'):
475+
for value in desc_input['value-choices']:
476+
custom_input_dicts.append({desc_input['id']: value})
477+
return custom_input_dicts
478+
479+
480+
def generate_random_number_input(desc_input):
481+
'''
482+
Generates a random number input based on the input spec
483+
'''
484+
if not desc_input.get('minimum') and not desc_input.get('maximum'):
485+
return 1
486+
487+
if desc_input.get('integer'):
488+
offset = 1
489+
else:
490+
offset = 0.1
491+
492+
if desc_input.get('minimum'):
493+
return desc_input['minimum'] if desc_input.get('exclusive-minimum') else desc_input['minimum'] + offset
494+
if desc_input.get('maximum'):
495+
return desc_input['maximum'] if desc_input.get('exclusive-maximum') else desc_input['maximum'] - offset

0 commit comments

Comments
 (0)