Executing MIRIAD Tasks: mirexec

The mirexec module makes it convenient to launch tasks from within Python. The simplest invocation looks a lot like what one would run on the command line:

from mirexec import TaskUVFlag
TaskUVFlag (vis='fx64a-3c286-2700', select='ant(26)', flagval='f',
            noquery=True).run ()

If you need them, however, the mirexec module provides more sophisticated facilities allowing you to retrieve the output of a task, run many tasks in parallel, and so on.

Running a MIRIAD task in mirexec requires three steps which, as shown above, can often be condensed into a single line of Python:

  1. Create an instance of a “task” class corresponding to the task you wish to run.
  2. Set the task keywords and options.
  3. Call a method that actually launches the task.

The following code is equivalent to the first example, but breaks down the steps more explicitly:

from miriad import VisData
from mirexec import TaskUVFlag
v = VisData ('fx64a-3c286-2700')
# Create instance:
t = TaskUVFlag ()
# Set some keywords:
t.vis = v
t.select = 'ant(26)'
t.flagval = 'f'
# Set some options:
t.noquery = True
t.hms = False # i.e., do *not* specify the "hms" option
# Launch task.
# Executes: uvflag vis=fx64a-3c286-2700 select=ant(26) flagval=f options=noquery
t.run ()

Creating Task Instances

The TaskUVFlag class shown in the above examples is a subclass of the TaskBase class, which provides a generic structure for invoking MIRIAD tasks. The mirexec module defines such subclasses for many, but far from all, of the tasks provided with MIRIAD. It’s easy to create your own TaskBase subclass for anything you need that’s not provided with miriad-python, however. See below for more information.

The TaskBase class provides functions for setting keyword arguments and actually invoking the task. For the full details, see the detailed API documentation. Subclasses specify the name of the particular task that is run and the keywords and options it accepts.

Task instances can be reused: you can create an object, set arguments, and run it, then change some or all of the arguments and run it again. Among other uses, this makes it easy to apply a task to several datasets:

t = TaskUVAver ()
t.interval = 5
t.line = 'chan,800,101'
t.nocal = True
for v in listManyDatasets ():
   # The set() method returns 'self' for easy chaining of
   # method invocations.
   t.set (vis=v, out=v.vvis ('av')).run ()

Setting Task Parameters

You can set the task parameters in several ways: as a property on the object, as in the example above, as a keyword argument to the object’s constructor, or as a keyword argument to the object’s set() method. The latter two forms are shown in the example below:

from miriad import VisData
from mirexec import TaskUVFlag
v = VisData ('fx64a-3c286-2700')
# This is equivalent to the previous example.
t = TaskUVFlag (vis=v, flagval='f', noquery=True)
t.select = 'ant(26)'
t.run ()
# As is this.
t.set (vis=v, select='ant(26)', flagval='f', noquery=True)
t.run ()

Thus, the most succinct way to execute a task is to write something like:

TaskUVFlag (vis=v, flagval='f', select='pol(yy)').run ()

The names and values of keywords in Python are mapped to command-line arguments with the following rules:

  • Keyword arguments have the same name in Python as they do on the command-line if possible. If the MIRIAD keyword is a Python keyword (e.g., “in”), the keyword is accessible in Python by suffixing it with an underscore (“in_”).

  • In most cases, the textual value of each MIRIAD keyword is the stringification of the Python variable assigned to it. If the Python value is None, the keyword is not supplied on the command-line.

  • However, if the Python variable assigned to the keyword is a non-string iterable, the textual value of the keyword is the stringification of each item in the iterable, joined together with commas. For instance, if you run:

    from mirexec import TaskMfCal
    TaskMfCal (vis=foo, line=['chan', 60, 15]).run ()
    

    the line keyword of mfcal will be chan,60,15.

  • The keyword “options” isn’t used directly. Instead, each possible option to a task is a separate field on the task object that should be set to a bool. The option is supplied if the field is True. There are rare tasks that have an option with the same name as a keyword; in those cases, the keyword is the one controlled by the property on the task object.

There are several functions that will actually execute the task. Each has different uses:

  • run() executes the task and waits for it to finish. The task output is sent to the stdout of the Python program and the task input is set to /dev/null.
  • snarf() executes a task and waits for it to finish. The task’s output to its standard output and standard error streams are returned to the caller.
  • runsilent() executes the task and waits for it to finish. The task output is sent to /dev/null.
  • launch() starts the task but doesn’t wait for it to finish; instead, it returns a MiriadSubprocess instance that allows interaction with the launched subprocess.
  • launchpipe() starts the task but doesn’t wait for it to finish. The output of the task is redirected to pipes that can be read using the MiriadSubprocess instance.
  • launchsilent() starts the task but doesn’t wait for it to finish. The output of the task is redirected to /dev/null.

Defining Your Own Task Classes

In most cases, it’s straightforward to define your own task class. To wrap the task “newtask”, you should write something like:

from mirexec import TaskBase

class TaskNewTask (TaskBase):
    _keywords = ['vis', 'line', 'flux', 'refant']
    _options =  ['nocal', 'nopass', 'mfs']

def demo (vis):
    t = TaskNewTask (vis=vis)
    t.flux = 1.0
    t.nocal = True
    t.run ()

The name of the task executable is inferred from the class name by stripping off the prefix “Task” and lower-casing the rest of the letters. If this heuristic won’t work, you can specify the task name explicitly by setting _name on the class:

from mirexec import TaskBase

class DifferentNames (TaskBase):
    _name = 'newtask'
    _keywords = ['vis', 'line', 'flux', 'refant']
    _options =  ['nocal', 'nopass', 'mfs']

If you’re feeling fancy, here’s a less typing-intensive way of generating arrays of short strings:

from mirexec import TaskBase

class TaskNewTask (TaskBase):
    _keywords = 'vis line flux refant'.split ()
    _options =  'nocal nopass mfs'.split ()

mirexec API Reference

This section presents a detailed API reference for the mirexec module.

Generic Task Class

exception mirexec.TaskFailError(returncode, cmd)

Signals that a task exited indicating failure, though it was able to be launched.

TaskFailError may be a subclass of subprocess.CalledProcessError, if such a class exists. (It was introduced in Python 2.5.) Otherwise, it is a functional equivalent to that class.

Instances have an attribute returncode indicating the exit code of the task. This will be nonzero, since zero indicates success. As far as I know, all MIRIAD tasks exit with a code of 1 unless they die due to a POSIX signal (in which case, the exit code is conventionally the negative of the signal number).

Instances also have an attribute cmd which is a string version of the command line that was executed. The arguments are joined together with spaces, so there’s potential for ambiguity if some of the argument values contain spaces.

Specific Task Classes

We try to keep this list up-to-date, but it may not be complete. If you discover a wrapped task that isn’t documented here, please notify the author. As mentioned above, it’s straightforward to wrap a new task yourself: see Defining Your Own Task Classes.

Setting up Subprocess Environment

Utility Classes