25
25
26
26
27
27
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 ):
30
30
'''
31
31
Returns a JSON string containing a JSON Boutiques description of a Nipype interface.
32
32
Arguments:
33
33
* module: module where the Nipype interface is declared.
34
34
* 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.
37
35
* container_image: name of the container image where the tool is installed
38
- * container_index: optional index where the image is available
39
36
* 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
40
41
* save: True if you want to save descriptor to a file
41
42
'''
42
43
@@ -105,23 +106,21 @@ def generate_boutiques_descriptor(
105
106
del tool_desc ['groups' ]
106
107
107
108
# 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 )
120
110
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' ])
125
124
126
125
# Save descriptor to a file
127
126
if save :
@@ -135,6 +134,26 @@ def generate_boutiques_descriptor(
135
134
return json .dumps (tool_desc , indent = 4 , separators = (',' , ': ' ))
136
135
137
136
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
+
138
157
def get_boutiques_input (inputs , interface , input_name , spec ,
139
158
ignored_template_inputs , verbose ,
140
159
ignore_template_numbers , handler = None ,
@@ -155,7 +174,6 @@ def get_boutiques_input(inputs, interface, input_name, spec,
155
174
Assumes that:
156
175
* Input names are unique.
157
176
"""
158
- spec_info = spec .full_info (inputs , input_name , None )
159
177
160
178
input = {}
161
179
@@ -234,7 +252,6 @@ def get_boutiques_input(inputs, interface, input_name, spec,
234
252
elif input ['type' ] == "Flag" :
235
253
input ['command-line-flag' ] = ("--%s" % input_name + " " ).strip ()
236
254
237
- input ['tempvalue' ] = None
238
255
input ['description' ] = get_description_from_spec (inputs , input_name , spec )
239
256
if not (hasattr (spec , "mandatory" ) and spec .mandatory ):
240
257
input ['optional' ] = True
@@ -251,20 +268,7 @@ def get_boutiques_input(inputs, interface, input_name, spec,
251
268
if value_choices is not None :
252
269
input ['value-choices' ] = value_choices
253
270
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)
268
272
if input ['type' ] == "Boolean" :
269
273
input ['type' ] = "Flag"
270
274
@@ -313,38 +317,13 @@ def get_boutiques_output(outputs, name, spec, interface, tool_inputs, verbose=Fa
313
317
except TypeError :
314
318
output_value = None
315
319
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
+
348
327
return output
349
328
350
329
@@ -379,6 +358,7 @@ def get_boutiques_groups(input_traits):
379
358
380
359
return desc_groups
381
360
361
+
382
362
def get_unique_value (type , id ):
383
363
'''
384
364
Returns a unique value of type 'type', for input with id 'id',
@@ -453,3 +433,63 @@ def get_description_from_spec(object, name, spec):
453
433
boutiques_description += '.'
454
434
455
435
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