|
| 1 | +# This tool exports a Nipype interface in the Boutiques (https://github.com/boutiques) JSON format. |
| 2 | +# Boutiques tools can be imported in CBRAIN (https://github.com/aces/cbrain) among other platforms. |
| 3 | +# |
| 4 | +# Limitations: |
| 5 | +# * optional inputs are ignored because they are not supported in Boutiques. |
| 6 | +# * inputs with cardinality "Multiple" (InputMultiPath in Nipype) are not supported. Same limitation for the outputs. |
| 7 | +# * value-templates are wrong when output files are not created in the execution directory (e.g. when a sub-directory is created). |
| 8 | + |
| 9 | +import os |
| 10 | +import argparse |
| 11 | +import inspect |
| 12 | +import sys |
| 13 | +import json |
| 14 | +import tempfile |
| 15 | + |
| 16 | +from nipype.interfaces.base import Interface |
| 17 | + |
| 18 | +def create_tempfile(): |
| 19 | + fileTemp = tempfile.NamedTemporaryFile(delete = False) |
| 20 | + fileTemp.write("hello") |
| 21 | + fileTemp.close() |
| 22 | + return fileTemp.name |
| 23 | + |
| 24 | +def print_inputs(tool_name, module=None, function=None): |
| 25 | + interface = None |
| 26 | + if module and function: |
| 27 | + __import__(module) |
| 28 | + interface = getattr(sys.modules[module],function)() |
| 29 | + |
| 30 | + inputs = interface.input_spec() |
| 31 | + outputs = interface.output_spec() |
| 32 | + |
| 33 | + command_line = "nipype_cmd "+str(module)+" "+tool_name+" " |
| 34 | + tool_desc = {} |
| 35 | + tool_desc['name'] = tool_name |
| 36 | + tool_desc['description'] = "Tool description goes here" |
| 37 | + |
| 38 | + tool_inputs = [] |
| 39 | + input_counter = 0 |
| 40 | + tool_outputs = [] |
| 41 | + |
| 42 | + for name, spec in sorted(interface.inputs.traits(transient=None).items()): |
| 43 | + |
| 44 | + #FIXME: optional inputs are not supported yet |
| 45 | + if not ( hasattr(spec, "mandatory") and spec.mandatory ): |
| 46 | + continue |
| 47 | + |
| 48 | + input = {} |
| 49 | + input['name'] = name |
| 50 | + type = spec.full_info(inputs, name, None) |
| 51 | + if not "an existing file name" in type: |
| 52 | + type = "String" |
| 53 | + else: |
| 54 | + type = "File" |
| 55 | + input['type'] = type |
| 56 | + input['description'] = "\n".join(interface._get_trait_desc(inputs, name, spec))[len(name)+2:].replace("\n\t\t",". ") |
| 57 | + command_line_key = "["+str(input_counter)+"_"+name.upper()+"]" |
| 58 | + input_counter += 1 |
| 59 | + input['command-line-key'] = command_line_key |
| 60 | + input['cardinality'] = "Single" |
| 61 | + tool_inputs.append(input) |
| 62 | + |
| 63 | + if not ( hasattr(spec, "mandatory") and spec.mandatory ): |
| 64 | + command_line+= "--%s"%name+" " # unreachable code as long as optional inputs are not supported |
| 65 | + |
| 66 | + command_line+= command_line_key+" " |
| 67 | + |
| 68 | + # add value to input so that output names can be generated |
| 69 | + tempfile_name = create_tempfile() |
| 70 | + input['tempfile_name'] = tempfile_name |
| 71 | + if type == "File": |
| 72 | + setattr(interface.inputs,name,os.path.abspath(tempfile_name)) |
| 73 | + |
| 74 | + for name,spec in sorted(outputs.traits(transient=None).items()): |
| 75 | + |
| 76 | + output = {} |
| 77 | + output['name'] = name |
| 78 | + output['type'] = "File" |
| 79 | + output['description'] = "No description provided" |
| 80 | + output['command-line-key'] = "" |
| 81 | + output['value-template'] = "" |
| 82 | + output_value = interface._list_outputs()[name] |
| 83 | + if output_value != "" and isinstance(output_value,str): # FIXME: this crashes when there are multiple output values. |
| 84 | + # go find from which input file it was built |
| 85 | + for input in tool_inputs: |
| 86 | + base_file_name = os.path.splitext(os.path.basename(input['tempfile_name']))[0] |
| 87 | + if base_file_name in output_value: |
| 88 | + output_value = os.path.basename(output_value.replace(base_file_name,input['command-line-key'])) # FIXME: this only works if output is written in the current directory |
| 89 | + output['value-template'] = os.path.basename(output_value) |
| 90 | + |
| 91 | + output['cardinality'] = "Single" |
| 92 | + if output['value-template'] != "": # outputs with no templates would certainly crash. |
| 93 | + tool_outputs.append(output) |
| 94 | + |
| 95 | + # remove all temporary file names from inputs |
| 96 | + for input in tool_inputs: |
| 97 | + del input['tempfile_name'] |
| 98 | + |
| 99 | + tool_desc['inputs'] = tool_inputs |
| 100 | + tool_desc['outputs'] = tool_outputs |
| 101 | + tool_desc['command-line'] = command_line |
| 102 | + tool_desc['docker-image'] = 'docker.io/robdimsdale/nipype' |
| 103 | + tool_desc['docker-index'] = 'http://index.docker.io' |
| 104 | + tool_desc['schema-version'] = '0.1' |
| 105 | + print json.dumps(tool_desc, indent=4, separators=(',', ': ')) |
| 106 | + |
| 107 | +def main(argv): |
| 108 | + |
| 109 | + parser = argparse.ArgumentParser(description='Nipype Boutiques exporter', prog=argv[0]) |
| 110 | + parser.add_argument("module", type=str, help="Module name") |
| 111 | + parser.add_argument("interface", type=str, help="Interface name") |
| 112 | + parsed = parser.parse_args(args=argv[1:3]) |
| 113 | + |
| 114 | + _, prog = os.path.split(argv[0]) |
| 115 | + interface_parser = argparse.ArgumentParser(description="Run %s"%parsed.interface, prog=" ".join([prog] + argv[1:3])) |
| 116 | + print_inputs(argv[2],parsed.module, parsed.interface) |
0 commit comments