OpC 2.04 User's Manual

John Gruenenfelder


Table of Contents
1. Introduction
1.1. The Program
1.2. The Language
1.3. The FITS Format
2. Building OpC
2.1. Aquiring OpC
2.2. Compiling OpC
2.3. Building Documentation
3. Running OpC
3.1. OpC by Design
3.1.1. Commands
3.1.2. Expressions & Functions
3.1.3. Self Documenting Functions
3.1.4. Important Functions
3.1.5. Operators
3.2. Your Friend, the Prompt
3.3. Working with Images
3.4. Casting
4. Function Modules
4.1. What are Function Modules?
4.2. Sections of a Function Module
4.2.1. Section: module_definition
4.2.2. Section: The Code
4.3. Error Handling in a Module Function
4.4. Screen Output Extraction Layer
4.5. Compiling your module
5. Scripting with OpC
5.1. Running Scripts
5.2. Creating Script Files
A. A Few Useful OpC Scripts
A.1. Fourier Transform Script
A.2. Wiener Filter/Wiener Deconvolution Script
B. libhlist Reference
B.1. Introduction
B.2. Structures and Data Types
B.3. FITS file input and output
B.4. Creating a FITS Header
B.5. Inserting and Removing Cards
List of Tables
3-1. Allowed Type Casting
3-2. Allowed Image/Pixel Subtype Casting

Chapter 1. Introduction

 

NOTE: No warranties, either express or implied, are hereby given. All software is supplied as is, without guarantee. The user assumes all responsibility for damages resulting from the use of these features, including, but not limited to, frustration, disgust, system abends, disk head-crashes, general malfeasance, floods, fires, shark attack, nerve gas, locust infestation, cyclones, hurricanes, tsunamis, local electromagnetic disruptions, hydraulic brake system failure, invasion, hashing collisions, normal wear and tear of friction surfaces, cosmic radiation, inadvertent destruction of sensitive electronic components, windstorms, the Riders of Nazgul, infuriated chickens, malfunctioning mechanical or electrical devices, premature activation of the distant early warning system, peasant uprisings, halitosis, artillery bombardment, explosions, cave-ins, and/or frogs falling from the sky.

 UNIX fortune program

1.1. The Program

OpC is a tool for performing analysis of images. The format of choice for image data is the FITS format, however, any data which can be expressed in the FITS format can be used easily with OpC. OpC provides a C-like syntax for performing a myriad of mathematical operations on data, from simple arithmetic to complex transformations.

Two modes of operation are present in OpC. The first is an interactive command interpreter which executes commands as the user types them and presents back the results. The second mode is execution of scripts from the command line. OpC will execute the script using arguments given on the command line with the same syntax as used in the interpreter.

OpC is a modular, self-documenting interpreter. The bulk of OpC's functionality lies in runtime loadable function modules. These modules add functions which can be used within OpC by the user. Functions also document themselves too. Descriptions of syntax, arguments, return types, and help text are all required when creating an OpC function. This information is then available to the user when the program is running.


1.2. The Language

OpC's language is very C-like, and should be easy to pick up. OpC is, for the most part, an expression based functional language. With the exception of five basic commands, everything in OpC is an expression, and as such can be nested repeatedly. OpC is a mostly functional language. All functions return values regardless of what they do and variables are immutable unless explicitly assigned. That is, when you pass a variable to a function, it will never be changed, though the result of the function may be new data which you could reassign to the existing variable.

OpC supports a number of different data types:

  • boolean

  • int

  • double

  • string

  • image

  • pixel

Values of type boolean are either true or false and are used in control statements and as the return value of many functions. Values of type string are characters quoted with a pair of quotation marks. Standard C escape characters are allowed in strings. Values of type image are the primary focus of OpC. Internally they are represented as FITS data. Finally, values of type pixel can be though of as 1x1x1 images, only without header information.


1.3. The FITS Format

OpC's usefulness is centered on manipulation of image data. Internally, image data is represented in the FITS format. The image contains a FITS header describing the data and the actual data itself. The FITS format is the standard for astronomical analysis and is used by a large number of programs. Briefly, a FITS file consists of an ASCII text header followed by raw binary data. The header is made up of a series of 80 character cards which contain information about the data such as dimensions, type, size, as well as bookkeeping information and logs. Because of the flexibility of the FITS format, most any other image data format can be expressed in the FITS format and so can be loaded and used by OpC (provided a function to load it exists).


Chapter 2. Building OpC

2.1. Aquiring OpC

The official web page for OpC can be found at http://bach.as.arizona.edu/opc. From this site you can download the program source or i386 binaries in Debian deb format or Red Hat RPM format. If you download the binaries, install them and skip this chapter.


2.2. Compiling OpC

OpC uses the GNU autotools (autoconf, automake, libtool) to make configuring OpC a simple process. These tools also aid greatly in portability, so OpC should work on a large number of UNIX-like systems. You do not, however, need these tools to compile the distribution, only if you intend to do some developement on OpC or modify the config/Makefiles. Please report any success/failure stories on systems you've tried OpC on.

First, unpack the distribution. Next, you must run the configure script in the top directory. This can be done one of two ways, either in the top directory itself, or by creating another directory (a build directory) and running it from there (i.e. '../opc-2.00/configure'). This second method is cleaner, especially if you plan on working with the source at all, but it doesn't matter which you choose.

Running configure --help will display a large amount of information. With the command line options you can direct where OpC is installed (default is under /usr/local). There are also two options to control the behavior of OpC. The first is to enable/disable the use of GNU readline, a library which provides inline editing and command history functions for the interative interpreter. It defaults to enabled if readline is present. The second is to enable debugging output. This is primarily useful if you need to know some of the internals (perhaps to fix a problem) of the parser and lexer used in OpC. It defaults to disabled.

After configuring, run make and wait a little while. Opc should build without any errors on your system if configure worked properly. After the compilation is finished, you can install OpC to it's final location. If the destination is not in a directory you own, you will need to be root to install OpC. Run make install (or make install-strip for a slightly smaller install size). OpC is now ready to run.

If you have any troubles with the compilation/installation process, please report them. For general help on using and running the configure script, read the INSTALL file included in the distribution. It contains information regarding the common options in the configure script, such as how to relocate the installation target directory, and cross compiling for another platform.

By default, the build process will generate the OpC executable and its associated libraries. These will be dynamic libraries if your system supports them, static otherwise. If you want a completely static build of OpC, specifiy the --enable-static and --disable-shared options when running configure. The resulting OpC binary will have all libraries and modules compiled into one large executable. If your system allows for dynamically loadable modules, you will still be able to load other OpC modules at runtime even if your binary was statically linked.


2.3. Building Documentation

The OpC User's Manual (what you're reading now) can be built/regenerated from the command line after running configure. You will need Jade, a suite of utilities to convert SGML text into something suitable for printing, in order to create the documents. Specifically, the configure script looks for a program called jw, the Jade Wrapper. If it is found, the appropriate portions of the Makefile will be enabled to allow creation of the manual.

To generate the manual, go to your build directory (or the source directory if they are the same). Run make FOO where FOO is one of: html, ps, pdf, txt, or texi. Not all options may function, depending on your installation of Jade and what helper utilities you have installed. Afterwards, the docs directory will contain a printable/viewable form of the documentation.

The OpC User's Manual was created with the DocBook DTD (version 4.01), which is a standardized SGML definition. Any tool which understands this format should be able to convert the manual (opc_manual.sgml) into any variety of formats. Visit http://www.docbook.org for more information on DocBook and its related tools.


Chapter 3. Running OpC

3.1. OpC by Design

In OpC, almost everything you type in is an expression, just as with any mathematical language. OpC's command set is broken up into roughly four categories: commands, expressions, functions, and operators. Commands and expressions are separate entities, and functions are part of expressions. All statements, whether they be commands or expressions, are delineated by semicolons. This allows for multiple statements per line. Whitespace is also insignificant in OpC save for at least one space to separate words (but is not needed to separate symbols/numbers from operators, for example).


3.1.1. Commands

Commands are typically entered singly on the command line and they do not return values (with the exception of free). Unlike functions, the arguments to commands are not placed in parentheses. The command set is simple and not extensible with loadable modules. Commands are typically used to communicate with the interpreter and not for performing actions on data.

Command List

bye / quit

Exit OpC

free

Free the memory used by a variable. Most variables are small, so this is not needed, but a large multiframe array may need to be freed if you are low on memory. Syntax: free variable

help

Get general or specific help. If run without an argument, some general help information will print out followed by a list of command and a list of installed functions with their synopses. If given a function name as an argument, full help for that function will be displayed. Syntax: help [function_name]

status

Display status information about the current state of OpC. Displays number of symbols, current interpreter options, and lists the currently loaded function modules.

varstatus

Display a list of all currently defined variables and their data types. At program start, you will only see e and pi defined.


3.1.2. Expressions & Functions

Expressions are the base element in OpC's parsing. Expressions can be numbers, function calls, subexpressions, etc. Because expressions can contain subexpressions, you may nest expressions as you see fit. Put parentheses around a set of operators to effect operator precedence.

Function calls in OpC follow the standard C calling convention. The function name is followed by a comma separated list of arguments (expressions) inside a pair of parentheses. It is perfectly acceptable to have function calls nested since they are just expressions. For example:

OpC) print( sin(cos( tan(5 * ( 6 + 7)))));


3.1.3. Self Documenting Functions

In the tradition of the UNIX editor EMACS, OpC has adopted a design policy of making functions self documenting. The documentation for a function is required and is created at the time a function is written. This information includes the functions return type, type of each argument, a short one line synopsis, and a larger free form description. The synopsis should be limited to no more than 56 characters in order to keep the formatting of the help display clean, but this is not a strict requirement.

This documentation is available on-demand to the user through the interpreter by using the help command. The specified types for the functions arguments are also used to perform type checking when calling a function and OpC will inform the user when there is a type mismatch, indicating what type was given and what type was expected. For more information on the help system, see Chapter 4.


3.1.4. Important Functions

While all functions have online documentation, a few of the more important ones will be described here, including image I/O and creation functions, FITS header functions, module functions, and screen output.


3.1.4.1. Image I/O and Creation

Three important functions fall into this category: fitsrd, fitswr, and newimage. The syntax for each is:

image fitsrd(string filename)

boolean fitswr(image im, string filename)

image newimage(string dtype, int x, int y, int z)

fitsrd and fitswr are found in the opc_fileio module. They let your read in and write out FITS images, respectively.

newimage, as the name implies, creates a new blank image as its output. Ideally you would want to specify the datatype for the new image by just putting its name. However, if this is done, the lexer will treat it as a variable name. So, the datatype must be put in quotes instead. The image will default to a value of zero for all pixels. To set all pixels in an image to a particular value, use the setval function.


3.1.4.2. FITS Header Manipulation

FITS images, as stated previously, have text headers made up of 80 character cards. In the OpC module opc_header, there are several functions to add new cards to the header, delete a card, or print out an images header. They all take an image as the first parameter and other parameters depending on the purpose of the function. The functions which modify the header will return an image as output. The original will not be changed, so be sure to put the ouput into a variable.


3.1.4.3. Module Functions

OpC supports loading modules during runtime. With the exception of a few built-in functions, all functions in OpC are provided by function modules. You can load your own modules into OpC to provide new functionality. This is achieved with the use of the addpath, load, and unload functions.

The function load takes a single string argument specifying the module to load. This string can be in one of two forms. The first is simply the module filename, with or without a pathname. If no pathname, or a partial pathname, is given, then the argument will be considered to be relative to the current directory. If a full path name is given, then that is all that will be checked. The second form is the base name of the module. The base name is the filename without any path information and without the file extension. For example, the module mods/opc_math.so would become opc_math.

The function unload will remove a loaded function module and its associated functions from memory. unload takes the module's base name as its only argument. To see what modules are loaded and what their base names are, use the status command.

The addpath function can be used to decrease the amount of typing you must do. OpC's module loader maintains a list of directories containing function modules. With addpath, you can add a new directory to this list. The argument to addpath must be a full pathname. These paths are searched when you give a module's base name to load. If you instead specify the full pathname for a module when loading, they will be ignored.


3.1.4.4. Output with print

print is a special built-in function. It can take any number of arguments of any type. To print something, simply pass it to the function as an argument. To add strings, just separete the arguments with commas as in any other function. For example:

a = 5;
b = "Hello";
print("The var a = ", a);
print(b, " world!");
print(a, " == ", 5);
	

Would print the following three lines.

The var a = 5
Hello world!
5 == 5
	

print always adds a newline at the end of its arguments, though you can add more by putting \n in a string.


3.1.5. Operators

3.1.5.1. Mathematical Operators

Mathematical Operator Listing

= (assignment)

Assign a value to a variable. The value may be a constant or an expression. Valid for all types.

- (unary minus)

Negate a value. This operator only works with int, double, and signed image values.

+ (addition)

Add two values together. Valid for all combinations of number values. Strings may be added together. Most value types may be added to images, though pixels are more limited.

- (subtraction)

Subtract one value from another. Valid for all combinations of number values. Most types can be subtracted from image values, but not vice versa. Pixels can only be subtracted from images and other pixel values. Strings cannot be used in subtraction.

* (multiplication)

Multiply two values together. All combinations of int, double, and image are valid. Numbers and strings may also be multiplied togeter (the result being a string repeated x number of times). Images may also be multiplied with pixels. Pixels can be multiplied with other pixels.

/ (division)

Divide one value by another. All combinations of numbers are valid. Images may be divided by numbers, images, and pixels. Pixels can only be divided by other pixels. Strings cannot be used in division.

^ (exponentiation)

Raise one value to the power of another value. All combinations of numbers are valid. For images, numbers and pixels may be used as the exponent. For pixels, only other pixels can be used as the exponent. Strings cannot be used in exponentiation.

% (modulo)

Compute one int modulo a second int. Integers are the only type that can be used in a modulo operation.


3.1.5.2. Boolean Operators

The boolean operators are used to form more complex execution control. Use them to add more clauses to if and while statements rather than adding additional if/while statements.

Boolean Operator Listing

&&

Boolean AND. Both left and right expressions must equate to true for expression to be true.

||

Boolean OR. Either the left or the right expression must equate to true for expression to be true.

!

Boolean NOT. Reverses boolean truth value of expression.

<, >, <=, >=

Boolean inequality operators. True if left value is less than, greater than, less than or equal, or greater than or equal to the right value, respectively.

==, !=

Boolean equality operators. True if left value is equal to or not equal to left value, respectively.


3.1.5.3. Execution Control

OpC currently provides two operators for execution control: if and while. These are the basic flow control operators. They are boolean operations and take any expression as their argument, just as in C. More complex methods, such as do-while and for, can be created from these two basic operators. The others may be built into OpC at a later time. Here is an example:

a = 0;
while (a < 5)
  {
    if (a % 2)
      print("a=", a);
    a=a+1;
  }
print("Done!");
       

3.2. Your Friend, the Prompt

All interaction with OpC takes place at the OpC) prompt. If GNU readline support is compiled in (it is by default), the command prompt will allow inline editing as well as keep a history of your previous commands. The history can be accessed by pressing the up-arrow key. Readline is used in a number of applications, such as the bash shell, and allows for a very extensive set of customizations, and in general greatly increases usabilty (and you probably won't get frustrated as often).

Also at the prompt you may escape out to the shell and execute some command by typing a '!' as the first character of the line. Anything following that will be executed via the system() systemcall.


3.3. Working with Images

All images in OpC are stored internally in the FITS format. A subrange of image frames or a subraster of an image (or images) can be specified without an explicit function call. The syntax:

new_image = img[x1:x2, y1:y2, z1:z2];
    

The above x, y, z values specify a range in the image img. A subraster of a single frame, for example, could be specified as img[20:40,30:50,5:5]. The result would be 21x21 subraster from frame 5. The final part could also be written as a single 5 as well. If you want to specify the entire range for that dimension, simply omit the values:

new_image = img[,,z1:z2];
    

Since the result of this bracket operator is an image, it can be put in place of a regular image anywhere one is required in a function call.

Additionally, OpC supports reverse indexing. By inverting the range limits above, you can reverse that particular range in the image. Thus, img[40:20,50:30,5:5] would give you the same area of the source image as the example above, but both the rows and columns would be reversed. Reverse indexing is possible on all three axes and can be performed on one or more axes at a time.


3.4. Casting

Values of one type may be cast to another type in most cases. A type cast follows this syntax:

new_val = (type)val;
    

If the cast is not allowed, an error will be generated and new_val will not be set to a new value. Table 3-1 lists the available types in OpC and what other types they may be cast to:

Table 3-1. Allowed Type Casting

TypeCan cast to:
boolean boolean, int, double, string, pixel
int
double
stringboolean, int, double, string
imageboolean, int, double, image
pixelboolean, int, double, pixel

Additionally, images and pixels have data types for the data stored within them, and they can be cast to other data types. Casting this subtype effects only the image or pixel and is not valid with any of the other normal types. Table 3-2 gives the valid image/pixel casting types.

Table 3-2. Allowed Image/Pixel Subtype Casting

TypeCan cast to:
ubyte ubyte, byte, ushort, short, ulong, long, float, double
byte
ushort
short
ulong
long
float
double
complex Casting not supported
dcomplex

Unlike with boolean, int, and double types, there is no automatic type casting in function calls for image/pixel subtypes. This is important because many of the functions that OpC includes work only on a data type of float so you will have to manually do the cast.

For complex and dcomplex types, in lieu of casting to another data type, use the real and imag functions to extract the real and imaginary portions of the data, or use the mod and pha functions to extract the modulus and phase portions of the data. These functions will return an image with a data type of float for complex images or a data type of double for dcomplex images.


Chapter 4. Function Modules

4.1. What are Function Modules?

Function modules contain the bulk of OpC's usefulness. They hold nearly all of the functions that OpC can perform. OpC was written to be modular so that new modules could be easily added and so that current functions could be replaced with newer ones without modification to the program itself.

Functions in OpC can also be overloaded with a newer version. So, for example, if you wanted to replace the default FITS reader, fitsrd with your own version, you would just create a module containing a function of the same name and load it. You version will now be used instead. Upon unloading your module, the original will be put back.

Function modules can be written in any language that will let you create a dynamically loadable object. The examples here are in C, but as long as you export the right symbols, the module should work just fine. One point to keep in mind, however, is that the only provided interface to the internal OpC functions is given in opc.h. Any language can call those functions, but it is the user's responsibility to call them correctly. If you do create such a binding, please send it in to be included with the OpC distribution.


4.2. Sections of a Function Module

Modules are fairly vague and have only two required parts. The first is a global symbol named 'module_definition' which should be an array of strings. These strings contain all of the information about what the module provides. The module_definition has a strict format. The second part is the actual function code and can do whatever you want it to.


4.2.1. Section: module_definition

The module_definition is an array of groups of six null terminated strings per function in the module. It tells OpC everything it needs to know about the function and how to use it. There is no deliniation between strings for one function or another except that they always come in groups of six. The end of a module_definition is signified by a NULL. The strings follow this format:

module_definition Sections

Function Name

This is the user visible name in OpC; that is, the name the user will type to execute the function.

Symbol Name

This is the name of the symbol in which the function resides. When OpC loads the module, it will attempt to locate this symbol to register the function.

Parameters

A comma separated list of parameter types and names that this function will accept. Valid types are: boolean, int, double, string, pixel, and image. Parameter names can be anything and are simply to help the user, but they must be present. As a special case, if the function takes a variable number of parameters, a single parameter of ... may be specified without any name associated with it. It is then up to the function to perform type checking and type conversion.

Return Type

All functions return a value (they're functions, after all). The valid return types are: boolean, int, double, string, pixel, and image.

Synopsis

This is a one line string of text which identifies the function when the user enters the help command. On an 80 column display, you should limit this string to no more than 54 characters, or else the string will print onto the next line. This limit is not enforced in any way, however.

Help text

Variable length help text to inform the user of the purpose and method of using this function. This help text can be of any size you wish and may be as long as you like. As far as the users are concerned, the more help text the better.

In C, a valid module_definition containing two functions would look like this:

const char *module_definition[] = {
  "max",
  "opc_max",
  "image in",
  "image",
  "Compute max value in an image",
  "Displays max value and stores value in a DATAMAX header card in the\n"
  "output image.  The output image is otherwise unchanged.",

  "min",
  "opc_min",
  "image in",
  "image",
  "Compute min value in an image",
  "Displays min value and stores value in a DATAMIN header card in the\n"
  "output image.  The output image is otherwise unchanged.",

  NULL
};
      

4.2.2. Section: The Code

This section will only cover creating modules in C, since that is the only language for which OpC provides header files. A module in OpC receives an array of "nodes" as its single parameter and returns a node pointer as its result. A node is the OpC abstract data type for pretty much everything. A node can be a constant value, symbol (variable or function), or an operator. As OpC's input is parsed, a node tree is generated and then executed.

When a module function is executed, the list of nodes that it receives has already been validated and contains only value nodes. Variable symbols are replaced with the value nodes they contain. If the module_definition does not specify an unknown number of parameters (...), the node list will also be type checked to make sure the parameters are correct. Furthermore, types of boolean, int, and double will be typecast amonst one another to make everything sane. This additional work is not performed if the number of parameters is unknown (because OpC doesn't have any way of knowing). It is the responsibility of the module function to type check its arguments if the ... is specified.

Nodes of type image require a little special handling. When you tell OpC that your function takes an image as an argument, OpC will allow any image to be passed to your function, regardless of what its internal data type is. For example, some functions in OpC will only work on images of type FLOAT, but the user can pass the function any image type they wish. The function must then check the type member of the image structure to make sure that type is allowed. If the image is not of the correct type, the proper procedure would be to print an error message and return NULL. Failure to perform this check can cause the program to crash if you begin working on images of a type your function does not support.

A module function declaration would look like this:

node * opc_max(nlist *args);
      

All of the needed types are defined in opc.h, so that should be included in your source file. The file opc_nodes.h contains the declarations for some functions that will be used later. You need to include it as well. The nlist structure is simple, but the node structures which it contains are somewhat complex. To access an image value (an image pointer) in the first node of the list from the above example, you would use:

args->nodes[0]->value.data.image_val
      

Or, using a macro provided in opc.h, the much simpler:

MODULE_ARG(0).image_val
      

nodes is the name of the array of node pointers. This array has a maximum size of 20, but this can be changed when compiling OpC if necessary. A node is a union of the three node types: valueNode, identNode, and operatorNode. A function will only ever receive valueNodes. The others are internal to OpC. A valueNode contains a type value to identify itself as a valueNode and data. The data is another union which contains all of the data types OpC supports. Thus we have accessed the first parameter to our module. Luckily, accessing the other parameters is almost identical. Just increment the array index and make sure the last part matches the data type you want. The types available are listed below. Browse through opc.h for more information on the structures used.

valueNode types

bool_val

A value of type boolean. boolean is a type declared in common.h (included by opc.h) and can have a value of either TRUE or FALSE.

int_val

A value of C type int.

double_val

A value of C type double.

string_val

A pointer to a NULL terminated string (C type char *).

image_val

A pointer to a structure of type image, defined in hlist.h (see Appendix B).

pixel_val

A pointer to a strucure of type pixel, defined in im.h. This is rarely used.

When working with the data be sure to always work on a COPY of the input, and where applicable, return that COPY as the result. OpC tries to adhere to a functional approach to language design. As such, data should be immutable except when using the assignment operator ("="). And in the case where a user nests several functions, the input to your module may be a copy itself and is therefore transient. This approach is a bit less efficient in its use of memory than other ways, but it is also much simpler.

When you return data, you return a node containing that data. There are several functions available to make this a very simple task, one for each data type OpC supports. The functions (declared in opc_nodes.h) are:

  • constant_bool()

  • constant_int()

  • constant_double()

  • constant_string()

  • constant_image()

  • constant_pixel()

The first three take a simple value and put it in the node. The last three take a pointer to their data and store that pointer. They do NOT copy the data. All six functions return a node *, which you can then return from the module function.

Put all together, here is an example module file defining the function max. It contains the necessary #includes, the module_definition, and the function itself:

#include <opc/opc.h>
#include <opc/opc_nodes.h>


const char *module_definition[] = {
  "max",
  "opc_max",
  "image in",
  "image",
  "Compute max value in an image",
  "Displays max value and stores value in a DATAMAX header card in the\n"
  "output image.  The output image is otherwise unchanged.",

  NULL
};

node *
opc_max(nlist *args)
{
  boolean       ret;
  pixel         pix;
  image         *out;

  out = iduplicate(args->nodes[0]->value.data.image_val);
  ret = imax(&pix, out);
  if (!ret)
    {
      execute_errorflag = TRUE;
      pimerr();
      XFREE(out);
      return NULL;
    }
  else
    return constant_image(out);
}
      

There are a few unfamiliar parts in the above example. The function iduplicate is in libim and declared in im.h which is included by opc.h. It creates a copy of an input image. This is a frequent task in many of the module functions, so there is a function to do it for you. The function imax is also in libim and it performs the actual max finding. The other unknown parts in the if statement are for error handling (see below).


4.3. Error Handling in a Module Function

OpC maintains an error flag to signal the interpreter than an error has occured. The symbol is named execute_errorflag and is defined in opc.h as a boolean. When an error occurs, a function should set this flag to TRUE and return a value of NULL instead of an actual node. This is the only time when it is acceptable to return NULL. When OpC finds this flag set, it will stop running the current execution program and go back to the interactive prompt, or the command line if running a script. This ensures that OpC is not left in an inconsistent state when an error occurs.

At some point, you will very likely use some of the functions available in libim, OpC's image processing library. Many (but not all) functions in this library use a global enum variable, Imerr, to signal an error condition. This enum and its possible values are declared in imerr.h which is included by im.h. When a function in libim encounters an error condition, this variable will be set with an error code. A value of zero or IMNOERR signifies that no error occured.

libim provides a function pimerr to output a meaningful text message if an error occured. This function will also reset Imerr to zero. pimerr uses OpC's screen output extraction layer to print its message.


4.4. Screen Output Extraction Layer

OpC provides an extraction layer for output to the screen. This layer allows for automatic logging, if enabled by the user, without any additional work by the module author. Currently, the extraction layer only supports outputting strings to the screen. In the future, if a GUI interface is ever added, this same system would allow output to an output port in the GUI, whereas a simple C library printf would always go to the console.

For normal screen output, use the functions declared in common.h. This file is included by opc.h. Output with the opc_print() function. It is called in exactly the same manner as the C printf() function. If logging is enabled, the string you output will also be copied to the log file. If, for some reason, you want to send a string only to the log file, you can use the function opc_log() which is called in an identical manner.

The normal output routine is subject to the user's desire for quiet, and the output can be disabled if the user sets the quiet flag in the interpreter. However, error messages shouldn't be ignored and OpC provides extra functions for those. For simple warnings, use the err_warn() function. It's behavior (currently) is much like opc_print() and it obeys the quiet flag. For a more serious error, use the err_error() function. It is always output regardless of the quiet flag. Finally, for a critical error which can in no way be recovered from (you really ought to avoid this), use the the err_fatal() function. It is always output regardless of the quiet flag and will cause the program to exit immediately. All three of these functions have identical syntax to opc_print().


4.5. Compiling your module

OpC modules are dynamically loadable shared objects. This means that they can be loaded at runtime by OpC and OpC needs no prior knowledge of them. Normally, when adding functions to a program you would need to recompile that program so that it can be made aware of the new abilities. OpC avoids this problem by loading shared object modules at runtime.

A shared object must be compiled slightly differently from a normal object file and must be linked differently. Unfortunately, this procedure will differ from machine to machine. If your system has libtool installed, you can use it to get around the peculularities of your particular system. Consult its documentation for more information. Given here is the procedure that works on many systems with GCC. GCC is very portable, but not all options work on all machine, so the following procedure may not work.

To compile your module, the only extra step is to add a new flag to the command line. That flag is -fPIC. This will cause the compiler to generate position independent code (PIC). This is necessary so that OpC can load your module anywhere into memory and have it execute properly. In general, this is all that should be necessary for most compilers, though the exact name of the flag may differ.

Linking your module is a little more complicated and the commands used are very compiler/linker specific. The module must be linked the same way you would link a shared library (since that is essentially what your module is). The following command line should work with GCC:

gcc -shared FILENAME.lo -Wl,--no-whole-archive -lm \
-Wl,-soname -Wl,FILENAME.so -o FILENAME.so

A little explanation... the -shared flag to GCC is passed to the linker indicating that you want a shared object to be linked. The -Wl flag is used to pass options directly to the linker. In this case, we are passing -soname FILENAME.so. Shared objects have a "shared object name" stored within them. The -soname flag sets this value in the output module. That's about it. You should now have a working module.


Chapter 5. Scripting with OpC

5.1. Running Scripts

Scripts in OpC can be run in one of two ways. They can be run from the command line non-interactively, or they may be run from within a running session of OpC.

To run a script from the command line, use the -s switch and specify the script filename as the argument, followed by any arguments the script may take. Arguments to scripts always come after all normal command line options. For example, a script which adds two numbers together, or one which processes two images and outputs to a third file, would be run like this:

opc -s addtwo.opc 5 7

opc -s proc_image.opc /home/johng/psf.fits /home/johng/filter4.fits newimg.fits

To run a script from within OpC, use the run function. The first argument to run is a string containing the name of the script. Additional arguments are required only if the script requires parameters. Argument substitution is similar to that on the command line, but slightly more complex. For example, the two examples above would become:

run("addtwo.opc", 5, 7);

run("proc_image.opc", "/home/johng/psf.fits", "/home/johng/filter4.fits",
    "newimg.fits");

Notice that string arguments must be quoted. What run really does is to convert all of the arguments you give it into strings through OpC's normal typecasting routine. This means that simple numbers (like 5 and 7 above) will be automatically cast to strings for you. Images, however, can not be automatically typecast. If you want to give any variable name as an argument you must quote it, otherwise the variable's value will be substituted and a typecast performed and only bool/int/double can be typecast to strings. See the online help for run for more examples.


5.2. Creating Script Files

A script is a plain text file, typically ending with .opc (not required). It contains a series of commands to be run in OpC in the same manner as you would enter them into the interactive interpreter. There are a couple of important differences in the processing, however. Interactively, a command ends when you press RETURN and you need not add the ending semicolon since the interpreter will do this for you. When running a script, carriage returns have no special meaning (though they may not occur unescaped in quoted strings) and the semicolon delimiter is required at the end of all commands. In this manner, a script resembles the C language a bit more than when using the interpreter interactively.

Another special feature of script files is that they may take arguments. These arguments will then be placed into the script before it is executed. An argument is replaced wherever a dollar sign followed by a number occurs ("$1", "$15", etc.). The replacement is entirely literal and no processing is done on the arguments. The argument marker in the script is replaced exactly with the characters specified on the command line. Thus, for a multiple word replacement, be sure to quote on the command line. Currently, a maximum of 20 arguments may be given to a script, although this can be changed at compile time.

Comments are allowed in script files too. You may use C-style /* */ comments as well as C++-style // comments. These will actually work in the interactive interpreter as well, though they are not of much use. The C-style comments may also span lines and can occur anywhere in your script, with the exceptions of inside words or quoted strings.

A script using the above number adding example might look like this:

/*
 * Simple OpC script to add two numbers together
 */

a = $1;
b = $2;
c = a + b;    // compute sum
print("Sum of ", $1, " and ", b, " is ", c);

This simple example demonstrates the use of both types of comments as well as using argument replacement throughout the script. A second script, from the image processing example above, might look like:

/*
 * OpC script to process two images
 */

in = "$1";
a = fitsrd(in);

out = a + fitsrd("$2");
fitswr(out, "$3");

Whenever an argument is a string, do not forget to quote that string in the script. An alternative would be to escape a quote character on the command line, but this is messy and error prone. This next example shows the usage of the if and while keywords in a multi-statement expression (by using curly braces):

/*
 * Multiline expressions in OpC using curly braces
 */

a = 0;
n = 0;

while (a < 15)
  {
    if ((a != 0) && (a % 2 == 0))
      {
        print(a, " is even");
        n = n+1;
      }
    a = a + 1;
  }

print("There were ", n, " even numbers");

This example would produce the following output:

> opc2 -s hoi.opc
2 is even
4 is even
6 is even
8 is even
10 is even
12 is even
14 is even
There were 7 even numbers

OpC will perform a check on the number of arguments in the script to make sure the proper amount are provided. If you supply too many arguments, or the script contains an argument marker for which no argument is given, OpC will give an error and not execute the script.

When running a script, OpC will not output the result of each line as it does when run interactively. The only output you will see will be from functions which output text or from the print() function. If you do not want this behavior, you can put the following line at the beginning of your script to re-enable this feature:

set("print_result", true);

Finally, to help you create scripts based on your current interactive session, OpC provides a function to output the sessions command history. The command history is accumulated whether or not you are using GNU readline for input, though you can only access it interactively when using readline. The output_history("filename") function allows you to output the command history to a named text file. This file can then be edited to create a script.


Appendix A. A Few Useful OpC Scripts

A.1. Fourier Transform Script

This script computes the Fourier transform of an input image. The real part of the complex image is the input data and the complex part is initialized to zero. The script then outputs the modulus and phase portions of the resulting image.

/*  Fourier Transform script for OpC (v2.00)  */

a = fitsrd("$1");
b = reass(fft(reass(cmplx(a, a-a))));
fitswr(mod(b), "$1_mod");
fitswr(pha(b), "$1_pha");

A.2. Wiener Filter/Wiener Deconvolution Script

This script computes a Wiener filter on the input object. If a PSF is provided, the script will instead perform a Wiener deconvolution of the input data. Input data is expected to be a 2N x 2N array. Note that the two input images must end in "_mod" and "_pha" respectively, but that this ending is not given when naming the files on the command line.

/* OpC (v2.00) script
 * Wiener filter/Wiener deconvolution
 * Input expects: FT Obj and FT Ref
 *
 * Run: "opc -s WF.opc Obj Ref f_c f_max N"
 *   Obj is object to be Filtered
 *   Ref is Psf reference data (for deconvolution -- else use Obj)
 *   f_c is d diffraction limit
 *   f_max is bound of domain for computing noise bias
 *   N is size of square array 2N by 2N
 */

/* Transliterated from EKH by John Gruenenfelder 07 Jun 2001 */

a = fitsrd("$1_mod");
print(a);
maa = max(a);
mia = min(a);

av = rstat_mean(a, $5, $5, $3, $4);     // Presumes (2x$5) x (2x$5) images
print(av);

b = fitsrd("$2_mod");
print(b);
mab = max(b);
mib = min(b);

a1 = rstat_mean(b, $5, $5, $3, $4);     // Presumes $3 is f_c = D/lambda
print(a1);

S = a - av;                     // Removes modulus bias = sqrt(|n(u)|)
N = S^2;                        // |S(u)|^2
p = b - a1;                     // Removes modulus bias = sqrt(|n(u)|)
a0 = av * av;                   // Bias^2
a1 = a1 * a1;                   // Bias^2
n = a0 + a1;                    // Noise from both Signal and Psf estimate
D = N + n;                      // |S(u)|^2 + |n_o(u)|^2 + |n_r(u)|^2
W = N / D;                      // Wiener Filter
mw = max(W);
w = W / mw;                     // Restore DC level
wf = disk(w, $5, $5, $3);
fitswr(wf, "WF");
M = S / p;                      // Linear Deconvolution

pr = fitsrd("$2_pha");
print(pr);

mi = min(pr);
ma = max(pr);
pa = fitsrd("$1_pha");
print(pa);

mi = min(pa);
ma = max(pa);
P = pa - pr;                    // Linear Deconvolution
print(P);

WF = M * w;                     // Wiener Filtered modulus
wf = aper(WF, $3);              // Aperture taper to difffraction limit
fitswr(wf, "WF_mod");
c = reass(cmplxmp(wf, P));      // Complex mod, pha
d = reass(ifft(c));
im = real(d);
fitswr(im, "$1_WF_$2");

Appendix B. libhlist Reference

B.1. Introduction

OpC's main focus is working with FITS images. This is also the format used for internal representation, regardless of the input image type. FITS images have an ASCII text header detailing image specifics and virtually any other information you might want to add (notes, history, calibrations, etc.). libhlist is a C library with a small set of functions for performing FITS file I/O and manipulating FITS headers.

libhlist contains functions for creating new image structures, reading/writing FITS files to and from disk, creating new headers, adding/removing cards from an existing header, reading/writing headers to and from disk, and extracting data from the header. Internally, a header is represented as a doubly-linked list of header cards. Each card contains fields for the key, value, and optional comment. libhlist also understands FITS extensions to a limited extent (right now it just reads image extensions).

libhlist is compiled along with OpC and installed in the same manner. By default, the library will be located in /usr/local/lib. To use the library, you must include the header file, hlist.h, which will be at /usr/local/include/opc/hlist.h. In order than the header find the files it needs, you should include -I/usr/local/include/opc in your compiler command or Makefile.


B.2. Structures and Data Types

Defined in hlist.h are the three primary structures used in libhlist. The overall image structure is called image and it contains information about the image, the image data, and a pointer to a header list structure. The list structure is called hlist and is a simple doubly-linked list. A pointer to a list of this type is passed to nearly all of the functions in libhlist. The list is comprised of a collection of nodes of type hnode. An hnode contains fields for a header cards key, value, comment, and data type.

Each hnode has an associated data type so that libhlist knows how to handle it. Some functions allow you to specify this type, for example when reading data from a particular card, or creating a new card. The possible FITS card data types are: FITS_CARD_BOOLEAN, FITS_CARD_STRING, FITS_CARD_INT, FITS_CARD_FLOAT, FITS_CARD_COMPLEX, FITS_CARD_COMMENT, FITS_CARD_HISTORY, FITS_CARD_EMPTY, FITS_CARD_UNKNOWN. Each corresponds to the allowed types of cards as given in the FITS standard. FITS_CARD_UNKNOWN should not be used as it indicates an error in the parsing of a card. FITS_CARD_EMPTY indicates a completely empty card (appearing as a blank line in the output file). FITS_CARD_COMMENT indicates a card which contains only a comment with no key or value.

In addition to the data types for header cards, the image data associated with a header also has a data type. These are enumerated in opc/common.h. The possible image data atypes are: imtypeUBYTE, imtypeBYTE, imtypeUSHORT, imtypeSHORT, imtypeULONG, imtypeLONG, imtypeFLOAT, imtypeDOUBLE, imtypeCOMPLEX, imtypeDCOMPLEX. These data types are used to set the BITPIX header card to the proper value and also set the IMTYPE card. IMTYPE is not a standard FITS header card, but is useful for more precisely indicating what type of data is in the FITS file.


B.3. FITS file input and output

boolean fitsrd(image * im, char * name, int begin, int end, int exten);

This function will read into memory a FITS file from disk as given by name. The image pointer im must already be initialized with the iinit function. begin and end are the first and last frame in the range you wish to read from the file. If you want to read all frames, specify FITSRD_ALL_FRAMES (-1) for both values. If you want the image structure to be populated and have the header read in, but do not want any data read, specify FITSRD_NO_FRAMES (-2) for both values. exten is the FITS extension you wish to read. The primary array is extension 0, and is usually what is wanted. Currently, fitsrd only understands FITS image extensions.


B.4. Creating a FITS Header

hlist * h_init(void);

Initialize an empty header list. Allocates necessary space and returns pointer to new list.

hnode * h_makenode(char * data);

Create a new hnode from an input string. The string should be at most 80 characters and is what would be read directly from a FITS header. That is, something of the form "FITSKEY = value / Comment". Not all portions of the string a required, as per the FITS standard. This function will parse such a string and break it into its component segments. The resulting hnode is complete and may be added to a header list.

hnode * h_create_node(const char * key, const char * value, const char * comment, card_type ctype);

Like h_makenode, this function will create a complete hnode from the parameters. Unlike that function, however, this one will not parse the data, and will just assemble the hnode from the data given. Portions may be omitted by passing NULL. ctype is the type of value the card is storing and is a required parameter.

boolean h_header(hlist * lst, long x, long y, long z, itype dtype);

Given an already initialzed header list (via h_init), this function will construct a minimal FITS header containing the bare number of cards to be full functional. The parameters x, y, and z are the dimensions of the image that will be stored with this header. dtype is the data type of the image data and is defined in opc/common.h. The BITPIX card will be determined from this value.

boolean h_copy(hlist * dest, hlist * src);

Create a copy of an existing FITS header list, src. dest must already be initialied via h_init.


B.5. Inserting and Removing Cards

boolean h_append(hlist * lst, hnode * newnode);

Append the given hnode to the end of the header list. No check is made to see if this really is the correct place for the card.

boolean h_insert_begin(hlist * lst, hnode * newnode);

Insert card at the very beginning of the list.

boolean h_insert_after(hlist * lst, int n, hnode * newnode);

Insert the given card after position n is the list. The position of a card may be found with h_findcard. Cards are numbered starting at one.

boolean h_insert_before(hlist * lst, int n, hnode * newnode);

Same as h_insert_after except that the given card is placed before position n.

boolean h_delete_first(hlist * lst);

Remove the first card from the list.

boolean h_delete_last(hlist * lst);

Remove the last card from the list.

boolean h_delete(hlist * lst, int n);

Delete the card at position n from the list.

boolean h_add_card(hlist * lst, char * key, void * value, char * comment, card_type ctype);

This is the function that should be used to add cards to a header under normal circumstances. It takes the given data and constructs a new header card of the appropriate type (as given by ctype). The card is then added to the proper position within the header. Special cards, like NAXIS or HISTORY for example, are understood. Regular cards will be placed at the end of the list, but before any HISTORY cards.