Chapter 1 - Building a Module

Chapter 1 - Building a Module

This chapter takes you step by step through the process of creating a simple module in the Module Builder. The proposition is to build a module that acts as a channel selector. This module will take in an array containing a vector of values per sample, and output an array with a scalar at each sample. The user manipulates a widget to select which scalar, or channel, is output.

The example shows how to build a module with a user function written either in C or in Fortran. The process is identical for both, but some menus differ, depending on the language used. These differences are called out clearly in the text.

The information on building a module spans two chapters:

If you want more information than is given in Chapter 1 about a particular step, refer to the parallel section in Chapter 2.

Introduction to Module Building

The IRIS Explorer product uses modules to carry out operations on data in order to visualize the data in a way that has meaning to the observer. The modules supplied with IRIS Explorer offer a range of functions, but many users will want to construct their own modules, providing a more specific function or greater power than already exists.

The Module Builder is a tool that lets you build your own IRIS Explorer modules, either by modifying and renaming existing IRIS Explorer modules or by creating new ones. The great virtue of the Module Builder is its graphical user interface, which lets you build a basic module with no programming beyond that needed to write the computational function.

The module-building process has three main stages:

Once the module is built, you can wire it into a map in the Map Editor and use it like any IRIS Explorer module.

Creating the User Function

The user function is the computational function or subroutine that acts on the data fed into the module through its input ports and produces new or changed data on the output ports. It provides the core functionality of the module.

Designing the Example Module

You are designing a module that will select one data variable, or channel, from an array that has a number of data variables. Arrays in IRIS Explorer are called lattices. The module is a simple selector with no limits on the range or data type. Here is a brief description of the structure you need in the new module, which you will call ChannelSelector.

It should take in all the data variables from the input lattice and put out values for only one data variable. It should have a widget for selecting a particular data variable. It needs an input port, an output port, some defined variables for the lattice data and coordinate values, and a computational function to select out the data values for a specific channel.

Writing the User Function

The example shows how you can create a channel selector module with a C function or a Fortran subroutine. The module accepts input data in the form of an IRIS Explorer lattice. You need not be concerned with lattices now, except to note that as a matter of convention, the variable names in this function correspond closely with the variable names in the cxLattice data type structure.

To find out more about lattices, read Chapter 3 - Using the Lattice Data Type.

Example Code and Modules

Working example code for this module can be found in $EXPLORERHOME/src/MWGcode/Tutorial/C and $EXPLORERHOME/src/MWGcode/Tutorial/Fortran, where $EXPLORERHOME is the root directory of IRIS Explorer on your machine. There are also complete module resources for this code, called ChannelSelector.mres in the same directories. You can open the respective files to see how yours should look.

Note: This example module is built around code that does not contain any references to the IRIS Explorer Application Programming Interface (API), which is a suite of routines that provide access to the IRIS Explorer data types (such as the lattice). Since the code does not call the API, it serves as an illustration of the way in which an unmodified user function can be used as the basis for a module. As an alternative, the module may be modified to use the API, which greatly simplifies the building of the module (at the expense of modifying the module source). The alternative code is presented in The ChannelSelect Module in Chapter 2, and the ways in which its construction differs from that of the example module are described below, in Building a Module which uses the API.

In the following sections you are taken through the steps required to construct a module using existing code, while imposing as few restrictions on the module as is feasible. This situation will occur frequently if you reuse existing and well tested code, for example from a library. Some steps might well be different if you decide to write code specifically for the module. This is indicated in the text.

The C User Function

The function called chan in the file channel.c serves as the user function in this module. It is written in C and includes a call to a function called sizes which is also shown below. The code shows the source of the function arguments used later in this chapter.

Note that the code is written for float data only. Through the specification in the module resources file, IRIS Explorer can ensure that this code may also be used for other data of a different type. To find out more about automatic type coercion see Automatic Type Coercion of Arrays in Chapter 2.

/* User function to select a single data channel
 * from a lattice of type float
 *
 * The IRIS Explorer wrapper code will ensure that lattice data
 * of a different type will be converted to float before being
 * passed to this function, and the output lattice converted from
 * float to the type of the input lattice.
 *
 * This feature enables you to write a much more general module that
 * can deal with all data types, not just float. This will be of
 * particular use when you need to utilise already existing code.
 */
long sizes(long nDim, long *dims);

void chan(long nDim, long *dims, long *nDataVar, long which,
             float *dataIn, float *dataOut)
{
  int i, numElems, sel;

  /* Number of data channels in the input lattice */
  numElems = sizes(nDim, dims);

  /* Use a legal range for the data */
  sel = which - 1;
  if (sel < 0)
    sel = 0;
  else if (sel >= (*nDataVar))
    sel = (*nDataVar) - 1;

  /* Copy the data to the output lattice */
  for (i = 0; i < numElems; i++)
    dataOut[i] = dataIn[i*(*nDataVar)+sel];
  *nDataVar = 1;
  return;
}

long sizes(long nDim,long *dims)
{
  long foo, i;

  foo = 1;
  for (i = 0; i < nDim; i++)
    foo *= dims[i];
  return(foo);
}

The Fortran User Function

The Fortran subroutine called chan in the file channelSelect.f is the user function in this module. This code fragment shows the source of the function arguments used later in this chapter. In addition, there is a subsidiary function, written in C, called sizes in the file sizes.c. This example therefore illustrates the way in which a module can be created from mixed language source.

Note that the code is written for REAL data only. Through the specification in the module resources file, IRIS Explorer can ensure that this code may also be used for other data of a different type. To find out more about automatic type coercion see Automatic Type Coercion of Arrays in Chapter 2.

Here is the Fortran subroutine:

C     Subroutine to read in a two-dimensional array
C     and output a one-dimensional array, both of type REAL
C
C     The IRIS Explorer wrapper code will ensure that lattice data
C     of a different type will be converted to REAL before being
C     passed to this function, and the output lattice converted from
C     REAL to the type of the input lattice.
C
C     This feature enables you to write a much more general module that
C     can deal with all data types, not just REAL. This will be of
C     particular use when you need to utilise already existing code.
C
      SUBROUTINE CHAN(NDIM,DIMS,NVARS,WHICH,DATAIN,DATOUT)
C     .. Scalar Arguments ..
      INTEGER         NDIM, NVARS, WHICH
C     .. Array Arguments ..
      REAL            DATAIN(NVARS,1), DATOUT(1)
      INTEGER         DIMS(NDIM)
C     .. Local Scalars ..
      INTEGER         I, J, NUM
C     .. External Functions ..
      INTEGER         SIZES
      EXTERNAL        SIZES
C     .. Intrinsic Functions ..
      INTRINSIC       MAX, MIN
C     .. Executable Statements ..
C
C     Number of elements that needs to be copied
C
      NUM = SIZES(NDIM,DIMS)
C
C     Data channel that needs to be copied
C
      J = MIN(MAX(1,WHICH),NVARS)
C
C     Copy the elements into the output array
C
      DO 20 I = 1, NUM
         DATOUT(I) = DATAIN(J,I)
   20 CONTINUE
C
C     Number of data channels in the output lattice
C
      NVARS = 1
C
      RETURN
      END

Here is the C routine. This function is called twice - once from the module data wrapper (which is written in C, and is automatically generated by the Module Builder) and once from the user function itself. The form of the definition of SIZES reflects the compiler-specific convention for calling C from Fortran.

long sizes(long nDim,long *dims)
{
  long foo, i;

  foo = 1;
  for (i = 0; i < nDim; i++)
    foo *= dims[i];
  return(foo);
}

// This is for the function called from Fortran.
#if (defined(WIN32) && !defined(__alpha))
// Microsoft's Fortran PowerStation Compiler has a different linkage system 
// from compilers on UNIX machines. The Dec Alpha NT compiler uses the same
// linkage system as UNIX but with the __stdcall mechanism.
long __stdcall SIZES(long *nDim,long *dims)
#elif (defined(WIN32) && defined(__alpha))
long __stdcall sizes_(long *nDim, long *dims)
#else
long sizes_(long *nDim,long *dims)
#endif
{
  return  sizes(*nDim, dims);
}

Since cxLattice is a C data structure, the Fortran code contains pointers to the cxLattice data type (see Chapter 3 - Using the Lattice Data Type).

Setting up your Build Environment

To avoid overwriting files in the example directory, it is a good idea to first create a test directory and copy the example code into it. Make a subdirectory in your home directory and copy the example code from $EXPLORERHOME/src/MWGcode/Tutorial/* into it. You can now build your module using the Module Builder and run it in the Map Editor. Just follow the steps in the next sections.

C or Fortran?

The method for building modules is the same for both languages, although some of the information you enter varies according to whether the user function is written in C or Fortran. Because the similarities are so great, this tutorial presents the process for users of both languages together. Where the activities for the two language diverge, the differences are clearly marked and parallel instructions are given for each language.

Invoking the Module Builder

Having selected your language, start the Module Builder from within the appropriate subdirectory of your test directory. The Module Builder is invoked using the following command:

mbuilder
The Module Builder main window appears (see
Figure 1-1 and Figure 1-2). Click in the Module Name slot to select it and delete the default, then type in the module name ChannelSelector. In the next slot, enter the name of the file that contains the user function:
C Users:
channel.c
Fortran Users:
channelselect.f sizes.c

You can also specify include files and additional libraries or your own Makefiles, but none are required for building the ChannelSelector module. To learn more about these options, read through The Include Files, Associated Libraries, and User Makefiles in Chapter 2.



Figure 1-1 The Module Builder Window for C Users


Figure 1-2 The Module Builder Window for Fortran Users

Saving the Module Resources

The Module Builder creates a number of files as you go through the construction process. The Module Builder automatically saves all the module characteristics, or resources, that you have just specified in the module resource (.mres) file before it builds the module. This is a binary file and cannot be accessed directly.

You can use Save Resources on the File menu to save your work at any time during the module construction phase.

It is a good idea to save your work periodically if you expect to be interrupted or if you are building a very complex module.

Exiting from the Module Builder

To exit from the Module Builder at any time once it has been invoked, click on the File menu on the main window and select Quit (see Figure 1-3).



Figure 1-3 Module Builder File Menu

Defining the Internal Structure

The inputs, outputs, function arguments, and connections among different variables constitute the internal structure of the module, and you define this structure in the Module Builder windows. You can have only one window open at a time, other than the main window.

Creating an Input Port

Click on the first button, Input Ports, to bring up the Input Ports window (see Figure 1-4).

Note: The window is initially displayed with only one text slot. Click on the slot to highlight it and press Enter to produce a new text slot.

The module requires an input port that will accept data in the form of the lattice data type.

  1. Click in the first text slot to highlight it, then type the name Input in the first text slot to create an input port.
  2. Click on the button on the option menu in the middle column to choose the data type menu and select cxLattice.


Figure 1-4 Input Ports Window

Defining the Data Type

When you select cxLattice from the data type option menu, the Lattice Constraints window appears (see Figure 1-5). To define the characteristics of the lattices the input port will accept:

  1. Click off the buttons to get the results shown in Figure 1-5.

    The input port called Input will now accept a lattice of any dimension and data type, containing any number of data variables and with any number of coordinates per node. Since the module only acts on the data part of the lattice, we have marked the coordinates part as optional (that is, the input port will accept lattices without any coordinates). Note that it is good practice to write modules with as few restrictions as possible. As the user functions have restrictions on the data types, the generality is in this case achieved through automatic type coercion by IRIS Explorer wrapper code. In general, you need not change existing code to widen its application range; IRIS Explorer can do this for you.

  2. Data values are required, but coordinate values need not be given, so Data Structure is marked Required and Coord Structure is Optional.
  3. Click OK to save the setting and close the window.


Figure 1-5 The Lattice Constraints Window

Defining the Parameters

The next step is to define a parameter for the input port in the Input Ports window (see Figure 1-4).

  1. Click the second slot in the Input Ports window and enter the parameter name Which.
  2. Select cxParameter from the data type option menu.
  3. Select Required from the option buttons in the third column for both ports.
  4. Click OK to save the setting and close the window.

The module must receive data on both its input ports before it will fire, so each port is made a Required port.

Creating an Output Port

Click the Output Ports button to bring up the Output Ports window (see Figure 1-6). The module needs an output port so that it can pass the data from the selected channel downstream to the next module.



Figure 1-6 The Output Ports Window

To create the output port:

  1. Enter the name of the output port, Output, in the text slot.
  2. Select cxLattice from the data type option menu.

    When the Lattice Constraints window appears (see Figure 1-7), set the constraints. All the constraints, except the Num Data Variables, are the same as those for the input lattice, however, the Num Data Variables is set to 1 in the output lattice because only one data channel is being output.



Figure 1-7 Lattice Constraints for the Output Port
  1. Click OK to save the constraints setting and close the window.
  2. Click OK to save the output port setting and close the Output Ports window.

For more information about input and output ports, read Creating Ports in Chapter 2.

Defining the Function Arguments

The Function Arguments window defines each function argument in the user function. Each function argument must be connected to an input or output item. You list the function arguments in their calling sequence, as shown in Figure 1-8 and Figure 1-9.

To define the function arguments:

  1. Click the Function Args button to display the Function Arguments window. When the window first appears, all of its fields are blank.
  2. Enter the name of the function, chan, in the Func Name slot. This is the name of the function contained in the channel.c or channelselect.f file.
  3. Set the language option. If you are using the C function, select C from the language option menu. If you are using the Fortran subroutine, select Fortran from the language option menu.
  4. Enter the first function argument for the user function in the text slot. Press Enter after typing each function argument name to generate a new text slot.

    The function arguments for the C function are shown in Table 1-1 and Figure 1-8.

    The function arguments for the Fortran subroutine are shown in Table 1-2 and Figure 1-9.

  5. Set the corresponding values for the argument type and reference by clicking each option menu button in turn. The C values differ from the Fortran values, as you will see from the function argument tables.
  6. When you have entered all of the arguments, click Apply to have them accepted but keep the window open.
  7. At the bottom of the window, you will see the function name displayed with all its arguments listed. Click OK to close the window and return to the Module Builder main window.

For more information about the data types and reference mechanisms, read Defining Function Arguments in Chapter 2.

These are the function arguments for the C function.

Table 1-1 Function Arguments for the C Function
Argument Name Type References
nDim int Scalar
dims int Array
nDataVar int &Scalar
which int Scalar
dataIn float Array
dataOut float Array

These are the function arguments for the Fortran subroutine.

Table 1-2 Function Arguments for the Fortran Subroutine
Argument Name Type References
nDim integer Scalar
dims integer Array
nDataVar integer Scalar
which integer Scalar
dataIn real Array
dataOut real Array

Figure 1-8 shows the argument names, data types, and references you enter if you are building your module around the C user function.



Figure 1-8 The Function Args Window for C Users

Figure 1-9 shows the argument names, data types, and references you enter if you are building your module around the Fortran user function.



Figure 1-9 The Function Args Window for Fortran Users

Connecting Ports and Function Arguments

Each port must be connected with one or more function arguments, so that the incoming data can be routed to the correct function argument and the outgoing data can be directed to the correct output port.

The connections are identical for C and Fortran user functions.

  1. Click on the Connections button to invoke the Connections window (see Figure 1-10). When it is first displayed, no connecting wires exist. These appear as you wire ports and function arguments together.
  2. To connect an input port to a function argument or output port, click with the right mouse button on the input port name bar. A popup menu appears, listing the data structures associated with the port. For example, the input port Input accepts lattice data, and its popup menu (see Figure 1-11) lists the lattice substructures. They contain data arriving on the module's input port from upstream. The connections you make from the data structures on the input ports to the function arguments determine which data are recognized and processed, and which data are ignored.
  3. Select the structure member you want, for example, Dimensions Array. The function arguments and ports that can accept a connection are highlighted.


Figure 1-10 The Connections Window
  1. Then click on a function argument, for example dims, with the right mouse button. A small Select button appears (see Figure 1-10).

    When you click Select, a blue wire links the input port with the function argument.

  2. Follow the same procedure to link input ports or function arguments to the output port. The output port menu items are printed in blue if they require a connection for the integrity of the data structure (see Connecting Arguments to Ports, in Chapter 2).

To break a connection, simply click on both ends of the connection, as in the Map Editor.

The popup menu in Figure 1-11 shows one connection from the input port (top-level Lattice Structure) to the output port.



Figure 1-11 Input Port Popup Menu

It also shows four connections from lattice components, such as the number of dimensions (Num Dimensions), to function arguments. The Output port has three connections passing into it, although the port menu is not shown. The details of the data type structures are explained in Chapters 3 through 7.

Making the Right Connections

The correct connections are critical to the proper operation of the module. In this example, the values affected by the function arguments are passed separately to the lattice structure on the output port. The values for all members of the lattice data type not explicitly linked to function arguments are passed directly from the input port to the output port as default values.

For more about connecting ports and function arguments, read Connecting Arguments to Ports in Chapter 2.

To build the ChannelSelector module correctly, you must make these connections. They are the same for the C and the Fortran modules.

Input
Lattice Structure to output port Output: Lattice Structure

Num Dimensions to function argument nDim

Dimensions Array to function argument dims. A button appears on the left-hand side of the function argument. You can toggle the Copy option by pressing the button; make sure the option is switched off (see Figure 1-12).

Num Data Variables to function argument nDataVar

Data Array to function argument dataIn

Which
Value to function argument which (see Figure 1-13)


Figure 1-12 Array Function Argument. Copy is switched off, so the array itself is passed to the function.


Figure 1-13 Port Popup Menu for a Parameter
<<Storage>>
Select to argument dataOut, then set the storage value (see Figure 1-15).

Note: If a connection to the <<Storage>> pseudo input port is used to allocate space for arrays, then storage must be allocated by means of a C function call (or expression) - even for Fortran-based modules. C programmers might prefer to allocate storage for arrays in the usual way with malloc inside their programs, rather than using the <<Storage>> connection. Alternatively, if the array is part of an IRIS Explorer data type, it is almost always easier to use the IRIS Explorer API to create the data type directly; this point is illustrated below, in Building a Module which uses the API.


nDataVar
Select to port Output: Num Data Variables

dataOut
Select to port Output: Data Array

Using the Pseudoports

<<Storage>>, <<Constant Value>> and <<Extern>> are pseudo-ports. When you connect one to a function argument, you use the second item on the function argument popup menu) to define the value for the pseudo-port. In this example, you need to define the storage value. The value is the same for C and Fortran modules.



Figure 1-14 Function Argument Menu for an Array Item

To set the storage size for dataOut, click the menu item Set storage size and enter the text shown in Figure 1-15. There is a default setting which you should delete first.

This text is expected by the module data wrapper code that the Module Builder generates when the module is built. In this example, a routine is called to compute the number of floating (C) or real (Fortran) values that must be allocated for the dataOut function argument.



Figure 1-15 Setting the Storage Size

You can enter comments after the values if you wish, to remind you and explain to others why you chose this value.

When you have filled in all the popup windows and made all the connections you need, click OK on the Connections panel to save the configuration and close the window.

For more information about the use of pseudo ports, read Creating Ports, in Chapter 2.

Defining the User Interface

The module has an interface that allows it to be controlled by the user. This interface is the module control panel. See Editing Control Panels and Functions in the IRIS Explorer User's Guide for more details on control panel design and widget use.

The module control panel holds widgets, such as dials and sliders, that let you control the values of input port parameters interactively.

Click the Control Panels button to invoke the Control Panel Editor. The Control Panel Editor and a prototype control panel appear (see Figure 1-16).



Figure 1-16 Control Panel Editor and Control Panel

All input ports of type cxParameter appear in the Parameters list in the Control Panel Editor. By default, new parameters have no widget associated with them. You can change this via the Type option menu. For example, to set the widget for the Which parameter port to another kind, click the left mouse button on it to select it, then click the Type option menu under Widget Attributes. You can select any widget type that is not grayed out. In Figure 1-16 the widget has been set to a vertical slider.

To change the widget's position in the control panel, click it to highlight it, then hold down the left mouse button and drag it into a new position.

To change the widget's size, use the type-in slots to enter a new set of dimensions, or drag on any of the eight black handles around the widget.

To set the lower limit for the slider, double-click on the bottom text slot to the right of the slider and type 1, then hit Enter. The module input port can accept a lattice with any number of data variables, and so the upper limit for the slider will be unknown in general, but you can still set a sensible value for this number. So, type 5 in the top slot on the right-hand side and hit Enter (see Figure 1-16). Finally, you can enter a current value for the parameter in the text field above the slider.

Creating a Menu Item

You can create your own menu bar options for the module control panel. For this module, you will create a File menu with a Reset option.

  1. Select Menu Bar from the Edit menu on the Control Panel Editor. The Menu Bar Editor appears (see Figure 1-17).
  2. Type File in the first slot to name the menu.


Figure 1-17 Menu Bar Editor
  1. Click the Menu button underneath the slot. The Menu Editor appears (see Figure 1-18).
  2. Type Reset in the first menu item slot to name the action.
  3. Click the option menu button next to the first slot and select the action Set param from the option menu.

    A new option menu button appears on the right, together with a text slot.

  4. Click the option menu button and select the parameter which is to be set (in this case, there is only one parameter, Which).
  5. Type 1 in the text slot to specify the parameter value.
  6. Click OK to apply the selection and close the window.

    You can close each open window individually, or you can select OK on the Control Panel Editor, which closes all the subsidiary windows and applies the changes you have made.



Figure 1-18 Menu Editor

Testing the Menu Bar

You can click on the Control Panels button to bring up the prototype control panel again. You will see that it now has a File option on its menu bar. Click File on the menu to see the Reset option.

This completes the construction of the module. The final step is to build the module and install it in IRIS Explorer so that you can use it in a map.

Building the Module

You have a blueprint for a module that must now be turned into an executable program. The code is linked and compiled during the build process.

Selecting Build Options

The Module Builder Build menu provides lets you set defaults and build the module. To check the build options for the ChannelSelector module, select Options... from the Build menu. The Build Options control panel appears (see Figure 1-19).



Figure 1-19 The Build Options Control Panel

You want to accept all the current defaults. You want the Module Builder to:

Click OK to accept the default options and close the window.

For more information about build options, read Building Modules, in Chapter 2.

Running the Build Command

The Build command is on the Build menu (see Figure 1-20).

  1. Click on the Build menu and select Build. This command builds the module in the current working directory. The output from the build process is displayed in this directory.

If you receive this message:

compile, link or install MAY have failed,

check to see that you are running the Module Builder from your current directory.



Figure 1-20 Build Menu Options
  1. Select Quit from the File menu to exit the Module Builder.

You can now start IRIS Explorer and launch the ChannelSelector module from the Module Librarian.

To see how it works, connect these modules: ReadImg to ChannelSelector to DisplayImg. Read in the image file $EXPLORERHOME/data/image/nag.rgb and select each of the color channels, 1 to 3, in turn. You may also reset the channel to 1 by clicking on the File option on the menu bar and selecting Reset.

Building a Module which uses the API

The source for the example module discussed here does not make use of the IRIS Explorer Application Programming Interface (API). In this section, we present an alternative module which performs the same function as the example module, but which uses the API. The API is a library of routines which give access to the creation and manipulation of the IRIS Explorer data types. It is described fully in the IRIS Explorer Reference Pages. The advantage of using the API is that it allows IRIS Explorer types to be handled within the function, which simplifies the way in which the function is connected to the module ports. The disadvantage is that the source must of course be modified to to invoke the relevant routines.

The alternative source (in C and Fortran) which uses the API is presented in The ChannelSelect Module in Chapter 2. Here, we describe the differences between its module resources and those which have been constructed in this Chapter. These only appear in two places - the Function Arguments and the Connections. Everything else - the definition of the ports and the control panel - is unchanged.

The Function Arguments

Figure 1-21 and Figure 1-22 show the Function Args Window for the C and Fortran versions of the module. In C, the input and output lattices are both typed as cxLattice*, with the input lattice being passed as a Scalar, and the output lattice as a &Scalar (i.e. the address of a scalar). In Fortran, both lattices are typed as an integer and passed as a Scalar. The close correspondence between the definitions in the window and those on the function call line can clearly be seen.



Figure 1-21 The Function Args Window for C Users


Figure 1-22 The Function Args Window for Fortran Users

The Connections

The Connections Window is the same for both C and Fortran versions of the module. The Lattice Structure component of the Input port is connected directly to inLat, and outLat is connected directly to the Lattice Structure component of the Output port. Because the output lattice is created inside the module (via a call to the cxLatNew API function) there is no need to use the <<Storage>> pseudo input port together with an internal function to calculate its size, as was required in the example module.



Figure 1-23 The Connections Window

You can mix IRIS Explorer datatypes with the other primitive datatypes on the function call line. For example, there is a connection from the Value component of Which to the which argument, as before.


Last modified: Feb 09 17:15 1999
[ Documentation Home ]
© NAG Ltd. Oxford, UK, 1999