Subsections

 
3 Defining Tasks in PyRAF

As with the IRAF CL, one can define tasks around IRAF executables, CL scripts, or ``foreign'' tasks. But the big advantage of PyRAF is that it allows one to write a Python program (that need not use IRAF at all) that can be treated like an IRAF task with the familiar CL command-line syntax and parameter editing facilities. In this way it is possible to integrate IRAF and Python programs into the same user environment and take advantage of the strengths of both.

3.1 Python Tasks

This section describes how to define PyRAF tasks that are implemented as Python scripts. We have already described Python scripts that call IRAF tasks. The scripts described in this section may or may not call IRAF tasks; the relevant feature is that these are PyRAF tasks. From the user's perspective, these look the same as any other PyRAF/IRAF task, i.e. they will typically have parameters, and they can be run in so-called ``command mode,'' without using parentheses or enclosing strings in quotes.

Note that Python tasks can only be used in PyRAF, not in the IRAF CL, because the CL cannot run Python. It is possible to write IRAF package CL scripts that define a mixture of IRAF and Python tasks that will work gracefully with both PyRAF and the IRAF CL in the sense that both types of task will work in PyRAF, and if the package is loaded in the CL a warning message will be printed that indicates that some tasks require PyRAF. If one attempts to run such a Python task from the IRAF CL, another warning message will be printed. While the task doesn't work, it does tell the user why not (i.e. it requires PyRAF).

3.1.1 A Simple Example

Here is a bare-bones example for creating a task `xyz' written in Python that can be called from PyRAF just like any other IRAF task. Two files are used, xyz.py and xyz.par. In this example, the name ``xyz'' is used throughout, but this is not required. While the rootname of the parameter file does need to be the same as the task name (as in the IRAF CL), the other names may differ. There is another example below that uses different file names.

The parameter file xyz.par is an ordinary IRAF par file. In this example the file contains the following:

input,s,a,"",,,"string to print"
mode,s,h,"al"

xyz.py contains the following. <path> should actually be the name of the directory that includes xyz.par (see below for clarification):

from pyraf import iraf

def xyz(input):
    print input

parfile = iraf.osfn("<path>xyz.par")
t = iraf.IrafTaskFactory(taskname="xyz", value=parfile,
            function=xyz)

In PyRAF, define `xyz' as a task by running the pyexecute() function:

--> pyexecute("<path>xyz.py")

At this point `xyz' is a PyRAF task; you can `lpar xyz', `epar xyz', or just run it.

The value parameter in IrafTaskFactory is the complete path name of the parameter file xyz.par. This could be given explicitly, but it is cleaner to use the iraf.osfn() function to take an IRAF ``virtual file name'' and return an operating-system dependent directory and file name. For example, if xyz.par were in the scripts subdirectory of the user's IRAF home directory, the argument to iraf.osfn would be "home$scripts/xyz.par". It is also possible to use the Python functions in the os and os.path modules to find files, fill in path names, etc. The rootname of a parameter file must be the same as the task name, and the filename extension must be ``.par'' (as in IRAF).

Note that the value of the function parameter in IrafTaskFactory is xyz, not "xyz". It's a reference to the function, not a string containing the function name. This is the function to be executed when the task is invoked. IrafTaskFactory can be used to create a wide variety of tasks, such as a package, CL script, pset, or ``foreign'' task; the function parameter is only used when the task being defined is a Python function.

The argument to pyexecute should include the directory (using IRAF notation), unless xyz.py is in the default directory when pyexecute is called. The task will be defined after IrafTaskFactory has executed. Running pyexecute is the recommended way to do this, but it isn't the only way. You could instead use execfile("<path>xyz.py"), using host syntax for the directory and file name. Or you could use import xyz if xyz.py is in your PYTHONPATH. One advantage of pyexecute is that you can call it from a CL script. If the script is run from a PyRAF session, the Python/PyRAF task will be defined; if the script is run from the IRAF CL (and STSDAS has been loaded), a warning will be printed to say that PyRAF is required, but it will not raise an exception. This works because there are files pyexecute.cl and nopyraf.cl in the STSDAS directory, and this pyexecute is what will be run if the user is in the IRAF CL rather than in PyRAF.

Note that pyexecute.cl has no intrinsic connection to STSDAS. If you wish to have the flexibility to include Python scripts in your IRAF packages and still be able to run either PyRAF or the IRAF CL, but STSDAS will not necessarily be loaded, you can simply copy pyexecute.cl and nopyraf.cl from STSDAS to some IRAF package that will be loaded and define these as tasks. You can install these in the IRAF tree if you have permission to do so.

The statement import iraf can be used in scripts that run in PyRAF. If a script might be run from Python or from the host operating system command line, use from pyraf import iraf instead. IRAF parameters, tasks and packages are objects, just like everything else in Python. Packages in IRAF may be loaded by executing them, which is very similar to the way they are loaded in the IRAF CL; for example: iraf.images(). The primary way that tasks are invoked in PyRAF is by using the __call__() method of the task object, i.e. it looks like any other function call. Since tasks (and parameters, etc.) are objects, they can be assigned to variables and passed to functions:

t = iraf.imhead
lparam(t)

3.1.2 An Example Using the _iraf Filename Convention

Here is another example, this one using different file names, partly to illustrate a convention that's used in STSDAS, to separate the PyRAF interface from the main Python script and to use a name ending in _iraf.py for the former. The files are assumed to be in the scripts subdirectory of IRAF ``home''. The task name is ncounts, and the files are ncounts.par, xyz_iraf.py and nc.py. Note that the task name and root of the par file name are the same; the other names may differ.

This task uses the images.imstat task to compute the total number of counts in an image. The standard output of imstat is assigned to a Python variable text_output, which is a list of strings that in this case contains just one string, [`npix mean'] (not this literal string, but rather the numerical values). The split() method splits this into two strings, one with npix and one with mean. The result is simply the product of these two, after converting from string to float. The result is assigned to the task parameter total, and it is also printed if verbose=yes.

The parameter file ncounts.par contains the following:

image,s,a,"",,,"image name"
verbose,b,h,yes,,,"print the value?"
total,r,h,0.,,,"(task output) number of counts in the image"
mode,s,h,"al"

xyz_iraf.py contains the following:

from pyraf import iraf
import nc

def _abc(image, verbose, total):

    total = nc.calc_ncounts(image=image, verbose=verbose)
    if verbose:
        print "total =", total

    # Update the value in the par file.
    iraf.ncounts.total = total

parfile = iraf.osfn("home$scripts/ncounts.par")
t = iraf.IrafTaskFactory(taskname="ncounts", value=parfile,
            function=_abc)

nc.py contains the following:

from pyraf import iraf

def calc_ncounts(image, verbose):
    """use imstat to get the total number of counts in an image"""

    iraf.images(_doprint=0)     # load the images package
    text_output = iraf.imstatistics(image, fields="npix,mean",
            format=0, Stdout=1)

    values = text_output[0].split()

    #      number of pixels     mean value
    return float(values[0]) * float(values[1])

In PyRAF, define ncounts as a task:

--> pyexecute("home$scripts/xyz_iraf.py")

The statement iraf.images(_doprint=0) loads the images package (without printing the task names and subpackage names). This could be skipped if the images package is already loaded, e.g. by the user's login.cl file, but it is generally not safe to make such an assumption, and it is harmless to load a package more than once.

The Stdout=1 parameter in the call to imstat means that the standard output of the task will be returned as the imstat function value. In this example the output is assigned to a Python variable text_output, which is then processed using Python. The variable text_output is a list of strings, one for each line of output from the task, in this case just one line. This feature serves as a substitute for I/O redirection, but for many applications it is also much more convenient than using a temporary file. This is discussed further in the section on I/O redirection.

In the above ncounts example, separating the Python code into two files xyz_iraf.py and nc.py was not necessary, it's a convention to isolate the PyRAF-specific code. xyz_iraf.py contains the part that defines the task, deals with the parameters, and calls a Python function to do the work. The latter function is in nc.py. The separation in this case is a little artificial, since calc_ncounts in nc.py still calls an IRAF task. On the other hand, nc.py could be imported into Python or (with minor additions) invoked from the shell, while xyz_iraf.py defines a task and requires a parameter file, which is more closely associated with the interactive aspect of PyRAF.

3.1.3 IRAF and Python Interfaces

Python and IRAF use different conventions for undefined values. The interface for an IRAF task should use IRAF conventions. If the script includes a Python function that could be used stand-alone, however, it would be more reasonable if that function used Python conventions. For example, a task might have input and output arguments, and it might modify the input file in-place if an output file name was not specified. In the IRAF CL a string may be left unspecified by setting it to null ("") or blank, and there is a special INDEF value for numeric variables. In Python, None is used for any unspecified value. One purpose for the _iraf.py file is to convert unspecified values from one convention to the other. Another purpose is to check that input files do exist and that all required parameters have actually been specified. It's very helpful to the user of a script to check for parameter problems at the beginning, especially if the task could run for some time.

3.1.4 Importing Modules

Note that xyz_iraf.py uses import nc. In order for this to work, nc.py must be in your Python path (sys.path) when you run pyexecute. For tasks in the STSDAS directory tree, this is accomplished by having a python subdirectory of stsdas, with a link to each of the packages (in the Python sense) that may be imported; the stsdas$python/ directory is included in sys.path when the stsdas package is loaded in PyRAF. When writing your own tasks that are not to be included in the STSDAS tree, one option is to simply put all the source files in one directory and add that to your Python path. Another option is to create a package subdirectory for each task or related set of tasks, with the root of these subdirectories in your Python path.

3.2 IRAF Executables

IRAF executables are created by compiling and linking code written typically in SPP (but Fortran and C can also be used). The task statement in the SPP code is converted by the SPP preprocessor into code that makes the connection with the CL. One executable may contain multiple tasks. A task in an IRAF executable can be defined in a Python script by using the task() function, for example: iraf.task(xyz = "home$scripts/abc.e"). Note that the keyword argument has the same syntax as would be used in the IRAF CL (or interactively in PyRAF) for defining a task, except that quotes are required. There must be a parameter file with the same root name as the task name and extension .par, in the same directory as specified for the executable. Additional keyword arguments PkgName and PkgBinary may be used to specify the package name and list of directories to be searched for the executable (e.g. PkgName="clpackage", PkgBinary=["bin$"]).

The IrafTaskFactory() function may be used instead of task(). IrafTaskFactory was described earlier for defining Python scripts, but both of these functions are quite general in terms of the types of tasks that they can define.

The SPP task statement can define multiple tasks (these are just different subroutines) to be included in the same executable. It is common practice to use just one or perhaps a few executables for the tasks in an IRAF package. This saves disk space for the executables (since the IRAF libraries are common to all tasks), and it reduces loading time. The syntax for defining multiple tasks in the same executable is a little peculiar. Each task except the last is given in quotes as an argument, and the last task is given as a keyword argument using the syntax shown earlier. For example, iraf.task("task_a", "task_b", task_c = "home$stuff.e").

One obscure feature of IRAF that you need to be aware of is that when the CL looks for the executable for a task, it looks first in the bin directories (e.g. iraf$bin.redhat); it only looks in the directory specified in the task statement if the file is not found in any of the bin directories. Thus if you use an executable name that happens to be the same as one in an IRAF package (e.g. x_tools.e is in STSDAS), your executable will not be found. This explains the peculiar wording in the first paragraph of this section, ``in the same directory as specified for the executable.'' The task statement might say the executable is in home$scripts/, while it might actually have been installed in a bin directory; nevertheless, the parameter file must be in home$scripts/, not in the bin directory.

3.3 IRAF CL Scripts

CL scripts may be defined as tasks in PyRAF using the task() function, e.g. iraf.task(jqz = "home$scripts/jqz.cl"). A PkgName could be defined, but it wouldn't make sense to specify a PkgBinary. Interactively in PyRAF, the same syntax is used as in the IRAF CL, e.g. task jqz = home$scripts/jqz.cl".

The goto statement is not yet supported (though its addition is being considered for a future version of PyRAF), so CL scripts that use goto statements cannot currently be defined as tasks in PyRAF without being revised to avoid the use of goto.

Even if you intend to use a CL script exclusively in the IRAF CL, defining it as a task in PyRAF is useful for testing and debugging, since PyRAF prints informative error messages. In PyRAF, the CL script is translated into a Python script when the task is defined, and it is the Python script that is executed when the task is run. You can obtain a copy of the Python script using getCode(), which is a method of the IrafCLTask class, e.g. p = iraf.jqz.getCode(). The code is in the form of a Python string, so it can be executed using the exec statement or the eval() built-in function.

3.4 Foreign Tasks

The task() function can be used for defining ``foreign'' tasks. Interactively, a task could be defined by, for example, task $emacs = "$foreign". The word $emacs cannot be used as a keyword argument because of the dollar sign, however, so the task() function for this example would be as follows: iraf.task(DOLLARemacs = "$foreign"). To define both emacs and vim as foreign tasks in one call, use iraf.task("$emacs", DOLLARvim = "$foreign").

The dollar sign before the task name (emacs or vim, in this example) means that the task has no parameter file. Arguments may be given when invoking the foreign task, however, and those arguments will be passed directly to the command (except that file names using IRAF environment variables will be converted to host file names). The dollar sign in $foreign indicates a foreign task, while the word ``foreign'' means the task name and command name are the same. Here is an example where the names are not the same: iraf.task(DOLLARll = '$ls -lg'); interactively, this would be task $ll = "$ls -lg".

Questions or comments? Contact help@stsci.edu
Documented updated on