Miriad-Python Manual¶
Welcome to the miriad-python manual. If you’re unfamiliar with how miriad-python works or what it’s for, try starting at the introduction. For more information about the miriad-python project, including installation instructions, issue reporting information, releases, and academic references, please see the miriad-python website.
Contents¶
An Introduction to miriad-python¶
miriad-python is a framework for interacting with the MIRIAD radio interferometry package via the Python programming language. It offers three main groups of tools, used for:
- reading and using MIRIAD data files in Python.
- executing existing MIRIAD tasks.
- writing your own MIRIAD tasks in Python.
You should get started by reading about high-level access to datasets.
Loading and Processing MIRIAD Data¶
miriad-python deals with MIRIAD data on two levels:
- There is a lightweight high-level class,
miriad.Data
, that allows you to easily store references to datasets, check whether they exist, rename them, and so on. - There is a lower-level module,
mirtask
, that lets you open up datasets and access at their contents directly. Doing so successfully requires a familiarity with the details of the MIRIAD data formats, which this documentation does not attempt to provide.
If you’re just getting started with miriad-python, it’s important to
understand the high-level miriad.Data
class but not necessary
to read about the low-level mirtask
module just yet.
High-Level Access to MIRIAD Data: miriad
¶
On the commandline, you refer to datasets by their filenames. The
miriad
module provides two fundamental classes,
VisData
and ImData
, which analogously let you
refer to datasets in a Python program. Instances of these class are
lightweight and provide features to make it easy to perform common
operations on your data.
To instantiate one of these classes, just call the constructor with a filename as an argument:
from miriad import VisData, ImData
vis = VisData ('./fx64c-3c147-1400')
im = ImData ('./residual.rm')
It’s important to understand that these objects are references to datasets, and as such the underlying file doesn’t have to exist when you create the object. Also, creating one of these objects is a very cheap operation.
Both miriad.VisData
and miriad.ImData
are subclasses of a more
generic class, miriad.Data
. Instances of this class have methods
and properties that provide common functionality regarding MIRIAD
datasets. One set of functionality is checking basic properties of the
dataset on disk:
Data.exists
to see if it exists on disk.Data.mtime
to check when it was last modified. This requires that the dataset exists; the variant attributeumtime
returns unconditionally. (Hence the “u” prefix to its name.)Data.realPath()
to get its canonical filename.
You can also perform some basic operations. (From here on out, we will drop the ‘’Data’’ prefix in the names we show. Also, note that you can click on the link associated with all of these function or property names to access the more detailed reference documentation for that item.)
moveTo()
renames a dataset.copyTo()
copies it.delete()
deletes it.apply()
configures a MIRIAD task object (mirexec.TaskBase
) to run on this dataset via themirexec
subsystem. See Executing MIRIAD Tasks: mirexec for more information. See also the verbose variantxapply()
.
You can create more Data
instances with filenames
similar to existing ones:
vvis()
creates a newVisData
instance referencing a similar filename.vim()
creates a newImData
instance referencing a similar filename.
And you can open the dataset with open()
to get
access to its contents. See Low-Level Access to MIRIAD Data for more information.
You may also wish to enable tracing of MIRIAD task execution in
miriad-python by calling basicTrace()
. There are a few more
rarely-used members of Data
not mentioned here that are
documented in the API reference below.
Visibility Datasets¶
The VisData
subclass of Data
has
additional routines specifically useful for UV data:
catTo()
runs uvcat on a dataset to produce a copy of it.averTo()
runs uvaver on a dataset to produce an averaged copy of it.lwcpTo()
creates a “lightweight copy” of a dataset, duplicating its metadata but not the visibilities, which makes certain common operations much faster.readLowlevel()
opens the dataset directly for lowlevel access to the visibility data.
Besides these routines, the VisData
subclass
implements several generic methods specified in Data
,
so you should always create a VisData
instance when
you know that you’re referring to a visibility dataset.
Image Datasets¶
The ImData
subclass of Data
is used
for referencing image data. It currently does not have any routines
specifically applicable to image data, but it implements
several of the Data
methods correctly, so you should
always create a ImData
instance when you know that
you’re referring to an image dataset.
miriad
API Reference¶
This section presents a detailed API reference for the miriad
module.
Dataset Classes¶
Tracing Task Execution¶
The miriad
module also provides infrastructure for tracing task
execution and operations on datasets.
-
miriad.
launchTrace
¶ Traces the execution of commands.
Should be a callable or
None
. Will be called bytrace()
, which is invoked every time a MIRIAD task is executed viamirexec
or a dataset is renamed, copied, or deleted. launchTrace should take one argument, which will be a list of strings representing the commandline that is being invoked. If none,trace()
has no effect.The function
basicTrace()
sets launchTrace to a simple default.
Low-Level Access to MIRIAD Data¶
Is awesome. But the documentation will be written a bit later.
MIRIAD Data Utilities: mirtask.util
¶
The module mirtask.util
contains miscellaneous utilities for
working with MIRIAD data. It is located in the mirtask
package because
some of these utilities make calls into the MIRIAD subroutine library,
unlike the general, pure-Python tools in the miriad
module.
Utilities for Writing Tasks¶
Linetypes¶
Baselines¶
Polarizations¶
Julian Dates¶
Optimizers¶
Coordinate Manipulations¶
Fast-Fourier-Transform Imaging¶
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:
- Create an instance of a “task” class corresponding to the task you wish to run.
- Set the task keywords and options.
- 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 bechan,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 isTrue
. 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 aMiriadSubprocess
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 theMiriadSubprocess
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 ofsubprocess.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¶
Writing Your Own MIRIAD Tasks¶
There are several things that you need to do to write your own MIRIAD tasks that blend in with the standard MIRIAD tasks.
MIRIAD-Style Argument Handling: mirtask.keys
¶
Like other UNIX programs, MIRIAD tasks accept input parameters from the user via command-line arguments. The way in which MIRIAD tasks do this, however, is different than the usual UNIX way.
Each task defines a number of “keywords”. The MIRIAD keyword-handling subsystem can be used to obtain zero or more values of each keyword from the command-line arguments. The values of a given keyword do not necessarily all have to be of the same type. The user specifies the values of these keywords on the command-line with an assignment syntax:
invert vis=3c286.uv map=3c286.mp imsize=824,724 slop=0.5 select='ant(1,3),pol(xx)'
Here, the vis keyword has a single string value (interpretable specifically as a filename), the imsize keyword has two integer values, and the select keyword has two string values. (Note that the keyword-handling routines process parentheses in string-valued keywords and do not consider the keyword to have the values “ant(1”, “3)”, and “pol(xx)”.)
Boolean-valued keywords are called “options” and are implemented by an options pseudo-keyword:
invert options=mfs,double
Here the options mfs and double have True
values while
all other options are False
.
Handling keywords in miriad-python¶
Tasks in MIRIAD obtain the values of keywords in a procedural way. In miriad-python, keywords are generally specified declaratively and their values are obtained automatically, although there is support for the more general procedural approach.
To parse arguments in a miriad-python task:
- Instantiate a
KeySpec
object. - Specify the keywords your task accepts.
- Use the
KeySpec.process()
method to obtain a data structure populated with the settings for all keywords based on your specification.
Here’s a simple example:
from mirtask import keys
ks = keys.KeySpec ()
ks.keyword ('param', 'd', 0.25)
ks.keyword ('mode', 'a', 'deconvolve')
ks.option ('verbose', 'noop')
opts = ks.process ()
if opts.param < 0:
die ('"param" must be positive, not %f' % opts.param)
if opts.mode not in ('deconvolve', 'stirfry'):
die ('Unknown operation mode "%s"' % opts.mode)
if opts.verbose:
print "Being verbose starting now!"
The methods on KeySpec
to define keywords are:
keyword()
defines a keyword that takes a single, typed value, with a default if the keyword is left unspecified.mkeyword()
defines a keyword that takes multiple values of the same type.keymatch()
defines a keyword that takes on one or more enumerated values with minimum-match string expansion.option()
defines one or more options.custom()
defines a keyword that is handled in a custom way by the caller.
The object returned by process()
has an attribute
for each keyword defined using the above functions.
- For keywords defined with
keyword()
, the attribute is equal to the user’s provided value or the default. - For keywords defined with
mkeyword()
, the attribute is a list of the values provided by the user, with the list being empty if the user provided none. - For keywords defined with
keymatch()
, the attribute is a list of the values provided by the user expanded out to their full values if the user abbreviated any. As above, the list may be empty. - For options defined with
option()
, the attribute is eitherTrue
orFalse
. - For keywords defined with
custom()
, the attribute is whatever value was returned by a user-specified routine.
If the user-specified values do not match the expectations defined by
the specification (e.g., a keyword that should be integer-typed is
passed the value “abcd”) then a MiriadError
is raised
in process()
.
Keyword Types¶
Keyword types in MIRIAD and miriad-python are identified by single letters. The following types are available:
Character | Description |
---|---|
i | (The character is the lower-case letter i.) An integer value. |
d | A floating-point (“double”) value. |
a | A character string value. |
f | A filename value. These are essentially treated like character strings, but there are special hooks in the MIRIAD processing code to expand out shell glob patterns into multiple values. |
t | A time or angle value. These are parsed according to one of several formats, described below. The output is always a floating-point number but its meaning depends on the parse format. |
Keyword Formats¶
Keywords describing a time or angle can be parsed according to one of several formats. You must specify one of these formats when declaring the keyword.
Name | Description |
---|---|
dms | The argument is an angle measured in degrees, written as “dd:mm:ss.s” or “dd.ddd”. The output is the angle in radians. |
hms | The argument is an angle/time measured in hours, written as “hh:mm:ss.s” or “hh.hhh”. The output is the angle/time in radians. |
dtime | The argument is a day fraction, i.e. the portion of a day that has elapsed at that local time. The user can provide it in the format “hh:mm:ss.s” or “hh.hhhh”. The output is the day fraction, a number in the range [0, 1]. |
atime | The argument is an absolute time, specified as either “yyMMMdd.ddd” or “yyMMMdd:hh:mm:ss.s”, or as an epoch, “bYYYY” or “jYYYY”. The output is in Julian days. |
time | Either an absolute time or a day fraction. The output is either a Julian day value or a day fraction. |
Integration with the UVDAT Subsystem¶
The keyword subsystem can integrate with MIRIAD’s UVDAT subsystem. This integration is necessary because the UVDAT subsystem uses the MIRIAD keyword-handling routines to obtain settings for UV data selection, whether calibration should be applied, and so on.
If your task does not use the UVDAT subsystem, you need take no action.
If your task does use the UVDAT subsystem, you must call
KeySpec.uvdat()
while defining your keywords. When doing so, you
specify processing options that will be passed to the UVDAT
subsystem. See the documentation of uvdat()
for more
information.
mirtask.keys
API Reference¶
This section presents a detailed API reference for the
mirtask.keys
module.
The UVDAT Subsystem for Reading UV Data: mirtask.uvdat
¶
Foo.
mirtask.uvdat
API Reference¶
This section presents a detailed API reference for the
mirtask.uvdat
module.