21 This module can be used to execute the MIRTK command-line tools from an image 22 processing pipeline script. Its main usage is identical to the actual subprocess 23 module which is used internally by this mirtk.subprocess module. 26 from __future__
import absolute_import, unicode_literals
34 from collections
import OrderedDict
41 __dir__ = os.path.dirname(os.path.realpath(__file__))
44 libexec_dir = os.path.realpath(os.path.join(__dir__,
'../../tools'))
46 if sys.platform.startswith(
'win'): libexec_ext = [
'.exe',
'.cmd',
'.bat']
47 else: libexec_ext = [
'']
61 def remove_kwargs(kwargs, keys):
62 if not isinstance(keys, (tuple, list)):
70 def path(argv, quiet=False):
71 """Get full path of MIRTK command executable.""" 72 if isinstance(argv, list): command = argv[0]
74 if command ==
'calculate': command =
'calculate-element-wise' 75 elif command ==
'convert-dof2csv': command =
'convert-dof' 76 elif command ==
'convert-dof2velo': command =
'convert-dof' 77 elif command ==
'concatenate-dofs': command =
'compose-dofs' 78 elif command ==
'concatenate-images': command =
'combine-images' 79 elif command ==
'remesh': command =
'remesh-surface' 81 for ext
in libexec_ext:
82 p = os.path.join(libexec_dir, config, command + ext)
87 for ext
in libexec_ext:
88 p = os.path.join(libexec_dir, command + ext)
94 sys.stderr.write(
'Error: Missing execuable for command: ' + command)
95 elif not os.access(fpath, os.X_OK):
96 sys.stderr.write(
'Error: Insufficient permissions to execute command: ' + fpath)
102 """Given an argument, possibly a list nested to any level, return a flat list.""" 103 if isinstance(arg, (tuple, list)):
106 lis.extend(flatten(item))
114 """Return quoted command arguments.""" 119 arg =
'"' + arg +
'"' 125 def _call(argv, verbose=0, execfunc=subprocess.call):
126 """Execute MIRTK command.""" 127 if not isinstance(argv, list):
128 argv = shlex.split(argv)
129 argv = [str(arg)
for arg
in argv]
130 if argv[0] ==
'convert-dof2csv':
131 new_argv = [
'convert-dof']
132 out_fmt =
'star_ccm_table' 134 if arg ==
'-pos': out_fmt =
'star_ccm_table_xyz' 135 else: new_argv.append(arg)
136 new_argv.extend([
'-output-format', out_fmt])
138 argv[0] = path(argv[0])
139 if not argv[0]:
return 1
141 sys.stdout.write(
"> ")
142 sys.stdout.write(
" ".join(quote(argv)))
143 sys.stdout.write(
"\n\n")
145 return execfunc(argv)
149 def call(argv, verbose=0):
150 """Execute MIRTK command and return exit code.""" 151 return _call(argv, verbose=verbose, execfunc=subprocess.call)
155 def check_call(argv, verbose=0):
156 """Execute MIRTK command and throw exception on error.""" 157 _call(argv, verbose=verbose, execfunc=subprocess.check_call)
161 def check_output(argv, verbose=0, code='utf-8'):
162 """Execute MIRTK command and return its output.""" 163 output = _call(argv, verbose=verbose, execfunc=subprocess.check_output)
164 if code: output = output.decode(code)
169 def run(cmd, *args, **kwargs):
170 """Execute MIRTK command and throw exception or exit on error. 172 This function calls the specified MIRTK command with the given positional 173 and option arguments (keyword arguments prepended by single dash, '-'). 174 The 'onexit' and 'onexit' keyword arguments define the return value and 175 the error handling when the subprocess returns a non-zero exit code. 176 By default, this function behaves identical to check_call, i.e., throws 177 and exception when the command failed and does not return a value otherwise. 183 Defines action to take when command subprocess finished. 184 - 'none': Always return None, even when process failed when onerror='return' 185 - 'output': Return standard output of command instead of printing it 186 - 'returncode': Return exit code of command. When onerror is not 'return', 187 this always returns 0 indicating successful execution of the command. 189 - 'return': Return value specified by 'onexit' even when command failed. 190 - 'throw': Throw CalledProcessError when command failed. 191 - 'exit': Exit this process (sys.exit) with exit command of failed command. 197 This convenience wrapper for check_call throws a subprocess.CalledProcessError 198 when the command returned a non-zero exit code when exit_on_error=False. 199 If exit_on_error=True, a the command arguments, the exit code, and the 200 Python stack trace are printed to sys.stderr and this process terminated 201 using sys.exit with the return code of the executed command. 205 if "exit_on_error" in kwargs
and "onerror" in kwargs:
206 raise ValueError(
"Deprecated keyword argument 'exit_on_error' given together with new 'onerror' argument!")
207 showcmd = kwargs.get(
"showcmd", default[
"showcmd"])
208 workdir = kwargs.get(
"workdir", default[
"workdir"])
209 threads = kwargs.get(
"threads", default[
"threads"])
210 if "exit_on_error" in kwargs:
211 onerror = kwargs.get(
"onerror",
"exit" if kwargs.get(
"exit_on_error",
False)
else "throw")
213 onerror = kwargs.get(
"onerror", default[
"onerror"])
217 onerror = onerror.lower()
218 if onerror
not in (
'return',
'throw',
'exit'):
219 raise ValueError(
"Invalid 'onerror={}' argument! Must be 'return', 'throw', or 'exit'.".format(onerror))
220 onexit = kwargs.get(
"onexit", default[
"onexit"])
224 onexit = onexit.lower()
225 if onexit
in (
'code',
'exit_code',
'return_code'):
226 onexit =
'returncode' 227 if onexit
in (
'stdout',
'return_stdout',
'return_output'):
229 if onexit
not in (
'none',
'output',
'returncode'):
230 raise ValueError(
"Invalid 'onexit={}' argument! Must be 'none', 'returncode', 'output' or alternatives thereof.".format(onexit))
231 remove_kwargs(kwargs, [
"showcmd",
"workdir",
"threads",
"onexit",
"onerror",
"exit_on_error"])
235 argv.extend(kwargs.get(
"args", []))
236 remove_kwargs(kwargs,
"args")
238 opts = kwargs.get(
"opts", {})
239 remove_kwargs(kwargs,
"opts")
240 if isinstance(opts, list):
242 if isinstance(item, (tuple, list)):
244 arg = flatten(item[1:])
248 if not opt.startswith(
'-'):
252 argv.extend(flatten(arg))
255 for opt, arg
in opts.items():
256 if not opt.startswith(
'-'):
260 argv.extend(flatten(arg))
262 for opt, arg
in kwargs.items():
263 opt = opt.replace(
'_',
'-')
264 if not opt.startswith(
'-'):
268 argv.extend(flatten(arg))
270 if threads > 0
and not 'threads' in opts:
271 argv.extend([
'-threads', str(threads)])
274 prevdir = os.getcwd()
278 if onexit ==
'output':
279 retval = check_output(argv, verbose=1
if showcmd
else 0)
281 check_call(argv, verbose=1
if showcmd
else 0)
282 if onexit ==
'returncode':
284 except subprocess.CalledProcessError
as e:
285 if onerror ==
'exit':
287 sys.stderr.write(
"\n")
288 sys.stderr.write(
"Command: ")
289 sys.stderr.write(
" ".join(quote(argv)))
290 sys.stderr.write(
"\n")
291 sys.stderr.write(
"\nError: Command returned non-zero exit status {}\n".format(e.returncode))
292 sys.stderr.write(
"\nTraceback (most recent call last):\n")
293 traceback.print_stack()
294 sys.exit(e.returncode)
295 elif onerror ==
'throw':
297 if onexit ==
'returncode':
298 retval = e.returncode