VIS-AD Version 1.0 ****************** 1. OVERVIEW OF VIS-AD ****************** The name VIS-AD stands for "VISualization for Algorithm Development". VIS-AD is an interactive software system intended to help scientists to develop algorithms and analyze data. Version 1.0 is the first public release of this system and runs on Silicon Graphics workstations only. We will port it to other workstations in the future. See Section 2 of the README document for complete instructions for installing VIS-AD. VIS-AD works something like an interactive debugger. It implements an interpreted high-level scientific programming language. The system provides a coherent user interface for editing, compiling and executing programs, and for visualizing the data in programs. Users may execute their programs continuously or in single steps, and may set execution breakpoints. An important user interface feature of VIS-AD is its HELP icon. Clicking on this icon toggles on a help window that explains how to use the VIS-AD user interface. When you move the mouse over any VIS-AD icon or window, the help window will tell you what it is, how to control it, and something about the current status of your program. ************************************************* WE HIGHLY RECOMMEND THAT NEW USERS LEAVE THE HELP WINDOW ON AND READ IT AS THEY OPERATE VIS-AD. ************************************************* We also recommend running the demo programs distributed with VIS-AD as a way to learn the system. You can get VIS-AD by anonymous ftp from iris.ssec.wisc.edu in the pub/visad directory. Section 2 of the VIS-AD README document describes how to get and install VIS-AD, and Section 4 describes the demo programs. Start with the demo program named "sort.v". Once the VIS-AD system is installed, the command "visad sort.v" will run this demo. In order to support users' existing software and computationally intensive tasks, VIS-AD programs can call functions written in the C and Fortran languages. These are called "external functions". The system links to them via sockets so they may run either locally or on another processor with a network connection. The "julia.v" and "lorenz.v" demo programs provide examples of external functions. The system provides a simple way for users to interactively steer the computations of their programs. At any point in your program you can call a special intrinsic function that creates graphical slider icons and retrieves values that users set on these slider icons with the mouse during program execution. The "julia.v" demo program provides an example of computational steering. The real advantage of using VIS-AD comes from its visualization capabilities. Any program data object can be visualized simply by clicking on its name (note that "data object" is a fancy term for the variables and arrays in your program). You do NOT need to includes calls like "display(temperatures)" in your programs. Rather, you can just click the mouse on the name "temperatures" occurring somewhere in the program and the data object named "temperatures" is displayed. Here's how it works. Every VIS-AD program starts with a set of data type definitions. For example, the "sort.v" demo program starts by defining the data types: type time = real; type temperature = real; type temperature_series = array [time] of temperature; Then inside the functions of "sort.v" the data object named "temperatures" is declared by: temperature_series temperatures; If you click on the COMPILE icon to compile the "sort.v" program and then click on the DISPLAY1 icon, you will see another small text editor window containing the lines: map time to x_axis; map temperature to y_axis; The types "time" and "temperature" are called scalars, and the types "x_axis" and "y_axis" are called display scalars. These two "scalar mappings" tell the system how to display the data object named "temperatures". If you click on the STOP/GO icon to start the "sort.v" program and then click on the name "temperatures" inside the "sort" function, you will see an animated graph of the array of temperatures being sorted. In complex VIS-AD programs with complex data types, the same data object may be displayed with several different sets of scalar mappings. Users may define up to four different sets of mappings at a time. These are in four different editor windows invoked by the icons DISPLAY1 through DISPLAY4. For example, in the "lorenz.v" demo the "val" data object is interesting in the mappings defined for DISPLAY1 and DISPLAY3. And in the "cloud.v" demo the "visirs" data object is interesting in all four sets of mappings. Of course, it is easy to define sets of scalar mappings that are no good for visualizing data, and not all data objects will be interesting in a given set of mappings. Just as programming languages do not write our programs for us, VIS-AD gives us the tools to design displays but does not design them for us. As you look over the demo programs, you will notice that arrays may be indexed by real numbers or even by pairs of triples of real numbers (called "real2d" and "real3d" in VIS-AD). Arrays in scientific programs often store finite sets of samples of continuous functions (i.e., a satellite image is a finite sampling of a continuous radiance field), so VIS-AD interprets arrays with "real", "real2d" and "real3d" indices as finite sets of samples of functions. In the C language the "temperatures" array might be declared by: float temperatures[100]; This tells us the size of the array but only provides the generic data types "int" for the array's index and "float" for its values. In the VIS-AD demo program "sort.v" the declaration of "temperatures" provides more specific data types for the array's index and values but does not tell us its size. The more specific data types in VIS-AD are useful as the basis for the way that users control how data objects are displayed. However, for arrays indexed by "real", "real2d" or "real3d" types we need more information than just their sizes. We need to specify a finite set of values of the index (we call these "sample sets"). In the "main" function of the "sort.v" program the sample set of the index of "temperatures" is specified as a linear progression of 81 values between 0.0 and 80.0, and the sample set of the values of "temperature" is specified as a linear progression of 101 values between 0.0 and 100.0. The "earth.v" demo declares several data objects used to store 2-D image arrays from a variety of different satellites and other sources. The "real2d" indices of these arrays are pairs of "latitude" and "longitude" values that define the Earth locations of image pixels. Each data source samples data at a different set of Earth locations (this is usually called the satellite's "navigation"). Because Earth location sample sets are built into these image data objects, the system will automatically register them in a common Earth frame of reference when they are displayed. The system also uses this information in the way it interprets computation. For example, if we add two images with different Earth samplings (the system supports addition of arrays) the system will first geographically register the images before adding pixel values. VIS-AD supports explicit MISSING data indicators. Any data object or sub-object (i.e., a pixel radiance) may be marked as MISSING. The data types, scalar mappings and sample sets of VIS-AD are a bit novel and require some getting used to. To learn about these ideas, we recommend studying and running the demo programs, and referring to the relevant sections of the README document. If you decide that you want to apply VIS-AD to your own applications, read Section 7 of the README document and then try to modify one of the demo programs that is similar to what you want to do. If you have problems look at the troubleshooting guide in Section 7.6. You can contact us by email with your questions and problems at: whibbard@macc.wisc.edu (Bill Hibbard) and brianp@ssec.wisc.edu (Brian Paul). We target our software developments for hardware that will be available a few years in the future (when our VIS-5D system first appeared in 1988 it could only run on the largest workstations), so VIS-AD runs best on SGI workstations with VGX, VGXT, VTX and Reality Engine graphics. However, it will run on all sizes of SGI workstations. On an Indy, the computations zip right along, but the graphics are a mite sluggish. The outline of the README document is: 1. OVERVIEW OF VIS-AD 2. INSTALLING VIS-AD 3. RUNNING VIS-AD 4. VIS-AD DEMO PROGRAMS 5. THE VIS-AD PROGRAMMING LANGUAGE 6. VIS-AD DISPLAY MAPPINGS 7. DEVELOPING YOUR OWN VIS-AD APPLICATION 8. VIS-AD INTRINSIC FUNCTIONS 9. VIS-AD EXTERNAL FUNCTIONS 10. THE FUTURE 11. COPYRIGHT STATEMENT 12. ACKNOWLEDGMENTS 13. REFERENCES ***************** 2. INSTALLING VIS-AD ***************** Version 1.0 of VIS-AD runs on SGI workstations only. You can get the system by anonymous ftp as follows: % ftp iris.ssec.wisc.edu (or ftp 144.92.108.63) login: anonymous password: name@location ftp> cd pub/visad ftp> ascii ftp> get README ftp> get visad.1.0.tar.Z ftp> bye The file "visad.1.0.tar.Z" is a compressed tar file. To uncompress and untar it, enter the following commands: % uncompress visad.1.0.tar.Z % tar -xvf visad.1.0.tar % rm visad.1.0.tar % cd visad This will create a directory called "visad" that contains the visad demo programs (files named *.v*) and some of their data files. It also contains the following sub- directories: src - contains the source code of visad lui3 - contains the user interface code funcs - contains the source code for external functions (used by the demo programs) In order to install VIS-AD you need to run a "make" that: 1. Builds the "liblui.a" library in the visad/lui3 directory. 2. Builds the "libvisad.a" library in the visad/src directory. 3. Compiles and links the "visad" executable file in the visad directory. 4. Compiles and links executable files in the visad directory for the external functions. In order to run this make, your system must have C and Fortran compilers, and libraries and include files for GL and X. You will also need about 30 MB of disk space. You can adjust a couple system configuration flags before you run the make. These flags are defined in "CONFIG" in the file "src/visad.m". They are: -DMBS=n This controls the amount of memory that VIS-AD will use. "n" is the number of megabytes that VIS-AS will allocate when it starts - all data objects are allocated out of this pool. You can over ride this with the flag "-mbs n" in the visad command line. -DPARALLEL This enables parallelism if your workstation has at least four processors (e.g., is a 340 or an ONYX/4) and makes VIS-AD's displays faster. ******************************************** There is a problem with using this flag with Irix 5. The problem is a bug in the operating system. The most common symptom is that VIS-AD hangs. SGI has fixed this problem and the fix is available as SGI patch000051. If you use the PARALLEL flag on Irix 5 you should get this patch. ******************************************** If your SGI workstation is running Irix 4 then you invoke the make by the command (make sure you are in the "visad" directory"): % make irix4 If your SGI workstation is running Irix 5 then you invoke the make by the command (make sure you are in the "visad" directory"): % make irix5 Odds are that the "make" will be successful. If it is not, the most likely cause is that you do not have the GL Development Option installed on your computer, or that you do not have the C and Fortran compilers installed. If the error messages say something like "file gl/gl.h not found" then you need to install GL. If the error messages say something like "cc command not found" or "f77 command not found" then you need to install the C and Fortran compilers. If the make gets to the link (the long command that begins "f77 vector.c alloc.c almac.c ...") and gives you an error message like "libmcidas4.a: cannot mix Elf and COFF objects" or "libmcidas5.a: bad magic number" then you probably chose the wrong option between "make irix4" and "make irix5". When you log onto your system it will tell you which version of Irix it is running. It should either be Irix version 4.something or Irix version 5.something. You may notice some warning messages about "Incompatible pointer type assignment" when the make compiles the DTM package - you can ignore these warnings - DTM works just fine. If you cannot run this "make" successfully you can get the executable files by anonymous ftp. They are in the files "visad.1.0.irix4.Z" and "visad.1.0.irix5.Z". Use ftp to "get" one of these file (depending on whether you are running Irix 4 or Irix 5) into the "visad" directory. Then "uncompress" and "untar" it using the commands: % uncompress visad.1.0.irix5.Z % tar -xvf visad.1.0.irix5 You may need to mark the files that it creates as executable using commands of the form: % chmod a+x visad If you want to run the demo programs "schl.v", "lamps.v", "fitssmi.v", "radar.v", "ssmi.v" or "dxs.v", they need data files that are not included in the ftp file "visad.1.0.tar.Z". The data files for "schl.v" and "lamps.v" are the 3-D grid files "GR3D0001" and "GR3D0002". In order to run these two demos use ftp to "get" the files "GR3D0001" and "GR3D0002" from the pub/vis5d directory of our ftp site into your "visad" directory (these files are distributed with our VIS-5D system). The data files for the demo programs "fitssmi.v", "radar.v", "ssmi.v" and "dxs.v" are combined in a file "visad.data.tar.Z" in the pub/visad directory of our ftp site. Use ftp to "get" this file into your "visad" directory, then "uncompress" and "untar" it using the commands: % uncompress visad.data.tar.Z % tar -xvf visad.data.tar If you have any questions or problems installing VIS-AD, let us know via email at: whibbard@macc.wisc.edu (Bill Hibbard) and brianp@ssec.wisc.edu (Brian Paul). It will be easier if you can paste the error messages from your "make" into your email to us. And easier for both you and us if you can get your local Unix hacker to take a quick look at the problem before you send an email to us. ************** 3. RUNNING VIS-AD ************** 3.1 Starting VIS-AD The VIS-AD system is invoked with the "visad" command. It takes one parameter, the name of a VIS-AD program file. This filename must end in ".v". You can override the default memory allocation by including the flag "-mbs n" on the command line after the filename (this sets "n" as the number of megabytes that VIS-AD will allocate; you can use any integer between 8 and the size of your swap space). 3.2 Editing program text When VIS-AD is started an array of button icons appears in the upper left corner of the screen and the "VIS-AD Program" window appears below that. If the program file specified in the command line exists, it contents are displayed in the VIS-AD Program window, where they may be edited. Otherwise the window is empty and ready for you to compose a new program (in this case clicking on the SAVE icon creates a new file and saves your program in it). The editor is quite simple and works as follows: Insert text just by typing. Delete text with the Delete key. Move around in the text with the arrow keys and with the Page Up & Page Down keys. You can also move the editor cursor by clicking the mouse at a spot in your text (note however that the left and center mouse buttons have special meanings once your program is compiled). Delete blocks of text by typing Ctrl/b at the start of the block and hitting Delete at the end of the block. Re-insert deleted blocks with the Insert key. Thus hitting Insert immediately after Delete will replace the block where it was. Other copies can be placed anywhere in the text (note that text can be copied between the Program Window and the VIS-AD Display Mapping windows described in Section 3.4). 3.3 Compiling and executing programs, and enabling displays The array of button icons in the upper left corner of the screen looks like this: SAVE QUIT HELP REVERSE COMPILE STOP/GO RE-RUN SINGLE DISPLAY1 DISPLAY2 DISPLAY3 DISPLAY4 These buttons are activated by clicking on them with the mouse. They have the following actions: SAVE saves the contents of the VIS-AD Program window back to the program file, and also saves the contents of the VIS-AD Display Mapping windows (described in Section 3.4) back to their files QUIT exit VIS-AD (icons pop up that allow you to verify that you want to exit) HELP invoke a help window; as you move the mouse over any VIS-AD window or icon, text will appear in the help window describing its functions and how you can control it; the text will also give you some indication of the status of your program REVERSE toggles between black and white backgrounds in the VIS-AD Display Windows (described in Section 3.4) COMPILE compile the program; compiles are practically instantaneous - they are done as soon as the button highlights (if there are no errors); if there are errors the cursor is placed on the approximate location of the first error and an error message is displayed on the bottom line of the program window; clicking COMPILE when it is highlighted, or editing the program text, causes the program to be un-compiled STOP/GO this toggles between executing and not executing the program; this button is highlighted whenever the program is executing; whenever the program is stopped in the middle of execution, the next program line to be executed is highlighted in reverse video in the program text; the program will only execute if the COMPILE button is highlighted RE-RUN restart execution of the program at the top of its "main()" function; this causes STOP/GO to be highlighted SINGLE execute one step of the program DISPLAY1 this button invokes one of four sets of windows for displaying the data objects of your program; each set of windows includes a VIS-AD Display Window where data objects are depicted, a VIS-AD Display Mappings window where users control how data are depicted, and a VIS-AD Display Widgets window for interacting with displays; the first time this button is pressed, the system tries to read a file exists with the same name as the program and with the extension .v1; if it exists this text is loaded into the VIS-AD Display Mappings window and compiled; otherwise the user may enter a set of scalar mappings in the VIS-AD Display Mappings (see Section 6) and then press DISPLAY1 again to compile the text; if there are no errors, DISPLAY1 is highlighted indicating that data may be displayed; otherwise the cursor is placed on the approximate location of the first error and an error message is displayed on the bottom line of the Mappings window; editing the program or editing the scalar mapping text causes the DISPLAY1 button to be un-highlighted; clicking DISPLAY1 while it is highlighted, or twice without any edits, causes all windows associated with the first display to disappear DISPLAY2 identical to DISPLAY1, for the second display DISPLAY3 identical to DISPLAY1, for the third display DISPLAY4 identical to DISPLAY1, for the fourth display Under the top group of buttons, there is a row of buttons in the format: NORMAL CURSOR SLICE These buttons are selected by clicking on them with the mouse. Only one can be selected at a time, so they are called "radio" buttons (since they behave like the buttons on a car radio). These buttons select between alternate functions for the right mouse button, when the mouse cursor is in the 3-D graphics window, as follows: NORMAL the right mouse button controls panning as described in Section 2.5 CURSOR turn on 3-D cursor and coordinate axis labels, and use the right mouse button to move the 3-D cursor in a plane parallel to the screen SLICE the right mouse button is used to grab and move 2-D slices containing iso-level contour lines of scalars mapped to "contour" 3.4 Overview of the display windows When a DISPLAYn button is pressed, three windows appear: The small VIS-AD Display Mapping window in the top center of the screen; users edit scalar mappings in this window to control how data are displayed, as explained in Section 6. The large VIS-AD Display Window in the lower right corner of the screen displays program data objects according to the scalar mappings defined in the Display Mapping window. The VIS-AD Display Widgets window appears in the upper right corner of the screen. These widgets vary according to the scalar mappings, as explained in Section 3.8. A set of scalar mappings can only be compiled if the program is compiled. Note also that the system uses the same editor for the program and for the scalar mappings - you can use Ctrl/b to cut and paste text between these windows. 3.5 Displaying data objects and setting breakpoints We group these two subjects together because they are both controlled by pointing and clicking in the VIS-AD Program window with the mouse. A data object is selected for display by pointing at any occurrence of its name in the program window and clicking the left mouse button (the program must be compiled, of course). This causes all occurrences of this name to be highlighted in reverse video, and depictions of the object to appear in the VIS-AD Display Windows of all currently compiled displays. The object may be de-selected by clicking again with the left mouse button on any occurrence of its name. Several data objects may be selected simultaneously. They will be depicted together in the Display Windows, and their names will be listed along the bottom of the Display Windows. When multiple objects are displayed simultaneously, each object is given a different color. This color is used for the name of the data object at the bottom of the Display Window, and is also used to depict any parts of the data object whose color is not determined by scalar mappings (as described in Section 6). If your program executes while a data object is selected for display, the depiction of the object will change whenever the object is modified. Thus VIS-AD can be used to animate computations. However, display is not synchronized with computation. If values of a data object are updated in a sequence of several statements (or by a loop) the display may show you a data object that is partially composed of new values and partially composed of old values, leading to visual artifacts. For examples, the cubes in the demo programs "cube3d.v" and "cube4d.v" sometimes look a little skewed if they are displayed while you are actively rotating them. If more than one display is currently compiled, clicking the left mouse button on the name of a data object may actually select the object for one display and de- select it for another. Thus it is a good idea to look at the list of selected object names at the bottom of each Display Window to be sure which objects are selected, especially if you are using multiple 3-D display windows. Execution breakpoints may be set in the text of a compiled program, by pointing at a line and clicking the middle mouse button. Lines are marked as breakpoints by an asterix in their first column. Breakpoints are cleared by clicking again on the line with the middle mouse button. 3.6 Changing the viewpoint of the 3-D data display Data objects are depicted inside 3-D boxes in the VIS-AD Display Windows. These 3-D displays are manipulated similarly to the 3-D displays of our VIS-5D system, if you are familiar with it. To rotate the box in 3-D, place the cursor in the Display Window, hold the left button down and drag the mouse. Every time you release and re-press the left button, you change your "grip" on the object. Because you are controlling 3-D rotations with the 2-D mouse, some rotations require a sequence of actions, releasing and re- pressing the left button. You can zoom the 3-D display by holding the center button down and moving the mouse away from you or toward you. When the "Normal" radio button is selected, you can move the 3-D display sideways by holding down the right mouse button and dragging. Note that all of these 3-D box manipulations require that the cursor be in the window containing the 3-D box. It is easiest to practice these manipulations on an empty 3-D display (it will contain a 3-D box), by compiling the display text but not selecting any program data objects for display. 3.7 The 3-D cursor and coordinate axis labels The CURSOR widget button toggles a 3-D cursor in the Display Windows. When the cursor is enabled, pressing the right mouse button causes the 3-D cursor to move to the screen position of the mouse cursor. You can use the mouse to drag the 3-D cursor in a plane parallel to the screen. The 3-D position of the 3-D cursor is indicated by tick marks along the coordinate axes. These tick marks are labeled by the values of scalars mapped to those axes. 3.8 Contour levels, animation, selectors and color maps The display widgets in the upper right corner of the screen control details of the way that objects are displayed. Understanding these requires some understanding of the display frame of reference mappings, as described in Section 6. The top line of widgets consists of these buttons: LABELS RESET ANIMATE STEP RESAMPLE SURFACE WIDE VOLUME These buttons have the following actions: LABELS toggles the use of labels for 2-D contour lines for scalars mapped to "contour" RESET resets the 3-D viewpoint to a default; the left mouse button gives you a view down on the Y-Z plane, the center button a view down on the X-Z plane, and the right button a view down on the X-Y plane ANIMATE toggles animation on and off for scalars mapped to "animation" STEP the right mouse button steps ahead one animation step, the left button steps back one step, and the center button resets to the first step RESAMPLE toggles resampling mode, in which objects like images are resampled to the sample sets (see Sections 5.5 and 6) of x_axis, y_axis and z_axis for display, resulting in smoother displays of data objects SURFACE toggles surface mode, in which objects like images that have an image channel mapped to a vertical dimension (for a terrain display) are displayed as a smooth surface rather than a collection of points WIDE toggles wide graphics mode, in which objects are displayed using wide lines and dots VOLUME currently not implemented - will toggle volume mode, in which dense 3-D objects are rendered as transparent volumes, rather than as collections of points (this will be similar to the volume display implemented by our VIS-5D system) Depending on the mappings in a compiled display frame of reference, the following types of widgets may appear below these four buttons: For scalars mapped to "contour", there will be a slider for selecting a value of the scalar used for calculating an iso-value surface in three dimensions. For scalars mapped to "contour", there will be a text entry widget for selecting a contour interval used for calculating sets of iso-value contour lines in two- dimensional planes. There are also a set of widget buttons for selecting "X-Y", "X-Z" and "Y-Z". These enable slice planes that contain iso-level contour lines on movable planes, that are used to depict slices of 3-D fields mapped to "contour." A slice may be dragged by "grabbing" one of its corners using the right mouse button and dragging it with the mouse cursor. This is enabled by the "SLICE" mode of the radio buttons. For "int" or "real" scalars mapped to "selector", there will be a pair of sliders for selecting the low and high limits for a range of values for the scalar. Sliding these widgets with the center mouse button pressed causes the matching low and high ranges to move together, so that the user can slide a constant interval. For "real2d" or "real3d" scalars mapped to "selector", there will be 2 or 3 pairs of sliders for selecting ranges for each component dimension of the scalar. For "string" scalars mapped to "selector", there will be a text entry widget for selecting a string value for the scalar. For "int" or "real" scalars mapped to "color", there will be a color widget for setting the mapping from scalar values to red, green and blue color components. The map is changed by placing the cursor in the color graph area, pressing any combination of mouse buttons (left for red, center for green and right for blue), and dragging the mouse to trace the color graphs. A small arrow indicates a scalar value along the color wedge may be dragged with the left mouse button. "Real3d" scalars mapped to "color" use true color and no color widget appears for them. ******************** 4. VIS-AD DEMO PROGRAMS ******************** The VIS-AD system is distributed with a number of demos, and running these is by far the easiest way to learn the system. As you learn the system we recommend leaving the help window up and reading it as it changes (you get the help window by clicking on the HELP icon). It will tell you the function of any VIS-AD window or icon just by moving the cursor over it. The help window will also tell you the status of your program. The comments in the demo programs (marked by /* ... */ as in the C language) provide detailed explanations of the programs' data types and computations. ************************************************** * Comments marked by "->" are especially useful. * * They tell you how to run the demos and * * how to visualize the data in the demos. * * * * - - - - - - - - - - - - - - - * * * * The comments in the demos tell you to click on * * the names of data objects in the program text * * in order to display the data objects. Do NOT * * click on the names in the comments - rather * * find the names in statements in the program * * (you may need to PAGE UP or PAGE DOWN to find * * them) and click on those names (this is like * * clicking on words to follow hypertext links). * ************************************************** If you get creative with the demos (we encourage it), by editing the display mapping text or the program text, and you run into problems, consult the troubleshooting guide in Section 7.6. These are the demo programs distributed with VIS-AD. We have listed them in an order appropriate for a tutorial on VIS-AD. sort.v This is the simplest demo and the one you should run first. It implements a bubble sort algorithm. julia.v This demo allows you to interactively play with Julia sets. It is a simple example of VIS-AD's computational steering capability. juliafam.v This demo lets you define a family of Julia sets and visualize them in three dimensions. chaos.v This demo lets you visualize the transition from non-chaos to chaos in a family of one-dimensional iterated functions. lorenz.v This demo simulates a simple but chaotic 2-D atmosphere using Lorenz's equations. It lets you visualize the relation between a 2-D physical space and a 3-D phase space. cube3d.v This demo will help you understand the geometry of the 4-D cube in the cube4d.v demo, by showing you the analogous 2-D projections of a 3-D cube. cube4d.v This demo allows you to interactively explore the geometry of a 4-D cube by visualizing its 3-D projections. When you visualize interactive rotations of the cube it will occasionally look a bit skewed - this is because display of the cube is not synchronized with matrix multiplications, so the cube will sometimes be displayed with some coordinates rotated by the latest matrix and some coordinates rotated by the previous matrix. life.v This demo implements Conway's life, a 2-D array of cellular automata that exhibit remarkably complex behavior; this demo provides a good example of how complex calculations may be implemented using VIS-AD's vector arithmetic and intrinsic functions cloud.v This demo implements an algorithm for discriminating clouds in time sequences of infrared and visible channel images by the GOES satellite. This is a complex algorithm that provides a good example of the utility of visualizing the intermediate computations of an algorithm. fit.v This demo illustrates the VIS-AD intrinsic functions for least squares fitting, using GOES satellite data. fit3d.v This demo illustrates the VIS-AD intrinsic functions for least squares fitting with multiple independent variables, using GOES satellite data. fitinter.v This demo illustrates the ideas of data modeling, by comparing a least squares fit with a function controlled interactively by the user. cumulus.v This demo reads visible and infrared GOES satellite images and uses them to discriminate cumulus clouds. earth.v This demo reads visible and infrared GOES satellite images, Earth topography, a vegetation index and a precipitation index, and provides a variety of ways of comparing these different data. schl.v This demo illustrates the capability of VIS-AD to mimic the functions of our VIS-5D system, by application to Bob Schlesinger's cloud model data. lamps.v This demo illustrates the capability of VIS-AD to mimic the functions of our VIS-5D system, by application to LAMPS model data. fitssmi.v This demo reads a three channel ssm/i image and computes a least squares fit of one channel as a function of the other two. These data may be visualized in a variety of ways. radar.v This demo reads data generated by a volumetric radar and lets you visualize it in three dimensions. This demo is an example of how users read their data into VIS-AD by writing external functions. ssmi.v This demo reads an image generated by a polar orbiting satellite and displays at as a continuous "peel" around the Earth. dxs.v This demo reads X-ray events gathered by the Diffuse X-ray Spectrometer on a Space Shuttle flight in January 1993. The demo computes and displays various histograms of these data, and lets you interactively select X-ray events. These data may be displayed in many different ways. NOTE: the data files required by the demo programs "schl.v", "lamps.v", "fitssmi.v", "radar.v", "ssmi.v" and "dxs.v" are not included with the ftp file "visad.1.0.tar.Z". The data files for "schl.v" and "lamps.v" are the 3-D grid files "GR3D0001" and "GR3D0002". In order to run these two demos you will need to get these files from our ftp site in the pub/vis5d directory (these files are distributed with our VIS-5D system). The other data files are combined in a file "visad.data.tar.Z" in pub/visad. In order to run the four demos "fitssmi.v", "radar.v", "ssmi.v" and "dxs.v" you will need to get this file and uncompress and untar it (as described in Section 2). Here we provide descriptions for how to run two of the demos. There are detailed comments in all of the demos explaining how to run them. 4.1 Running the sort.v demo To run the bubble sort algorithm demo, type the command: % visad sort.v Then click on the COMPILE button and the STOP/GO button to compile and execute the bubble sort. The program will loop forever (until you click on STOP/GO again or set a breakpoint). If you put the cursor in the program text window, you can move around in the text with the arrow keys and the PAGE UP and PAGE DOWN keys. Click on the DISPLAY1 button to create a Display Window for visualizing the data objects of sort.v. Inspecting the program, you will see that the main() function (which is at the end of the sort.v file) is an infinite loop. The "temperatures" array contains the values for sorting. The main() function calls the set() function to initialize "temperatures" to random values, then calls the sort() function to sort the values in "temperatures". With the program running and DISPLAY1 enabled, clicking the left mouse button on the "temperatures" data object should display an animation of the array being sorted. Because the actual parameter "temperatures" occurs in the call to sort() as a simple name it is passed by reference and can be selected for display in either the main() function or the sort() function. Click on the STOP/GO button while the array is partially sorted will halt the program somewhere in the sort() function, causing a program line to be highlighted. Click the left mouse button on the "outer", "inner" and "swap" names in the sort() function. This will cause 3 small spheres to be displayed along the x and y axes of the display. "outer" and "inner" are used by sort as indices into the "temperatures" array, and "swap" is a temporary used to exchange values in the "temperatures" array. By repeatedly clicking a mouse button on the SINGLE button, you can watch the sort happen one step at a time. Now click again on "inner" and "swap", so that only "temperatures" and "outer" are selected for display, and click on the STOP/GO button to start the program running again. Now you can see the "temperatures" array being sorted, and the sphere displaying the "outer" value tracing the decreasing upper limit of unsorted values. 4.2 Running the cloud.v demo The cloud.v program is a lot more complex than the sort.v program. To run it, type the command: % visad cloud.v Click on the COMPILE button, the STOP/GO button and the DISPLAY1 button. The program will run until it reaches the "pause" statement near the bottom of the text. The cloud.v program runs for a minute or two, and is organized to save most of its intermediate calculations. You will notice that until the program stops at the pause statements, the interaction with VIS-AD is a little sluggish. We hope to improve this in later versions of VIS-AD, and once SGI corrects the concurrency bug in Irix 5. The cloud.v program starts with a large number of type definitions and contains only the single function main(). The main() function is organized as an infinite loop: while (1 == 1) { ... pause; } Thus clicking the STOP/GO button after the "pause" will re- run the algorithm. The cloud.v program reads a infrared and visible channels of GOES satellite data, for each of four time steps, into the "ir" and "vis" data objects. These images are read from "McIDAS" area files using the "read_area" function. Clicking the left mouse on the names of these data objects will generate displays of them. To see the "vis" data object, it is necessary to rotate the 3-D box of DISPLAY1, either using mouse drags or clicking on the RESET icon with the center mouse button. After the program reads the data, the loop: foreach (tm) { ... }; processes each time step in sequence. The "lowpass" function is used to lowpass filter the infrared data. This filter operation is controlled by the "filtsize" parameter returned by a call to the "slider" function. "slider" is an intrinsic function that generates a slider icon on and that returns the value set by the user on the icon. If "filtsize" = 0 then the filter does nothing. After the call to "lowpass", the image data objects are partitioned into regions in the "irs" and "viss" data objects. The "remap" intrinsic function provides a general capability to resample arrays and is used to partition the images. The infrared and visible data in "irs" and "viss" are merged into the multi-channel "visirs" data object. Note that the "irs", "viss" and "visirs" data objects contain arrays of images indexed by both "image_region" and "time". Clicking on "visirs" will display the infrared and visible data together, and adjusting the "image_region low" and "image_region hi" slider icons allows single regions to be displayed. The ANIMATE and STEP buttons (of the VIS-AD Display Widgets window) can be used to animate time steps. Clicking the center mouse button on STEP resets to the first time step. The "make_histogram" function is used to calculate histograms of the "ir_radiance" values in each "image_region". Clicking on "hists" will display these histograms over all the selected "image_regions". The data objects "tlow", "thi" and "varlimit" are scalars, and are not indexed by "time" or by "image_region". Thus these data objects can only be usefully displayed as the algorithm iterates over each time step and region. The other data objects in the program are indexed by "time" and by "image_region" and can be usefully displayed when the program is paused. Some of these data objects are: vars an image containing the spatial "variance" of the infrared data varsel the same as the "vars" data object but restricted to low "variance" pixels - "variance" values greater than "varlimit" are replaced by MISSING, which is invisible in the display histcat a histogram of "ir_radiances" restricted to those pixels with low variance; displaying "hists" and "histcat" together illustrates the motive behind the variance restriction - to accentuate the clustering of the histograms histlowpass a lowpass filtered version of "histcat" histcluster "histlowpass" restricted to "ir_radiances" in clusters; the "count" values in the "histcluster" array have been replaced by MISSING for those "ir_radiance" values not in clusters histlow "histcluster" restricted to clusters in a low "ir_radiance" range histhi "histcluster" restricted to clusters in a high "ir_radiance" range lowsel the "irs" image restricted to pixels with "ir_radiance" values in clusters of "histlow" hisel the "irs" image restricted to pixels with "ir_radiance" values in clusters of "histhi" irslope an image containing values giving a "texture" measure of the ir data slopesel the same as the "irslope" data object but restricted to pixels with low "texture" values; high "texture" values are replaced by MISSING vissel the same as the "viss" data object but restricted to pixels with high "vis_radiance" values; low "vis_radiance" values are replaced by MISSING lowandsel the same as the "lowsel" data object but restricted to pixels that are not MISSING in "lowsel", "slopesel" and "vissel" visirsels the final selected cloud pixels; pixels judged by the algorithm to not be in clouds are set to MISSING; comparing the "visirs" and "visirsels" data objects in display number 1 shows the overall effect of the cloud discrimination algorithm vvits an image with "vis_radiance", "ir_radiance", "variance" and "texture" values at each pixel; this data object is interesting in all four displays; this data object appears as a colored 3-D scatter diagram in display number 3 (it is useful to click on the WIDE icon to make the scattered points easier to see, and to play with the color map for "texture"). Display number 1 is good for tracing through the calculation of all these data objects, showing the steps of the cloud discrimination algorithm. In particular, see if you can find some places in the "visirsels" data object where the algorithm has made mistakes. Then trace back through the data objects that lead to "visirsels" to see if you can find out the cause of the mistakes. Display number 3 shows image data objects as scatter diagrams. These are most interesting with the "image_region low" and "image_region hi" slider icons set to show a single region (sliding either icon with the center mouse button pressed slides the two together at a constant interval). The "visirs" and "visirsels" data objects are displayed as 2-D scatter diagrams of "ir_radiance" versus "vis_radiance" in the X-Z plane, and it may be necessary to rotate the 3-D box (or click on the RESET icon with the center mouse button) to see the X-Z plane. The "vvits" object is displayed as a colored three- dimensional scatter diagram in display number 3. The "visirs" data object is interesting in all four displays. In display 1 it appears as a terrain of "ir_radiances" colored by "vis_radiances". Clicking on the SURFACE icon will make the terrain into a smooth surface. In display 2 the "visirs" data object appears as a 2-D image. Use the three color icons to re-draw the color maps so that "ir_radiance" is a red wedge (hint - linear over the range of ir_radiances from 180 to 240) and "vis_radiance" is a green wedge. If your workstation does not have texture mapping (i.e., the 2-D image appears as a set of dots) clicking on the WIDE icon may help. In display 3 the "visirs" data object appears as a 2-D scatter diagram. In display 4 the "visirs" data object appears as a stack of four images with pixels colored according to their "vis_radiance". The icons for this display include a pair of sliders for selecting a range of "ir_radiance" values. It may be useful to set "ir_radiance low" to about 200 and "ir_radiance hi" to about 210 or 220. Perhaps most interesting of all, you can edit the mappings in the four Display Mapping text windows to define other ways of looking at these data. It is easy to design lousy sets of mappings, but if you think about the data you may come up with some other interesting ways of looking at the data objects of the cloud.v demo. ******************************* 5. THE VIS-AD PROGRAMMING LANGUAGE ******************************* The VIS-AD programming language is similar to C in many ways. This description focuses on the differences. The easiest and quickest way to understand the VIS-AD programming language is to look at the demo programs (discussed in Section 4). 5.1 Basic structure of a VIS-AD program A VIS-AD program begins with a set of type definitions, and all data objects must be declared using these types rather than generic types such as "int" or "float". The type definitions are followed by a (possibly empty) set of global data object declarations, a (possibly empty) set of external function declarations, and a set of function definitions. Version 1.0 of VIS-AD uses a simple one-pass Yacc compiler so all functions must be defined before they are referenced, and the "main" function must be defined last. 5.2 Type definitions Types may be scalar or complex. Scalar types have the syntax SCALAR_TYPE ::= type NAME = BASIC_TYPE ; or SCALAR_TYPE ::= type NAME = BASIC_TYPE , sample = SAMPLE_FUNCTION ; where NAME is an identifier and BASIC_TYPE is one of "real", "real2d", "real3d", "int" or "string". SAMPLE_FUNCTION is an invocation of the intrinsic functions described in Section 8.6 for determining the default sample set for values of this type. Complex types are constructed as arbitrarily nested arrays and structures (tuples) from scalar type names. The syntax of complex type definitions is COMPLEX_TYPE ::= type NAME = TYPE_SPEC ; TYPE_SPEC ::= array [ SCALAR_NAME ] of TYPE_SPEC TYPE_SPEC ::= structure { TUPLE_LIST } TYPE_SPEC ::= SCALAR_OR_COMPLEX_NAME TUPLE_LIST ::= . ELEMENT_NAME = TYPE_SPEC ; TUPLE_LIST ::= TUPLE_LIST . ELEMENT_NAME = TYPE_SPEC ; where NAME and ELEMENT_NAME are identifiers, SCALAR_NAME is the name of a previously defined scalar type, and SCALAR_OR_COMPLEX_NAME is the name of a previously defined scalar or complex type. Data objects and functions are declared with the names of these user-defined data types, rather than with generic types like "int" or "real". The "cloud.v" program offers numerous examples of type definitions and object declarations. It includes the scalar type definitions type ir_radiance = real, sample = linear_set(0.0, 254.0, 255); type vis_radiance = real, sample = linear_set(0.0, 254.0, 255); type pixel_location = real2d, sample = product_set(linear_set(0.0, 255.0, 8), linear_set(0.0, 255.0, 8)); type image_region = int, sample = integer_set(1, 16, 16); type count = int, sample = integer_set(-100, 2000, 2101); The "cloud.v" program uses "ir_radiance" and "vis_radiance" for infrared and visible radiance values of GOES satellite data, and "pixel_location" for a pair of values for the 2-D locations of pixels. The intrinsic functions "linear_set", "integer_set" and "product_set", described in Section 8.6, provide default information about the finite samplings of values of these scalar types (sample sets are discussed in Section 5.3). The "cloud.v" program also includes the complex type definitions type ir_image = array [pixel_location] of ir_radiance; type visir = array [pixel_location] of structure { .v_ir = ir_radiance; .v_vis = vis_radiance; }; type visir_set = array [image_region] of visir; type histogram = array [ir_radiance] of count; type histogram_set = array [image_region] of structure { .h_loc = pixel_location; .h_hist = histogram; }; Here "ir_image" is an array of "ir_radiance" values indexed by a set of "pixel_location" values, and "visir" is an array of pixel structures each containing an "ir_radiance" and an "vis_radiance" value. The "cloud.v" program partitions images into regions, and the "image_region" scalar is used as an index into arrays of regions. Thus "visir_set" is an image partitioned into regions. The "histogram" type is an array that associates a "count" with each "ir_radiance", and the "histogram_set" type associates a "histogram" and a "pixel_location" with each "image_region". Components of data objects of these types are accessed using standard C language syntax. Data objects are declared by the statements: histogram_set hs; pixel_location loc; image_region r; ir_radiance ir; visir_set vira, virb; Then an entire "visir" image is copied by the statement: vira[r] = virb[r]; A histogram count can be incremented by the following: ir = vira[r][loc].v_ir; hs[r].h_hist[ir] = hs[r].h_hist[ir} + 1; The form of VIS-AD type definitions requires and encourages users to provide application specific information about the use of types. Thus complex types are defined in terms of the user-defined scalar types, rather than directly in terms of the generic types "int", "real", "real2d", "real3d" and "string". In particular, array indices are defined as scalar types. This application specific information provides the reference for the scalar mappings (defined in Section 6) for displaying data in an application specific frame of reference. 5.3 Management of numerical sample sets and of MISSING data In a C program, the set of possible values of a variable is generally determined by the machine architecture. For example, an "int" will take integer values between -2**31 and 2**31 -1, and a "float" will take values from a more complex but well-defined set. These sets are finite samplings of the infinite sets of integers and real numbers. In VIS-AD, these samplings are managed explicitly. VIS-AD supports sample sets that are finite arithmetic progressions of values for "real" and "integer" types (via the intrinsic functions "linear_set" and "integer_set"). VIS-AD supports cross products of these finite arithmetic progressions for "real2d" and "real3d" types (via the intrinsic function "product_set"). VIS-AD also supports more complex sample sets for "real2d" scalar types. These are grids of points laid out according to map projections like Mercator of polar stereographic, as well as according to navigation functions for commonly used environmental satellites. These complex samplings are inherited from McIDAS area files (image files) via the intrinsic functions "read_nav_area" and "nav_set". Sample sets for "string" types are defined as all strings of a given length, using the intrinsic function "string_set". Samplings of "real" scalars can be set using the intrinsic function "linear_set", which takes three arguments. For example, if "loc" is a "real2d" scalar, then the statement: sample(ar[]) = linear_set(low, hi, count); causes the sampling of index values of "ar" to be the arithmetic progression [1] low+i*(hi-low)/(count-1) for i = 0, ..., count - 1 The intrinsic function "integer_set" is similar to "linear_set", but is constrained to an arithmetic progression of integer values. It is useful for "int" scalars. Samplings of "real2d" or "real3d" scalars can be set using the intrinsic function "product_set", which takes two or three arguments that should each be calls to "linear_set". For example, if "loc" is a "real2d" scalar, then the statement: sample(loc) = product_set(linear_set(low1, hi1, count1), linear_set(low2, hi2, count2)); causes the samplings for values of the "loc" scalar to be in the set [2] ( low1+i1*(hi1-low1)/(count1-1), low2+i2*(hi2-low2)/(count2-1) ) for i1 = 0, ..., count1 - 1 and for i2 = 0, ..., count2 - 1 Samplings of "real2d" scalars can also be set according to the McIDAS navigation for an area, so that image or grid data can be indexed by (latitude, longitude) pairs, rather than just (line, element) or (row, column) pairs. If "ar" is a 2-D image array, then the statement: sample(ar) = nav_set(area, line, element, nlines, nelems); sets the sampling of the index of the "ar" array according to the navigation of McIDAS area number "area". The "upper left" element of the array is at the latitude and longitude of the element of the McIDAS area index by "line" and "element". The samples of the "ar" array are at the same resolution as the McIDAS area, and the number of samples in the array is "nlines" by "nelems". Note that the "nav_set" function sets navigation that is consistent with the navigation set by the "read_nav_area" intrinsic function. If no sample set is defined for a "real" scalar, then 32-bit floating point numbers are used for its values. Unless you have a good reason to define a sample set for a "real" scalar (see Section 7.2 for a discussion of the issues) it is a good idea to not define a samples set just so that floating point values will be used. VIS-AD allows data objects of any type to take the special value MISSING (i.e., the value is undefined). Any sub-object of a data object may also take the MISSING value. In fact, every data object is initialized to MISSING when a program starts executing. MISSING may be used as a constant expression matching any type, as in the statements: a = MISSING; and if (a == MISSING) { ... }; Arrays are interpreted as finite samplings of functions. Thus values assigned to array elements are resampled to the finite sampling specified for the array values, and values outside the range of the sampling are set to MISSING. For example, in the statement: ar1[x] = ar2[y]; the value of "x" is resampled to the closest index value of "ar1", "y" is resampled to the closest index value of "ar2", and that value (call it "w") is resampled to the sample set of the values of "ar1" (assuming the range of "ar1" is a scalar). If "y" falls outside the range of index values of "ar2" then "ar2[y]" evaluates to MISSING. If "x" falls outside the range of index values of "ar1" then no assignment is made. If "w" falls outside the range of values of "ar1", then "ar1[x]" is set to MISSING. This can lead to confusing results when you think of the VIS_AD programming language like other languages. However, if you think of arrays as finite samplings of functions then it makes sense. Scalar data objects are basically just treated as floating point numbers - they have sample sets, but an assignment to a scalar just defines a sample set appropriate for the value being assigned. You can explicitly define sample sets for scalars, and this is useful for "foreach" statements (described in Section 5.6). Sample sets may be defined in three different places in a VIS-AD program: 1. Default sample sets can be defined in scalar type definitions (see Section 5.2). These default sample sets are used for all occurrences of the scalar type (i.e., in array indexes, in array values, in scalar data objects, and in the display mappings discussed in Section 6) unless over-ridden by either of the following. 2. Sample sets may be defined at run time in a program (as discussed in Section 5.5). 3. Sample sets may be defined in display mappings from scalar types to display scalar types (discussed in Section 6). 5.4 Accessing components of "real2d" & "real3d" values In general, the syntax of executable statements in VIS-AD is a subset of C syntax. However, the expressions v{1}, v{2} and v{3} are used for the components of a "real2d" or "real3d" value v, and the expressions {a, b} and {a, b, c} are used to build "real2d" and "real3d" values from "int" or "real" components. See the "cloud.v" and "fitssmi.v" demo programs for examples of how to use these notations. 5.5 Setting the sampling of values at run time Many VIS-AD programs just define default sample sets as part of scalar type definitions and use those throughout their computations. Other programs inherit sample sets when they read data from files (for example, the "read_area", "read_nav_area" and "read_grid3d" intrinsic functions get sample sets from the McIDAS data files that they read) and use those for computations. However, it is sometimes useful to adjust the samplings of variables explicitly during a computation, and VIS-AD includes syntax for setting samplings at run time. If "ar" is the name of an array, then the statement sample(ar) = linear_set(low, hi, count); defines the sample set of index values of the array "ar" as an arithmetic progression. It also initializes the values in the "ar" array to MISSING. If "ar" is an array of scalars, then the statement sample(ar[]) = linear_set(low, hi, count); defines the sample set for values of the "ar" array as an arithmetic progression. If "ar" is an array of tuples of scalars (such as the "visir" type defined in Section 5.2), then the statement sample(ar[].element_name) = linear_set(low, hi, count); defines the sample set for the .element_name component values of the "ar" array as an arithmetic progression. The sampling of one data object can be explicitly copied into another using the intrinsic function "set_of". For example: sample(ar) = set_of(loc); And of course, assignments always bring sample sets with them. For example, if "ar1" and "ar2" are arrays, then the statement: ar1 = ar2; sets the index of "ar1" to have the same sampling as "ar2" (although values in the "ar2" array will be resampled to the sample set of the value types of "ar1", if they are different from the sample set of the value types of "ar2"). If no default sample set is defined for a scalar type, and the sample set for a value of that type is set at run time by a statement "sample(...) = " or by a vector assignment to an array (see Sections 5.10 and 5.11) then the default sample set is set at run time, and will be used as the default from that point on. The "cloud.v" demo program includes many examples of defining sample sets explicitly at run time. 5.6 Loops over samplings of values The program segment: foreach (v) { ... } is a loop that executes the statement inside the braces once for each value of the current sampling of the scalar object "v", with the object "v" iterating through those values. The syntax: foreach (v in ar) { ... } is similar, where "ar" is an array and "v" is a scalar whose type matches the index of "ar" (that is, "v" only needs to match the basic type of the index of "ar", as explained in the Section 5.7). The statements in braces are executed once for each index of the current "ar" array object, with the object "v" iterating through those values. See the "cloud.v", "cube3d.v" and "cube4d.v" demo programs for examples of how to use the "foreach" statement. The VIS-AD programming language also supports a "for" statement with a syntax and meaning similar to the "for" statement of the C language. 5.7 Type checking The VIS-AD compiler checks type matching in assignments, between actual and formal parameters of functions, for returned values of functions, between operands of binary arithmetic operations, and in "foreach" statements. However, at the scalar level types need only match as far as dimensionality. Thus any "int" or "real" matches any "int" or "real", any "real2d" matches any "real2d", any "real3d" matches and "real3d", and any "string" matches any "string". 5.8 Function calls Expressions can include calls to functions with arguments. Functions may be internal, intrinsic or external. Internal functions are defined as part of a VIS-AD program, much as functions are defined in a C program (the "sort" function in demo program "sort.v" is an example of an internal function). Intrinsic functions are built into the language, much like the "printf" function in C and the "MAX" function in Fortran. External functions are implemented in either C or Fortran, and provide a way to link VIS-AD programs to software written by users in either of these compiled languages. VIS-AD passes arguments to internal and intrinsic functions using call by reference. Thus such functions may modify their formal parameters. However, the actual parameters in the calling statement are only modified if they are simple object names (even an expression of the form "ar[v]" is not a simple object name - if it is used as a function argument then the functions cannot modify any value in the "ar" array). External functions may not modify the values of their arguments - but they may return a data object (it must have the data type declared for the function - see Section 9). 5.9 The slider function VIS-AD is set up to make it easy to edit a program and re-compile and re-run it in order to experiment with the program logic. However, the VIS-AD language also includes an intrinsic function named "slider", which returns a value set by the user on a slider widget. This lets users interactively steer computations. Users can user calls to "slider" to experiment with numerical parameters of an algorithm without re-compiling. The function call: slider("name", low, high, default) causes a slider widget to be created when the program is compiled, labeled with the string "name", with "low" and "high" as the range of values on the widget, and with "default" as the initial setting of the slider. If a program contains at least one call to the "slider" function, a window containing the corresponding widgets appears in the lower left corner of the screen when the program is compiled. While the "name" in the slider call must be a constant, "low", "high" and "default" can either be constants or variables. If they are variables, the slider will appear with default values after compilation, but these will be changed when the slider function is called during program execution. See the "julia.v" and "dxs.v" demo programs for examples of how to use the "slider" function. 5.10 Vector expressions Entire arrays may be combined arithmetically using vector arithmetic statements. If "a", "b" and "c" are all arrays of scalars with index types of the same dimension, then the statement: c = a * b; resamples the index values of "b" to the sample set of the index of "a", adds values of "a" and "b" element by element, and puts the result in "c". ************************************************** Note that vector expressions resample the index values of all arrays to the sampling of the first array encountered in the expression - this permits you to perform arithmetic on images generated by satellites at different Earth perspectives - but you should be aware that it is happening. ************************************************** Vector arithmetic can also be used for complex expressions like: c = 2.0 * (a - 1.0) / b; c = sqrt(a * a + b * b); c = max(a, b); Note that mathematical intrinsic functions can be applied to vectors. Because the VIS-AD programming language is interpreted, vector expressions can be important for writing program that execute quickly. See the "cube4d.v" demo program for examples of how to use vector expressions. 5.11 Accessing elements in arrays of tuples The VIS-AD programming language supports special syntax for accessing components of arrays of tuples. For example, given the multi-band image type (and its scalars) the we defined in Section 5.2: type ir_radiance = real; type vis_radiance = real; type pixel_location = real2d; type visir = array [pixel_location] of structure { .v_ir = ir_radiance; .v_vis = vis_radiance; }; and given data objects with this type: visir ima, imb, imc; then the following statements merge the "vis_radiance" values from "imb" with the "ir_radiance" values from "imc": ima = MISSING; ima[].v_vis = imb[].v_vis; ima[].v_ir = imc[].v_ir; The first statement sets "ima" to MISSING so that "ima" will inherit the index sample set of "imb" (we could have also used: sample(ima) = set_of(imb); to do this explicitly. Unless "ima" is MISSING, a statement like: ima[].v_vis = imb[].v_vis; will resample the index values of "imb" to the existing index values of "ima" (this is necessary in order that all tuple elements of "ima" have the same index sampling). The real point is that, since this is an interpreted language, the statement: ima[].v_vis = imb[].v_vis; is executed very quickly compared to the loop: foreach (loc in ima) { ima[loc].v_vis = imb[loc].v_vis; } In general, the syntax: array_name[].element_name assumes that "array_name" is an array of tuples, and that "element_name" is the name of one element of those tuples. This expression represents an array object with the same index as "array_name". The objects in this array have the same type as the selected element of array_name. This syntax can be used in expressions, or on the left side of an assignment statement. Because the VIS-AD programming language is interpreted, this notation can be important for writing program that execute quickly. See the "cube4d.v" demo program for examples of how to use this notation. *********************** 6. VIS-AD DISPLAY MAPPINGS *********************** A display frame of reference is defined by a set of mappings from the scalar types defined in a program, to the following set of "display scalars": display scalar dimension of mapped scalar -------------- -------------------------- x_axis 1 y_axis 1 z_axis 1 xy_plane 2 xz_plane 2 yz_plane 2 xyz_volume 3 color 1 or 3 contour 1 animation 1 selector 1, 2, 3 or string The dimension of a scalar (e.g., "int" and "real" are 1-D, "real2d" is 2-D and "real3d" is 3-D) must match the dimension of the display scalar it is mapped to, as detailed in the table above. Values of scalars mapped to "._axis", ".._plane" or "xyz_volume" are used to determine locations of voxels within the 3-D box. Values of scalars mapped to "color" determine colors of voxels, and values of scalars mapped to "contour" are used to generate iso-level surfaces and lines through voxel space. Values of scalars mapped to "animation" are used to distribute voxels over an animation sequence. Values of scalars mapped to "selector" are used to select voxels for display, based on user selected ranges of values for scalars mapped to "selector". The DISPLAY1 and DISPLAY3 frames of reference defined for the "cloud.v" program, found in the files "cloud.v1" and "cloud.v3", are good examples of display frames of reference. DISPLAY1: map pixel_location to xz_plane; map ir_radiance to y_axis; map vis_radiance to color; map variance to y_axis, sample = linear_set(0.0, 6.0, 256); map texture to selector; map time to animation; map image_region to selector; map count to x_axis, sample = integer_set(-2000, 2000, 4001); DISPLAY3: map ir_radiance to x_axis, sample = linear_set(180.0, 244.0, 256); map vis_radiance to z_axis; map variance to y_axis, sample = linear_set(0.0, 6.0, 256); map texture to color; map time to animation; map image_region to selector; map count to y_axis; The range of values of a scalar's sample set is mapped to the full scale of a display scalar. A scalar's sample set may be specified by an intrinsic function as part of the mapping text. If none is, the default sample set specified in the scalar's type definition is used. If no default sample set was defined, the sample set used for the first data object created with the scalar is used (you will sometimes see the Display icons change while a program is executing - this happens when a sample set is defined for a scalar that had no default, and that scalar is mapped in the display). The DISPLAY1 frame of reference displays of data objects of the "ir_image" type as terrains, mapping "pixel_location" to the "xz_plane" and "ir_radiance" to the "y_axis". Objects of the "visir" type are displayed as colored terrains, with their pixels colored according to their "vis_radiance" values. The DISPLAY1 frame of reference generates a pair of widgets for selecting a range of "image_region" values, allowing the user to select a subset of regions for display for objects of the "visir_set_sequence" type. The DISPLAY3 frame of reference displays data objects of the "visir_set_sequence" type as 2-D scatter diagrams. "pixel_location" is not mapped, so pixel location information is ignored. "ir_radiance" and "vis_radiance" are mapped to the "x_axis" and "z_axis", so that pixel values are displayed as a scatter diagram of "ir_radiance" versus "vis_radiance" (you need to rotate the 3-D display box to see the x-z plane). Objects of the "vvit_set_sequence" type are displayed as colored 3-D scatter diagrams. ************************************** 7. DEVELOPING YOUR OWN VIS-AD APPLICATION ************************************** The easiest way (by far) to develop a new VIS-AD application is to modify an existing application, or to mix together parts of several existing applications. We hope that one or more of our demo programs can serve as a model to help you get started. In order to develop an application, you need to: Define data types. Manage sample sets. Get data into your application. Organize your computation. Define displays. Figure out why it doesn't work (troubleshooting). We cover these topics in the subsections below. If you develop an application that is interesting or novel, we'd like to hear about it by email (whibbard@macc.wisc.edu). 7.1 Defining data types See Section 5.2 for a general discussion of data types. Define scalar types to represent the primitive mathematical variables of the application. Define arrays and tuples for the collections of data in the application. Arrays often represent functions in the mathematical model of your application (e.g., temperature is a function of space). If your application includes multi-dimensional arrays, you can either define them using nested array, such as: type latitude = real; type longitude = real; type radiance = real; type image = array [latitude] of array [longitude] of radiance; Or you can define using "real2d" or "real3d" indices, such as: type lat_lon = real2d; type radiance = real; type image = array [lat_lon] of radiance; Using "real2d" and "real3d" indexes simplify access to arrays. They also allow non-Cartesian sample sets for arrays. However, when you define displays you can only map "real2d" scalars to "xy_plane", "xz_plane", "yz_plane" or "selector", whereas the "real" indexes of nested arrays can be mapped independently and to a greater variety of display scalars. 7.2 Managing sample sets VIS-AD sample sets are novel compared to ordinary scientific programming languages like Fortran and C, so you should give them special attention when you design an application. See Section 5.3 for a general description of sample sets. For every scalar type defined in your program you need to decide whether to define a default scalar set in the type definitions. You also need to decide when to set sample sets of data objects at run time in your program, and whether to explicitly define sample sets in display mappings. The main issues are: 1. Sample sets must be defined for the index of every array data object, either as the default of the index type or at run time. 2. Samples sets of array values can be used to reduce the memory needed to store arrays. If you define a sample set of 255 or fewer values (leaving one value for a MISSING data code), then these values are stored in one byte. If you define a sample set of between 256 and 65535 values, then these values are stored in two bytes. Note that array index values are not stored explicitly (they are implicit in the location of data in the array, as with other programming language) so there is no need to reduce their storage space. However, array values (e.g., radiances in images) are stored explicitly and this issue applies. 3. If no sample set is defined for values of a real type then floating point values are used which are relatively robust in computation. However, if a sample set is defined then values outside the range of that sample set cannot be represented except by MISSING, which can cause confusion. For example, if you add radiances (with a sample set of 255 values) and store the result back as a radiance then that radiance will be MISSING if the sum is out of range. 4. If no default sample set is defined for a scalar type, then the first time you define a sample set for a value of this type at run time, that sample set will become the default sample set for the scalar type. This can be very useful for programs that read data from files using intrinsic functions (see Section 8.2) - the sample sets that come from the file then become the default and are used for other related data objects (unless explicitly over-ridden) and display mappings. See the discussion of demo programs below. 5. If no sample set is defined for a scalar type (either explicitly or implicitly when data are read from files - see the previous point), then you need to define a sample set with all display mappings of that type. The demo programs illustrate several different approaches to managing sample sets. In the "sort.v" program, no default sample sets are defined with the type definitions: type time = real; type temperature = real; type temperature_series = array [time] of temperature; However, the "main" function defines sample sets at run time as follows: temperature_series temps; sample(temps) = linear_set(0.0, 80.0, 81); sample(temps[]) = linear_set(0.0, 100.0, 101); The index of "temps" has type "time" so these set the default sample set of "time" to {0.0, 1.0, ..., 80.0}, and the range type of "temps" has type "temperature" so these set the default sample set of "temperature" to {0.0, 1.0, ..., 100.0}. The display mappings in DISPLAY1 are: map time to x_axis; map temperature to y_axis; and the defaults are used for these, resulting in reasonable displays of arrays being sorted. The demo programs "earth.v" and "cumulus.v" read 2-D image arrays from McIDAS area files via the intrinsic function "read_nav_area". These image arrays have a variety of different types, but they are all indexed by the "real2d" scalar "earth_location". No default sample set is defined for "earth_location", so the first call to "read_nav_area" sets its default sample set (to the set of Earth longitudes and latitudes of the image's pixels). However, succeeding calls to "read_nav_area" over-ride this default and set the samplings of the indexes of image arrays according to the Earth navigations of the images they read. Thus each image array has an index sample set that defines how its pixels are located in earth longitude and latitude. This permits these images to be co- registered in displays, and co-registered in arithmetical operations. The "earth.v" and "cumulus.v" programs include the following statements: get_sample(vis_org, sizes, low, hi); lon1 = low[0]; lon2 = hi[0]; lat1 = low[1]; lat2 = hi[1]; They get the longitude and latitude ranges of the index sample set of the "vis_org" image (a GOES satellite visible-channel image). These ranges are then referenced in the scalar mappings of DISPLAY1: (1) map earth_location to xy_plane, sample = product_set(linear_set(lon1, lon2, 256), linear_set(lat1, lat2, 256)); This defines a display in a rectangular longitude - latitude coordinate system, just covering the range of the "vis_org" image. Note that DISPLAY1 cannot be compiled until the program executes the statements that set values in "lon1", "lon2", "lat1" and "lat2". If DISPLAY1 defined the mapping: (2) map earth_location to xz_plane; then the default sample set of "earth_location" would be used, and the "vis_org" image would be displayed in its own perspective (i.e., the perspective of the Earth as seen from the GOES satellite at the time that the image was produced). Note that in (1) "earth_location" is mapped to the "xy_plane" but in (2) "earth_location" is mapped to the "xz_plane". This is because latitudes, which define the vertical coordinate in (1), increase from South to North and match the orientation of VIS-AD's "xy_plane", whereas line numbers, which define the vertical coordinate in (2), increase from North to South (at least for images read from McIDAS area files) and match the orientation of VIS-AD's "xz_plane". See Section 7.5.2 for a more general discussion of this issue. The demo program "schl.v" reads 3-D grid files (these are the native files of our VIS-5D system) into arrays indexed by the "real3d" scalar "loc". No default sample set is defined for "loc", so the first call to "read_grid3d" sets its default sample set (to the set of Earth longitudes, latitudes and altitudes of the grid's points). Succeeding calls to "read_grid3d" over-ride this default and set the samplings of the indexes of image arrays according to the Earth navigations of the grids they read (however, all the grids read by the "schl.v" program have the same sample set). The "schl.v" program defines a variety of "real" scalars for the atmospheric fields in its grids (they are "u", "v", "w", "ql", "th", "q", "p", "ed" and "f") and no default sample sets are defined for these types. The "real" type "time" is used an index of a time sequence of grids, and has the default sample set "linear_set(0.0, 20.0, 21)". This sample set could be actual time values - see the demo programs "dxs.v" and "ssmi.v" for examples of actual time values read from files. In DISPLAY1, the mappings: map loc to xyz_volume; map time to animation; map ql to contour, sample = linear_set(0.0, 7.0, 256); illustrate three different ways that sample sets can get into display mappings. The sample set for "time" is the default defined with the type definition, the sample set for "loc" is the default set when a file is read, and the sample set for "ql" is set explicitly with the mapping. The demo program "radar.v" reads a 3-D array of radar reflectivities from a file into an arrays indexed by the "real3d" scalar "loc". A default sample set is defined for "loc" by: type loc = real3d, sample = product_set(linear_set(0.0, 120.0, 121), linear_set(0.0, 120.0, 121), linear_set(0.0, 12.0, 13)); The "radar.v" program uses the external function "rdscan" to read this data, and external function cannot define sample sets. They can only plug data into arrays for which sample sets have been defined by the program. Thus the call to "rdscan" is: vol = rdscan(vol); where "vol" is an array data object indexed by values of the "loc" type. "vol" is passed as an argument to the "rdscan" function as a template array that "rdscan" can just plug its values into - "rdscan" cannot alter the sample sets of "vol". The default sample set of "loc" defines the dimensions of the 3-D array of radar reflectivities returned by "rdscan", although the index sample set of "vol" could have been set at run time. Note that the "vol" array is full of MISSING values when it is passed to "rdscan". 7.3 Getting data into your application If your data are in the McIDAS system, the intrinsic functions "read_area" and "read_nav_area" read McIDAS area files (image files), the intrinsic function "read_grid" reads McIDAS (2-D) grid files, and the intrinsic function "read_grid3d" reads 3-D grid files (as used by VIS-5D). The intrinsic function "read_chris" reads a very simple file format for images. Each pixel is stored in two bytes, with "nelems" pixels per line and "nlines" lines. "nelems" and "nlines" are not stored in the file, but are arguments of the "read_chris" function. You can write an external function in either C or Fortran for reading data from your files into VIS-AD data objects. There are a few examples of such functions (in the "funcs" directory) with the demo programs: "rdssmi.c" with the demo "ssmi.v" "rdscan.f" with the demo "radar.v" "read_gevents.c" with the demo "dxs.v" If you write an external function for reading data, your VIS-AD program will need to pass a data object of the appropriate data type as an argument to this function, or this function will need to build a data object of the appropriate type. See Section 9.4 for a discussion of these two approaches. With either approach, you will need to define appropriate sample sets for the data object that your external function creates. See Sections 5.3, 5.5 and 7.2 for discussions of sample sets. 7.4 Organizing your computation If you just want to look at your data, your program can consist of some calls to data reading functions followed by a "pause" statement. If you also want to do some computing with your data, you can insert computations after you read your data. Remember that this is an interpreted language - so for heavy computations either use vector arithmetic (described in Sections 5.10 and 5.11) or external functions to take advantage of the compiled languages C and Fortran. If your computation is intended to select or discriminate among data points, then you can use MISSING values to mark rejected data points. The system includes a variety of intrinsic functions for manipulating data selections using MISSING data in this way (see the demo programs "cloud.v" and "cumulus.v" for examples). If your computation is statistical, the system includes intrinsic functions for generating elementary statistics, for building and manipulating histograms, and for finding least squares fits. The power of these functions can be extended when combined with vector arithmetic (there is an example in the demo program "fitinter.v"). If your application is numerical, the system includes a variety of mathematical intrinsic functions and vector arithmetic capabilities (see the demo programs "cube4d.v" and "fitinter.v" for examples). If you want to interactively steer your computation (for example to tune a parameter much like you tune a radio) then you need to figure out which parameters to control and set them by calling the "slider" function (described in Section 5.9). You also need to put the slider calls and your computations inside an infinite loop, like this: while (1 == 1) { param1 = slider("param1", low1, hi1, default1); . . . paramN = slider("paramN", lowN, hiN, defaultN); /* computations that use param1, ..., paramN here */ /* optional "pause;" statement here */ } Interactive steering is especially useful in conjunction with VIS-AD's flexible visualization techniques, so when you execute this loop you would probably display one or more data objects that are the result of the computations. As your program executes, you can change the parameters using the sliders and see how that affects the results of your computation. If your computation is slow, you may want to put a "pause" statement at the top or bottom of the while loop so you can control each iteration of your computation. The demo programs "julia.v" and "fitinter.v" are good examples of this kind of interactive steering. Beyond interactive steering you can treat your program as part of the system's user interface, so that you are not only varying parameters of your computations, but you are fiddling with the program logic. Because VIS-AD lets you visualize every data object in your program, you can visually trace through its computations to understand why it succeeds or fails, and adjust your program logic accordingly. If your program becomes a hacked up mess, if it grows haphazardly rather than according to plan, and if it contains sections of code commented out because you may need them later, those are all signs that you are using VIS-AD well. This is because science is an exploration of the unknown and your program will evolve in response to what you learn as you develop it. 7.5 Defining displays The real payout of using VIS-AD comes from the ease and flexibility of designing displays. Here we discuss some of the general design rules, as well as some more detailed issues. 7.5.1 General ideas of display design To design displays all you need to do is define mappings from your scalar types to the display scalars described in Section 6. Then you can display any data object in your program simply by clicking on its name. The demo programs provide lots of examples. Array data types define functions from their index types to the types of their values. So when you map an array's index type to the "x_axis", and you map the array's value type to the "y_axis", the array will be displayed as a graph of "y" as a function of "x", which will work OK. However, if you map the array index to "color", then the array will be displayed with "y" as a function of "color", which will not work well. This problem is an example of a general principle, and we can define a few "rules" for display designs. In a function y = f(x), "x" is the independent variable and "y" is the dependent variable. We interpret arrays as finite samplings of functions. Thus, in a data type T, we call all scalars occurring as array domains "independent scalars" and all other scalars "dependent scalars". Similarly, displays define functional relation between display scalars, with "x_axis", "y_axis", "z_axis" (and the combinations ".._plane" and "xyz_volume"), "animation" and "selector" as "independent display scalars", and "color" and "contour" as "dependent display scalars". Now the display design rules are: 1. If you want to see the functional relations between scalars, then all independent scalars should be mapped, but not to dependent display scalars. At least one dependent scalar in an array must be mapped in order to see that array. 2. If you want to see data as scatter diagrams, then independent scalars should not be mapped (or may be mapped to "color" or "selector"), and dependent scalars should be mapped to independent display scalars. 3. If you run short of spatial axes when you design scatter diagrams, you can map a dependent scalar to "color". You can map more than one dependent scalar to "color" if you are prepared to adjust "color" icons to separate scalars into red, green and blue. Displays do not interfere with computations, so feel free to experiment with display mappings - you cannot hurt the computation. If one display doesn't work well, just edit the mappings and try again. 7.5.2 Handedness, signs, and all that gunk VIS-AD Display Windows are three-dimensional. Their native coordinate systems are right handed Cartesian spanned by the "x_axis", "y_axis" and "z_axis". When a Display Window first appears, the "x_axis" increases to the right, the "y_axis" increases upward, and the "z_axis" increases out of the screen. A variety of coordinate systems are used for data in the Earth sciences and other sciences. In the displays of the demo programs "earth.v" and "cumulus.v", we map the "real2d" scalar "earth_location" to "xy_plane". The pairs of values of "earth_location", as defined by the intrinsic functions "read_nav_area" and "nav_set", are ordered (longitude, latitude) so longitude is mapped to the "x_axis" and latitude is mapped to the "y_axis". Earth latitude increases from South to North and hence maps naturally to VIS-AD's "y_axis". Different scientists use different signs for longitude. In the McIDAS system longitudes increase from East to West, but the intrinsic functions "read_nav_area" and "nav_set" change this so that longitude increases from West to East. Thus longitude maps naturally to VIS-AD's "x_axis". It is possible to reverse signs in display mappings. For example, the demo program "dxs.v" uses the following mapping for longitude: map longitude to x_axis, sample = linear_set(360.0, 0.0, 256); Using this mapping, the "x_axis" increases from left to right but "longitude" increases from right to left. It is also possible to rotate the 3-D box in the Display Window by 180 degrees so that "y_axis" increases from top to bottom, but then "z_axis" will increase into the screen. Many satellites scan lines in their images from West to East, and order lines from North to South. The pairs of values of "pixel_location", as defined by the intrinsic function "read_area", are ordered (element, line). If we map this image coordinate system to the "xy_plane", then element is mapped to the "x_axis" and line is mapped to the "y_axis" and we will need to either adjust the mapping to change the sign of line, or rotate the 3-D box by 180 degrees. Rather than doing either of these things, the demo programs "cloud.v", "fit.v", "fit3d.v" and "fitinter.v" map "pixel_location" to "xz_plane". Then a 90 degree rotation (done by just clicking the center mouse button on the RESET icon) of the 3-D box has "z_axis" increasing downwards (just as satellite scan lines increase downwards) and "y_axis" increasing out of the screen. The issues of handedness and signs are messy in 3-D displays, but hopefully this discussion will help. 7.5.3 Programming tricks to improve display designs One of the notable features of VIS-AD is that there are no calls to graphics functions embedded in programs. However, you may occasionally want to add some logic to your programs in order to generate data objects that are interesting to visualize. For example, given the data types: type ir_radiance = real; type vis_radiance = real; type pixel_location = real2d; type ir_image = array [pixel_location] of ir_radiance; type vis_image = array [pixel_location] of vis_radiance; you may want to visualize data objects of the types "ir_image" and "vis_image" as an colored terrain defined by the mappings: map pixel_location to xz_axis; map ir_radiance to y_axis; map vis_radiance to color; However, distinct data objects of the types "ir_image" and "vis_image" will be displayed as a terrain and a 2-D color image independently, not as a colored terrain. In order to see a colored terrain, define the data type: type visir = array {pixel_location] or structure { .v_vis = vis_radiance; .v_ir = ir_radiance; } Then given data objects: visir vir; ir_image ir; vis_image vis; the statements: vir[].v_vis = vis; vir[].v_ir = ir; will merge the "ir" and "vis" data objects into a single data object that will be displayed as a colored terrain. You may also want to modify your program to be able to work around the limitation that a scalar can only be mapped to one display scalar. We will remove this limitation in future versions of VIS-AD. Until then, you need to use the following "trick". If you want to map "ir_radiance" to both "y_axis" (terrain height) and "color", define data types: type ir_radiance2 = real; type irir = array {pixel_location] or structure { .v_ir = ir_radiance; .v_ir2 = ir_radiance2; } Then given data objects: irir ir2; ir_image ir; the statements: vir[].v_ir = ir; vir[].v_ir2 = ir; will create two redundant "ir" values in the "irir" data object, which will generate the desired display using the mappings: map ir_radiance to y_axis; map ir_radiance2 to color; The demo programs "ssmi.v" and "radar.v" contain examples of this trick. 7.5.4 Using display widgets to improve display designs Many of VIS-AD's displays are just sets of discrete points. Clicking on the WIDE icon causes points and lines to be rendered three pixels wide which can reduce aliasing and other artifacts of the point rendering. When images are mapped to a 2-D color array (i.e., when the "real2d" index of an image array is mapped to some ".._plane" and image values are mapped to "color"), if the sample set of the image array's index does not coincide with the sample set used in the mapping to ".._plane", then the image may appear to have tears and gaps. Clicking on the RESAMPLE icon causes the image array's index values to be resampled to the sample set used in the display mapping, so gaps and tears are filled in. The demo program "earth.v" provides examples of this. When images are mapped to terrains (or when 2-D histograms are mapped to terrains), they are displayed as discrete sets of points. Clicking on the SURFACE icon causes the points to be interpolated to a smooth surface. The demo programs "earth.v", "cloud.v" and "dxs.v" provide examples of this for both images and 2-D histograms. When scalars are mapped to "contour" and are displayed as iso-lines (either because the data are 2-D or you are using the 2-D slices in 3-D data), clicking on the LABELS icon causes numerical labels to appear. 7.5.5 Comparing data objects and multiple displays Computations generally involve many different data objects, so comparisons between data objects are important for understanding computations. The easiest way to visually compare several data objects is to select them all for display in one Display Window. When you select a data object for display its name is highlighted (in reverse video) in the program text, but this can be confusing if you have enabled more than one Display Window (i.e., more than one DISPLAYn icon is highlighted). Thus it is better to look at the list of selected data objects printed at the bottom of the Display Window. If one object tends to obscure the display of another, it is often effective to click repeatedly on the name of the obscuring data object, so that its display toggles on and off. If no scalar component of an object is mapped to "color" then the object will be displayed in monochrome. Each data object is displayed in a different monochrome color, and these colors are used for the names of data objects at the bottom of the Display Window. The order in which data objects are selected controls their colors, and also the order in which they are rendered - thus different selection orders can generate different displays. You may want to experiment with order of selection to get the display you need to compare data objects. The demo program "cloud.v" provides lots of examples of comparing data objects to understand computations. You can also compare displays using several different sets of display mappings. Each of the four displays (DISPLAY1, DISPLAY2, DISPLAY3 and DISPLAY4) has its own Display Window, Display Mapping window and Display Widgets window. In order to several displays at once, you need to move and resize the main Display Windows so that they don't obscure one another. Since each DISPLAY has its own set of scalar mappings, you also need to think about how you could benefit from seeing data in different coordinate systems. Selecting data objects for display is a bit complex when more than one DISPLAY is enabled - clicking on the name of a data object toggles it on/off independently in each enabled DISPLAY. Thus you may actually be toggling a data object on in one DISPLAY and off in another. The easiest way to control this is enable each DISPLAY one at a time, selecting the data objects you want to see in each. Then, when data objects are selected in each DISPLAY, re- enable them all. ********************************************* * With multiple DISPLAYs it is important to * * watch the lists of selected names at the * * bottoms of the main Display Windows. * ********************************************* By enabling multiple DISPLAYs it is possible to construct a sort of spreadsheet of up to four cells (i.e., the four DISPLAYs). This would be an "imperative" style of spreadsheet, in which the computation is independent of the cells, and data objects are directed to the cells by the user. In a "functional" spreadsheet style, each cell contains a piece of the overall computation, and displays the results of its computational piece. The demo programs "cloud.v", "lorenz.v", "fit.v", "fitinter.v" and "ssmi.v" provide examples of interesting comparisons between multiple DISPLAYs. If you are confused by the displays of your data objects (e.g., you click on a data object and nothing appears), see Section 7.6.4 for troubleshooting ideas. 7.6 Troubleshooting 7.6.1 If you click on the COMPILE icon and it does not highlight: Look for a compiler error messages in the bottom line of the Program window. If there is an error, the text cursor will be on the line where the error occurred (or on the next line). 7.6.2 If you click on one of the DISPLAY icons and it does not highlight: Look for a compiler error messages in the bottom line of the Display Mapping window. If there is an error, the text cursor will be on the line where the error occurred (or on the next line). 7.6.3 If you program seems dead (or starts over at the beginning when you execute it): Look for a runtime error messages in the bottom line of the Program window. If there is an error, the text cursor will be on the line where the error occurred (or on the next line). When your program finishes executing (i.e., runs off the end of the "main" function then all data objects are cleared, so nothing will display). So its a good idea to put a "pause" statement at the end of "main". 7.6.4 If you click on the name of a data object and nothing appears: Make sure at least one DISPLAY icon is highlighted. Make sure the name of the data object is highlighted - if its not then its not selected for display. If more than one DISPLAY is highlighted, things can get complicated, so you may want to click all but one of them off. For example, a data object may be selected in DISPLAY1 but if the DISPLAY2 window obscures the DISPLAY1 window then the data object's display will be obscured. The data object may be MISSING, or it may be an array full of MISSING values, which are invisible in the display. All data objects are initialized to MISSING, so this may indicate that you never assigned a value to the data object. There are circumstances under which you think you assign a value to a data object but it doesn't happen: 1. You assign a value to an array but provide an index value for the array outside the range defined for the array index's sample set. 2. You assign a value to an array outside the range defined for the array value's sample set. These circumstances can cause mysterious MISSING data objects when you think you assigned values to the data object. The scalar types used in the data objects type may not be mapped in the display's Mapping window. Or the mappings may define a degenerate display for a data object - so it may be displayed as a single point hidden along one of the axes of the display. See Section 7.5.1 for advice about designing displays. The values in the data object may not overlap the sample sets that define the ranges of values in a display. You can check this by displaying the range of values defined with mappings (for mappings to spatial coordinates click on the CURSOR icon and check the range by using the 3-D cursor, for mappings to color check the range using the cursor in the color map icon, for mappings to selector check the range on the selector slider icons, etc). Then you can insert a few "print" statements in your program to print the actual values of variables and array elements. You can check the range of an array's index by using the intrinsic function "get_sample" and printing the range values (i.e., low and hi) it gives you. 7.6.5 When all else fails: My high school physics teacher had a sign at the front of his classroom that said "Serious thinking starts with the problems that don't work out, not the ones that do." If you are building a VIS-AD application and run into problems that you cannot solve, send me an email (whibbard@macc.wisc.edu) describing the problem in as much detail as possible (perhaps include your program and mapping files in your email) and I will try to help. ************************** 8. VIS-AD INTRINSIC FUNCTIONS ************************** Intrinsic functions are implemented as part of the VIS-AD programming language, and generally implement commonly needed operations. The intrinsic functions are listed by giving VIS-AD type templates for the functions and their arguments. The arguments are listed in the style of ANSI C, with type declarations embedded in the argument list. Many of the VIS-AD intrinsic functions are polymorphic in the sense that their arguments may take several different types (for example, the remap and make_histogram functions may applied to 1-D, 2-D or 3-D images). We indicate this in the type templates, using "any_type" to match any type, "any_scalar_type" to match any scalar type, and using "realNd" and "realMd" to match "real", "real2d" or "real3d". Also, "int" and "real" values may be used interchangeably (if you pass a "real" value to a function that wants an "int", it will truncate to an integer). The VIS-AD intrinsic functions include: 8.1 Mathematical functions val min(val a, b;) val max(val a, b;) val pow(val a, b;) ------------------ type val = real; return the minimum or maximum of "a" and "b", or a^b (i.e., "pow" is the same as the "pow" intrinsic function in the C language); "pow" returns the MISSING value if its inputs are out of range; see the demo program "earth.v" for an example vals min(vals a, b;) vals max(vals a, b;) vals pow(vals a, b;) -------------------- type val = real; type loc = realNd; type vals = array [loc] of val; these are the vector equivalents of the functions above; that is, these functions return arrays whose elements are the results of applying the functions to the elements of "a" and "b" val sqrt(val a;) val abs(val a;) val sin(val a;) val cos(val a;) val tan(val a;) val atan(val a;) val exp(val a;) val log(val a;) --------------- type val = real; these are all the same as the C language intrinsic functions of the same name; for trigonometric functions, angles are in radians; these functions return the MISSING value if the input "a" is out of range; see the demo programs "fitinter.v", "cube3d.v" and "cube4d.v" for examples vals sqrt(vals a;) vals abs(vals a;) vals sin(vals a;) vals cos(vals a;) vals tan(vals a;) vals atan(vals a;) vals exp(vals a;) vals log(vals a;) --------------- type val = real; these are the vector equivalents of the functions above; that is, these functions return arrays whose elements are the results of applying the functions to the elements of "a" val statistics(vals a; val nonmiss, mean, variance;) ---------------------------------------------------- type val = real; type loc = realNd; type image = array [loc] or val; return the sum of the non-MISSING elements of the array "a"; return the number of non-MISSING elements in "nonmiss", return the mean of those elements in "mean", and return the variance of those elements in "variance"; see the demo program "fitinter.v" for an example 8.2 Input / output functions print(any_scalar_types obj1, obj2, ...;) --------------------------- print the values of any scalar objects or expressions (the values are printed in the window where you entered the visad command); see the demo programs "dxs.v", "sort.v", "cube3d.v" and "cube4d.v" for examples of how this function is used error(text message;) -------------------- type text = string; print "message" and abort execution; see the demo programs "dxs.v", "fitssmi.v", "radar.v" and "ssmi.v" for examples of how this function is used any_type read(name filename;) -------------------------------- type name = string; read a data object from a disk file; the file must have been created by the "write" function; this is an easy way to pass data between different VIS-AD programs; note that a literal string can be used for the filename so read("datafile") is a legal call to this function write(name filename; any_type obj); -------------------------------- type name = string; write a data object of any type to a disk file image read_area(value iarea, nlines, nelems, line, elem;) -------------------------------------------------------- type loc = real2d; type value = real; type image = array [loc] of value; read a sector of size "nlines" by "nelems", offset by "line" and "elem", of McIDAS area file number "iarea" into an object of type "image"; pixels in the "image" array are indexed by line and element; see the demo programs "cloud.v", "fit.v", "fit3d.v" and "fitinter.v" for examples of how this function is used image read_nav_area(value iarea, nlines, nelems, line, elem;) ----------------------------------------------------- type loc = real2d; type value = real; type image = array [loc] of value; read a sector of size "nlines" by "nelems", offset by "line" and "elem", of McIDAS area number "iarea" into an object of type "image"; pixels in the "image" array are indexed by latitude and longitude (i.e. the area's navigation is the sampling of loc); see the demo programs "cumulus.v" and "earth.v" for examples of how this function is used grid read_grid3d(value ifile, igrid;) ------------------------------------ type loc = real3d; type value = real; type grid = array [loc] of value; read grid number "igrid" from VIS-5D 3-D grid file number "ifile", into an object of type "grid"; see the demo programs "lamps.v" and "schl.v" for examples of how this function is used grid read_grid(value ifile, igrid;) ------------------------------------ type loc = real2d; type value = real; type grid = array [loc] of value; read grid number "igrid" from McIDAS grid file number "ifile", into an object of type "grid"; these are 2-D grids image read_chris(text name; value nlines, nelems, line, elem, lsize, esize;) ------------------------------------------------------ type loc = real2d; type value = real; type text = string; type image = array [loc] of value; read image sector from file "name"; the file contains "lsize" lines of "esize" pixels each, with 2 bytes per pixel; the returned image sector contains "nlines" lines of "nelems" pixels each, offset by "line" and "elem"; see the demo program "fitssmi.v" for an example of how this function is used 8.3 Image manipulation functions Although "image manipulation" suggests 2-D arrays, most of these functions can be applied to 1-D, 2-D and 3-D arrays (i.e., most of these functions operate on arrays with "realNd" indices, where N = 1, 2 or 3). See the demo program "cloud.v" for examples of how to combine most of these functions in complex image processing algorithms. image remap(image a, b;) ------------------------ type loc = realNd; type val = real; type image = array [loc] of val; return an array that resamples the values of image object "a" to the sample set of the index of image object "b"; note that this function may be applied to data of dimension 1, 2 or 3; see the demo programs "cloud.v", "cumulus.v" and "earth.v" for examples of how this function is used image remap_mean(image a, b;) -------------------------------- type loc = realNd; type val = real; type image = array [loc] of val; resample image "a" to the sample set of the index of image "b", creating a distribution of values from image "a" associated with each pixel of image "b"; then return an image, with the same index sample set as "b", whose values are the means of the distributions of "a" values image remap_variance(image a, b;) -------------------------------- type loc = realNd; type val = real; type image = array [loc] of val; resample image "a" to the sample set of the index of image "b", creating a distribution of values from image "a" associated with each pixel of image "b"; then return an image, with the same index sample set as "b", whose values are the variances of the distributions of "a" values; see the demo program "cloud.v" for an example of how this function is used image lowpass(image a; val b;) ------------------------------ type loc = realNd; type val = real; type image = array [loc] of val; return a lowpass filter of image "a" with filter window size "b"; note that the "loc" index of the array objects may be either "real" or "real2d"; see the demo programs "cloud.v" and "fit.v" for examples of how this function is used image shift(image a; loc b;) ------------------------ type loc = realNd; type val = real; type image = array [loc] of val; return an array that shifts the values of image object "a" by the amount "b"; this function may be used to implement finite difference operations on image; note that this function may be applied to data of dimension 1, 2 or 3 image rotate(image a; loc b;) ------------------------ type loc = realNd; type val = real; type image = array [loc] of val; return an array that shifts the values of image object "a" by the amount "b" and wraps around the edges of the image; note that this function may be applied to data of dimension 1, 2 or 3; see the demo program "life.v" for an example of how this function is used image select_range(image a; val low, hi;) ----------------------------------------- type loc = realNd; type val = real; type image = array [loc] of val; return an image that is equal to image "a", except that those pixels not in range (low, hi) are set to "missing"; see the demo programs "cloud.v", "cumulus.v", "dxs.v" and "life.v" for examples of how this function is used image and_image(image a, b;) ---------------------------- type loc = realNd; type val = real; type image = array [loc] of val; return an image that is equal to image "a", except that pixels are set to "missing" where corresponding pixels of image "b" are missing; note images "a" and "b" must be at the same sampling; see the demo programs "cloud.v", "cumulus.v", "dxs.v" and "life.v" for examples of how this function is used image or_image(image a, b;) --------------------------- type loc = realNd; type val = real; type image = array [loc] of val; return an image that is equal to image "a", except that pixels that are "missing" in image "a" are set to the values of the corresponding pixels of image "b" are missing; note images "a" and "b" must be at the same sampling; see the demo programs "cloud.v" and "life.v" for examples of how this function is used image not_image(image a;) ------------------------- type loc = realNd; type val = real; type image = array [loc] of val; return an image with the same sampling as image "a", and whose pixels are set to "missing" where the corresponding pixels in "a" are not "missing", and are set to a nominal (non-missing) value otherwise; see the demo program "life.v" for an example of how this function is used image slope(image a;) --------------------- type loc = real2d; type val = real; type image = array [loc] of val; return an image whose values are the sums of the absolute values of the differences of the corresponding pixels of image "a" with each of their 4 neighbors; see the demo program "cloud.v" for an example of how this function is used 8.4 Data modeling via least squares fitting image poly_fit(image x, y, z, w; template s; coefs c; val r, resid;) ----------------------------------------------------- type loc = realNd; type val = real; type image = array [loc] of val; type template = string; type index = int; type coef = real; type coefs = array [index] of coef; poly_fit finds a least squares fit to "w" as a polynomial function of "x", "y" and "z"; the string "s" defines what terms are included in the polynomial, for example the string "1 + x + x2 + xy + y2" says to model "w" as a function of five terms: a constant term, "x", "x" squared, the product of "x" and "y", and "y" squared - since no term involves "z" it can be MISSING; note that either "y" or "z" may be MISSING if they are not mentioned in "s"; poly_fit returns an image fitting "w" as the function value; the array of optimum coefficients are returned in "c", the root mean square of residuals is returned in "resid", and the reduction in variance is returned in "r"; see the fit.v and fitinter.v demo programs for more detail about how these values are calculated; see the demo programs "fit.v", "fit3d.v", "fitinter.v" and "fitssmi.v" for examples of how this function is used image poly_fit2(image x, y, z, w; template s; coefs c; val r, resid;) ----------------------------------------------------- type loc = realNd; type val = real; type image = array [loc] of val; type template = string; type index = int; type coef = real; type coefs = array [index] of coef; poly_fit2 is identical to poly_fit, except that poly_fit2 minimizes the square of the Cartesian distance (i.e. in the space spanned by "w" and all of the terms defined in "s" except the constant term) whereas poly_fit just minimizes the square of the distance in "w"; this is motivated by the assumption that there are errors in the independent variables "x", "y" and "z" as well as the dependent variable "w"; for example, if we use "poly_fit2" to fit "x" as a linear function of "y" and "y" as a linear function of "x" it will produce the same numerical relation between "x" and "y" both ways, but this is not true using "poly_fit"; see the demo program "fit.v" for an example of how this function is used 8.5 Histogram manipulation functions See the demo program "cloud.v" for examples of how to use all of these functions except "make_2d_histogram". See the demo programs "cumulus.v", "earth.v" and "dxs.v" for examples of how use the function "make_2d_histogram". histogram make_histogram(image a; val b;) ----------------------------------------- type loc = realNd; type val = real; type count = int; type image = array [loc] of val; type histogram = array [val] of count; calculate a histogram object with bin size "b" for the values in image object "a"; see the demo programs "cloud.v" and "dxs.v" for examples of how this function is used image select_histogram(image a; histogram b;) --------------------------------------------- type loc = real2d; type val = real; type count = int; type image = array [loc] of val; type histogram = array [val] of count; return an image that is equal to image "a", except that those pixels whose values index "missing" counts in histogram "b" are set to "missing"; see the demo program "cloud.v" for an example of how this function is used histogram find_cluster(histogram a; val wsize, bsize, csize, mul;) ----------------------------------------------------- type val = real; type count = int; type histogram = array [val] of count; return a histogram that is equal to histogram "a", except that counts are set to "missing" for any "vals" that are not in clusters; a temporary histogram "t" is calculated as the lowpass box filter of histogram "a" with a window size 1+2*wsize, and "val" is judged to be in a cluster if a[val+i] > mul*t[val+i] for any i between -bsize and + bsize; furthermore, clusters separated by gaps of less than 2*csize are merged; see the demo program "cloud.v" for an example of how this function is used histogram select_cluster(histogram a; val low, hi;) --------------------------------------------------- type val = real; type count = int; type histogram = array [val] of count; return a histogram that is equal to histogram "a", except that counts are set to "missing" for any "vals" in clusters that do NOT overlap the range (low, hi); see the demo program "cloud.v" for an example of how this function is used histogram delete_cluster(histogram a, b;) ----------------------------------------- type val = real; type count = int; type histogram = array [val] of count; return a histogram that is equal to histogram "a", except that counts are set to "missing" for any "vals" in clusters that overlap clusters in histogram "b"; see the demo program "cloud.v" for an example of how this function is used val percent_hist(histogram a; val b;) ------------------------------------- type val = real; type count = int; type histogram = array [val] of count; return the "val" level of histogram "a" with percentile "b", where "b" is between 0.0 and 1.0; see the demo program "cloud.v" for an example of how this function is used histogram2d make_2d_histogram(image1 a; image2 b; val1 c; val2 d;) --------------------------------------------------------- type loc = realNd; type val = real; type val1 = real; type val2 = real; type count = int; type image1 = array [loc] of val1; type image2 = array [loc] of val2; type histogram2d = array [val1] of array [val2] of count; calculate a 2-D histogram object with bin size "c" for the values in image object "a" and with bin size "d" for the values in object "b"; see the demo programs "cumulus.v", "dxs.v" and "earth.v" for examples of how this function is used 8.6 Sample set functions These functions define the sample sets for scalar types and their occurrences in data objects (as discussed in Section 5.3). They may be called in scalar type definitions (as discussed in Section 5.2), in "sample" statements (as discussed in Section 5.5) and in mappings used to define displays (as discussed in Section 6). They return a "sample set" rather than a data object, so they may not be called except in scalar type definitions, in "sample" statements, and in display mappings. We use "set" to indicate a sample set rather than a data type. set linear_set(val low, hi, size;) ---------------------------------- type val = real; return a sample set for a "real" or "integer" type that is an arithmetic progression of "size" real values from "low" to "hi"; all the demo programs (except "julia.v") contain examples of how this function is used set integer_set(val low, hi, size;) ----------------------------------- type val = real; return a sample set for a "real" or "integer" type that is an arithmetic progression of "size" integer values from "low" to "hi"; note the progression may not end exactly on the "hi" value, in order to make the step size between values an integer; many of the demo programs contain examples of how this function is used set string_set(val length;) --------------------------- type val = real; return a sample set for a "string" type that consists of all strings of length "length" set product_set(set factor1, factor2;) set product_set(set factor1, factor2, factor3;) ----------------------------------------------- return a sample set for a "real2d" or "real3d" type that is the cross product of its arguments (these are usually just calls to "linear_set"); many of the demo programs contain examples of how this function is used set set_of(any_type obj;) ------------------------- if "obj" is a scalar, return its sample set; if "obj" is an array, return the sample set of its domain; if "obj" is a tuple or MISSING, this generates an error; see the demo programs "cube3d.v", "cube4d.v", "cumulus.v", "earth.v" and "life.v" for examples of how this function is used set nav_set(val iarea, line, elem, nlines, nelems;) --------------------------------------------------- type val = integer; return a sample set for a "real2d" type that is the set of (longitude, latitude) pairs for the pixels of McIDAS area file (image file) number "iarea", offset by "line" and "elem", and with a size of "nlines" by "nelems" val get_sample(any_type a; vals sizes, low, hi;) ------------------------------------------------ type val = real; type index = int; type vals = array [index] of val; return the number of dimensions of the sample set of "a"; return the sizes and range of each dimension in "sizes", "low" and "hi"; see the demo programs "sort.v", "cumulus.v" and "earth.v" for examples of how this function is used image change_set(image a, b;) ---------------------------- type loc = realNd; type val = real; type image = array [loc] of val; return an array that contains the values of the array "a" combined with (NOT resampled to) the index sample set of array "b"; the dimension and sizes (as returned by the intrinsic function "get_sample") of the index sample sets of "a" and "b" must match; this function can be used to change coordinate systems (e.g., given a 2-D image indexed by longitude and latitude coordinates, "change_set" can be used to switch to line and element coordinates) 8.7 Obsolete functions The system includes a number of obsolete intrinsic functions that should not be used. Some were short cuts to doing things that have since been implemented properly. image(M+1) append(imageM a; image b;) ------------------------------------- type index = realNd; type val = real; type image = array [index] of val; type imageM = array [index] of structure {.i1 = val; ...; iM = val;}; type image(M+1) = array [index] of structure {.i1 = val; ...; iM+1 = val;}; (**** while the "append" function still exists for compatibility with old programs, new programs should use the syntax described in Section 5.11 rather than this function ****) append the array of values in the second argument as the last tuple components of the array in the first argument; note that M is between 1 and 11 for N=1 or 2, and M is 1 for N=3; the samplings of "index" must match between arguments image extract(imageM a; struc_index b;) --------------------------------------- type index = realNd; type val = real; type struc_index = int; type image = array [index] of val; type imagesM = array [index] of structure {.i1 = val; ...; iM = val;}; (**** while the "extract" function still exists for compatibility with old programs, new programs should use the syntax described in Section 5.11 rather than this function ****) the first argument is an array of tuples of scalars; extract an array of scalars, by selecting just the b-th element from each tuple in the array, where b is the second argument; numbering of tuple components starts with 0 (i.e., a second argument of "0" selects the first tuple component) scatter make_scatter(image a, b;) --------------------------------- type loc = realNd; type val = real; type var = real; type index = int; type image = array [loc] of val; type scatter = array [index] of structure {.sl = val; .sr = var;} (**** since there is already a "make_variance" function, and since images can be displayed as scatter diagrams by defining a frame of reference that leaves pixel locations unmapped, and that maps pixel radiances to the x, y and z axes, there is no real point to this function ****) first, resample image "a" to the sampling of image "b", so that a distribution of values of "val" from image "a" is associated with each "loc" of image"b"; return a scatter object of the means versus the variances for each of those distributions of values histogram hist_scatter(scatter a; var b; val c;) ------------------------------------------------ type val = real; type var = real; type count = int; type index = int; type scatter = array [index] of structure {.sl = val; .sr = var;} type histogram = array [val] of count; (**** this function is a helper for the "make_scatter" function, which is also obsolete ****) return a histogram of "val" values from the scatter object "a", restricted to those structures in "a" whose "var" value is < "b" val ccmrad(profile a,b,c,d,e,f,g,h; val i,j,k,l,m,n,o,p,q,r,s;) -------------------------------------- type val = real; type level = real; type profile = array [level] of val; (**** this is a special function for a particular application involving a radiation cloud model ****) note that ccmrad returns values in its last 4 arguments (p, q, r and s) ************************* 9. VIS-AD EXTERNAL FUNCTIONS ************************* External functions are written by users in the compiled languages C and Fortran. They can be used to: Import data from various file formats and scientific computation systems into VIS-AD programs, Link to users' existing software. Do heavy computations in a compiled language, that would be too slow in VIS-AD's interpreted language. Avoid writing too much code in the VIS-AD programming language (which can only be executed using the VIS-AD system). For example, if you are developing a weather model you can write the bulk of the code in C or Fortran and only write the top level logic in the VIS-AD language (which you can easily rewrite in C or Fortran for production runs). If you write external functions for VIS-AD that you think would be useful to others, for example to read data from commonly used file formats or to do common mathematical operations, please let us know via email (whibbard@macc.wisc.edu) so that we can make your external functions available as part of our ftp distribution. External functions are called from a VIS-AD program just as an internal function (i.e., a function defined in the language) or an intrinsic function would be called, except that the type of the function must be declared using a statement like: extern temp_image irtemp(); This declares "temp_image" as the type of the function named "irtemp". These external function declarations must come after type definitions and before internal function definitions (i.e., before any executable statements). The source code for external functions is in the "funcs" sub-directory of the "visad" directory. We supply special scripts for compiling and linking external functions. The "externf" script if used for Fortran functions (source files named *.f) and the "externc" script is used to C functions (source files named *.c). See the Section 9.2 for a discussion of building and debugging external function. A simple example of an external function is "irtemp". It converts 8-bit GOES infrared calibrated radiances to Kelvin temperatures, and is used by the "cumulus.v" and "earth.v" demo programs. A VIS-AD program that calls "irtemp" should include type declarations similar to (the demo program "earth.v" and "cumulus.v" do call "irtemp"): type loc = real2d; type ir_radiance = real; type temperature = real; type ir_image = array [loc] of ir_radiance; type temp_image = array [loc] of temperature; and the object declarations similar to: ir_image im_ir; temp_image im_temp; and an external function declaration similar to: temp_image irtemp(); Then "irtemp" is called like this: im_temp = irtemp(im_ir); The Fortran source code of the function is: SUBROUTINE IRTEMP() PARAMETER (MAXLEN=100000) INTEGER ADINT REAL IMAGE(MAXLEN) C C CALL ADIMG TO PICK UP THE FIRST ARGUMENT (INDICATED BY C THE "1") IN THE "IMAGE" ARRAY. THE SIZE OF THE IMAGE IS C RETURNED IN "NLINES" AND "NELEMS". "MAXLEN" TELLS ADIMG C THE MAXIMUM SIZE OF DATA THAT CAN FIT IN "IMAGE". C CALL ADIMG(1, MAXLEN, IMAGE, NLINES, NELEMS) C LOOP THROUGH IMAGE PIXELS LEN=NLINES * NELEMS DO 100 I = 1, LEN C TEST FOR MISSING DATA (INDICATED BY 1.0E35) IF(IMAGE(I) .LE. 1.0E30) THEN C CALCULATE TEMPERATURE FROM IR RADIANCE IF(IMAGE(I) .LE. 176.0) THEN IMAGE(I) = (660.0 - IMAGE(I)) / 2.0 ELSE IMAGE(I) = 418.0 - IMAGE(I) ENDIF ELSE C RETURN A MISSING VALUE (INDICATED BY 1.0E35). C THIS IS UNNECESSARY SINCE "IMAGE(I)" IS ALREADY MISSING, C BUT WE INCLUDE IT AS AN ILLUSTRATION IMAGE(I) = 1.0E35 ENDIF 100 CONTINUE C C CALL ADRTIM TO RETURN THE "IMAGE" ARRAY TO THE CALLING C PROGRAM. THE "1" MEANS THAT THE VALUES IN "IMAGE" WILL C BE INSERTED INTO THE DATA OBJECT PASSED AS THE FIRST C ARGUMENT TO THE FUNCTION AND THE RESULTING IMAGE OBJECT C WILL BE RETURNED AS THE VALUE OF "IRTEMP". "NLINES" AND C "NELEMS" ARE THE SIZE OF THE "IMAGE" ARRAY AND MUST BE C CONSISTENT WITH THE SIZE OF THE FIRST ARGUMENT. C CALL ADRTIM(1, IMAGE, NLINES, NELEMS) RETURN END External functions do not pick up their arguments in the normal way - rather they pick them up by calls to libraries of functions described in Sections 9.3 and 9.4. The single argument to "irtemp" is the 2-D image array "im_ir". "irtemp" picks up this argument by a call to the ADIMG function. The first argument to ADIMG is the number of the argument - 1 indicates that "im_ir" is the first argument to "irtemp". The pixel values of "im_ir" are returned in the IMAGE array, and SIZE is the length of this array so that ADIMG does not over-run IMAGE. The size of the "im_ir" images is returned in NLINES and NELES. External functions do not return values in the normal way either - they return values by calls to libraries of functions described in Sections 9.3 and 9.4. "irtemp" returns a 2-D image array that is the same size as its argument "im_ir" by a call to ADRTIM. The first argument of ADRTIM is 1 to indicate that the first argument of "irtemp" is used as a template for the returned image. The values in the IMAGE array are filled into the "im_ir" array and the result is passed back to the VIS-AD program as the value of "irtemp" assigned to "im_temp". The NLINES and NELEMS arguments to ADRTIM are the size of IMAGE and must match the size of the first argument. The "ir_temp" function compares the IMAGE pixel values with 1.0E30. When VIS-AD passes data to external functions it flags MISSING values by floating point values of 1.0E35, and when external functions return values they flag MISSING values by floating point values of 1.0E35. We have provided two very different libraries of functions for external functions, depending on whether they are written in Fortran or C. The Fortran-callable functions are relatively simple and can only handle arguments and return values with relatively simple data types. The C-callable functions are quite flexible and permit the external function to explore its arguments to determine what their type is. Thus external functions written in C can be polymorphic - that is, they can respond to various types of arguments. There is an important limit to this polymorphism: the type of an external function is declared in a VIS-AD program - so within one program an external function can only return one type of data object. Of course, The Fortran-callable library functions can be called from C (as described at the end of Section 9.3), and are the easiest way for external functions to handle arguments and return values. 9.1 External function error messages If an external function calls a library function that is inappropriate for the argument it is applied to (i.e., argument number ARGNUM for the Fortran-callable functions), or aborts or makes some other error, then an error code is returned to VIS-AD. This generates a run time error message at the bottom of the Program Window. Whenever one of these error messages appears, the text cursor is placed on the line where the error occurred (or at least close). The error messages for external functions are: Cannot invoke external function "name" The system cannot find "name" as an external function; it has not been properly created by "externf" or "externc". external function - bad return type The data type of the returned object does not match the declared type of the external function. external function - bad argument index The argument index in a library function call (e.g. ARGNUM in ADREAL or param_index in ad_param_out) is too small or too large. external function - bad tuple index The index of a tuple (e.g. BAND in ADMB or tuple_index in ad_obj_tuple) is too small or too large. external function - bad array index The index of an array (e.g. array_index in ad_obj_array) is too small or too large. external function - bad type A library function was called and applied to an object of inappropriate type (e.g. ARGNUM in ADVOL refers to a scalar object). external function - bad set A library function was called and detected a bad sample set (e.g. dset or rset do not point to valid sample sets in a call to ad_make_array_obj). external function - array size too small A Fortran-callable library function supplied an array for retrieving values that is too small for the data object passed as an argument (e.g. SIZE in ADIMG is less than NLINES * NELEMS for the image object passed as an argument). external function - time out (aborted) An external function aborted. We have included the Fortran-callable library function LDEST to make it easy to print messages from external functions to help you debug them. external function - cannot use missing object for return You should never see this message; it indicates that an argument is a MISSING object and was used as a return value from the external function, but VIS-AD converts MISSING array data objects to arrays full of MISSING values before passing them to external functions. Note, however, that it is legal to explicitly return a MISSING object using ADRTMS. external function - uncode error code N You should never see this message; it indicates an unexpected error return code from an external function. external function dismissed - leg before wicket You should never see this message. 9.2 Building and debugging external functions *********************************************** * If you write an external function, the name * * of its source file must be the same as the * * name of the function, extended with either * * ".f" for Fortran or ".c" for C. * *********************************************** For example, the external function "julia" (called by the demo programs "julia.v" and "juliafam.v") is written in C so its source code in in the "julia.c" file in the "funcs" directory. Its source code has the basic form: julia() { . . . } External functions are compiled and linked using the "externf" and "externc" scripts as follows. The Fortran function "irtemp.f" is built by: externf irtemp and the C function "julia.c" is built by: externc julia The "externf" and "externc" scripts link your function with the "libvisad.a" and "libmcidas.a" libraries. If your external function must link with other libraries or object files, include their names (up to 8 of them) as arguments to the script after then name of your function. For example, the C function "read_gevents" is built by the command: externc read_gevents binaryio.o bintable.o fits.o The "funcs.m" make file in the "funcs" directory contains examples of how the scripts "externc" and "externf" are used. The external functions dsitributed with VIS-AD are built by the command: make -f funcs.m The simplest way to debug your external functions is by inserting print statements into your source code. You can also use the Fortran callable library function LDEST to print a text string and an integer. However, it is also possible to debug using dbx, although it is a bit tricky. Here are the steps for debugging external functions with dbx: 1. Edit the "externc" or "externf" script to use the CFLAGS and FFLAGS for debugging. That is, switch from: # for no debugging: CFLAGS="-c -cckr -O2" FFLAGS="-c -O2" # for debugging: #CFLAGS="-c -cckr -g" #FFLAGS="-c -g" to: # for no debugging: #CFLAGS="-c -cckr -O2" #FFLAGS="-c -O2" # for debugging: CFLAGS="-c -cckr -g" FFLAGS="-c -g" 2. Insert a call to the "sleep(20)" function as the first line of your external function. This call works in either C or Fortran, and will cause your function to sleep for 20 seconds (giving you time to get its process id and bind dbx to it). 3. Invoke "externc" or "externf" to build your external function with debugging enabled. 4. Run the "visad" command applied to a VIS-AD program (i.e., "something.v") that calls your external functon. 5. Compile your VIS-AD program (i.e., click on the COMPILE icon), put a break point one the first line that calls your external function (click the center mouse button on that line), and run the VIS-AD program (click on the STOP/GO icon). Wait for your VIS-AD program to halt at the breakpoint. 6. Click the STOP/GO icon again (VIS-AD will invoke the process that encapsulates your external function). 7. Quickly (you've got 20 seconds) enter a "ps -ef" command in another window to list all the processes running on your workstation. Look for the process with the name of your external function (under the COMD column) and read off the process ID of that process (under the PID column). Lets call that process ID "NNNN". 8. Enter the command "dbx -p NNNN". This will cause dbx to grab your external function. If you were quick enough, a "where" command to dbx will tell you that your external function is in the "sleep" function. Now you can debug normally. 9. If you were too late in Step 8, go back to Step 2 and change "sleep(20)" to something long enough to give you time to do Steps 7 and 8. 10. When you are done debugging, you may want to undo the changes in Step 1 and redo Step 3 to compile your external function with optimization. The C callable library functions are quite complex. They have lots of indirection in their arguments, and this is a common source of bugs. Also, navigating in complex VIS-AD data objects is difficult. Thus dbx may be necessary for developing external functions that call the C library functions. 9.3 Fortran-callable library functions The ADNARG function retrieves the number of arguments passed to the external function. The ADINT, ADREAL and ADCHAR functions retrieve the values of scalar arguments. ARGNUM is the number (between 1 and ADNARG) of the argument being retrieved. The ADVOL, ADIMG, ADIMLC, ADMB and ADARAY functions retrieve the values of array arguments. ADVOL, ADIMG and ADARAY retrieve the values in arrays indexed by "real3d", "real2d" and "real" (or "int") scalars - they also retrieve the dimensions of the array. ADMB retrieves values of arrays of tuples of scalars indexed by "real2d" scalars - these are appropriate types for multi-band images. Each call to ADMB retrieves one band of a multi- band image. ADIMLC retrieves the index values associated with the values retrieved by ADIMG or ADMB (e.g., it can retrieve the longitudes and latitudes of image pixels). The ADRTR, ADRTI and ADRTMS functions are called to return REAL, INTEGER and MISSING values. However, to return an array object an external function must modify one of its array object arguments. The index values of the returned array object (i.e., the sample set of index values) come from the argument, while the array object values come from arrays passed to ADRTVO, ADRTIM, ADRTAR or ADRTMB. ADRTVO, ADRTIM and ADRTAR are used to return arrays indexed by "real3d", "real2d" and "real" (or "int") scalars. ADRTMB is used to return multi-band 2-D images (and must be called once for each band of the image). ADRTVO, ADRTIM, ADRTAR and ADRTMB must reference the number of an argument (specified in ARGNUM) to the external function that is an array of the same dimension as the array being returned (the index sampling for the returned object is taken from this argument). ADRTVO, ADRTIM, ADRTAR and ADRTMB basically just substitute the values in IMAGE for the values in this argument array. The NLINES and NELES values in the calls to ADRTIM and ADRTAR must be the same as the NLINES and NELES of the arguments, and the sample set of the returned array object is the same as the sample set of the argument (see Sections 5.3 and 5.5 for a discussion of sample sets). Similarly for NELES in ADRTAR and NLEVS, NLINES and NELES in ADRTVO. Now we provide detailed information about the calling sequences of all the Fortran-callable library functions. INTEGER FUNCTION ADNARG() C GET NUMBER OF ARGUMENTS PASSED TO EXTERNAL FUNCTION C FUNCTION VALUE: C ADNARG = (INTEGER) NUMBER OF ARGUMENTS INTEGER FUNCTION ADINT(ARGNUM) INTEGER ARGNUM C GET INTEGER ARGUMENT PASSED TO EXTERNAL FUNCTION C INPUT: C ARGNUM = PARAMETER NUMBER ( BETWEEN 1 AND ADNARG() ) C FUNCTION VALUE: C ADINT = VALUE OF INTEGER ARGUMENT REAL FUNCTION ADREAL(ARGNUM) INTEGER ARGNUM C ADREAL - GET REAL ARGUMENT TO EXTERNAL FUNCTION C INPUT: C ARGNUM = ARGUMENT NUMBER ( BETWEEN 1 AND ADNARG() ) C FUNCTION VALUE: C ADREAL = VALUE OF REAL ARGUMENT REAL FUNCTION ADCHAR(ARGNUM, CTEXT) INTEGER ARGNUM CHARACTER*N CTEXT C ADCHAR - GET REAL ARGUMENT PASSED TO EXTERNAL FUNCTION C INPUT: C ARGNUM = ARGUMENT NUMBER ( BETWEEN 1 AND ADNARG() ) C OUTPUT: C CTEXT = VALUE OF STRING ARGUMENT (TRUNCATED IF LENGTH C IS GREATER THAN N, BLANK-FILLED IF LESS) C ( NOTE THAT N MAY BE ANY NON-NEGATIVE VALUE ) C FUNCTION VALUE: C ADCHAR = MINIMUM OF N & THE ACTUAL LENGTH OF STRING SUBROUTINE ADVOL(ARGNUM, SIZE, VALUES, * NLEVS, NLINES, NELES) INTEGER ARGNUM, SIZE, NLEVS, NLINES, NELES REAL VALUES(SIZE) C ADIMG - GET VOLUME ARGUMENT PASSED TO EXTERNAL FUNCTION C INPUT: C ARGNUM = ARGUMENT NUMBER ( BETWEEN 1 AND ADNARG() ) C SIZE = SIZE OF VALUES ARRAY C ( ERROR IF NLEVS*NLINES*NELES > SIZE ) C OUTPUT: C VALUES = ARRAY TO HOLD IMAGE DATA C NLEVS = NUMBER OF LEVELS OF DATA C NLINES = NUMBER OF LINES OF DATA C NELES = NUMBER OF PIXELS PER LINE SUBROUTINE ADIMG(ARGNUM, SIZE, VALUES, NLINES, NELES) INTEGER ARGNUM, SIZE, NLINES, NELES REAL VALUES(SIZE) C ADIMG - GET IMAGE ARGUMENT PASSED TO EXTERNAL FUNCTION C INPUT: C ARGNUM = ARGUMENT NUMBER ( BETWEEN 1 AND ADNARG() ) C SIZE = SIZE OF VALUES ARRAY C ( ERROR IF NLINES*NELES > SIZE ) C OUTPUT: C VALUES = ARRAY TO HOLD IMAGE DATA C NLINES = NUMBER OF LINES OF DATA C NELES = NUMBER OF PIXELS PER LINE SUBROUTINE ADIMLC(ARGNUM, SIZE, LOC1, LOC2, * NLINES, NELES) INTEGER ARGNUM, SIZE, NLINES, NELES REAL LOC1(SIZE), LOC2(SIZE) C ADIMG - GET INDEX VALUES FOR IMAGE ARGUMENT PASSED TO C EXTERNAL FUNCTION ( ARRAY IS INDEXED BY PAIRS OF C REAL NUMBERS, SUCH AS LONGITUDES & LATITUDES ) C INPUT: C ARGNUM = ARGUMENT NUMBER ( BETWEEN 1 AND ADNARG() ) C SIZE = SIZE OF VALUES ARRAY C ( ERROR IF NLINES*NELES > SIZE ) C OUTPUT: C LOC1 = ARRAY TO HOLD 1ST COMPONENTS OF ARRAY INDICES C LOC2 = ARRAY TO HOLD 2ND COMPONENTS OF ARRAY INDICES C NLINES = NUMBER OF LINES OF DATA C NELES = NUMBER OF PIXELS PER LINE SUBROUTINE ADMB(ARGNUM, BAND, SIZE, * VALUES, NLINES, NELES) INTEGER ARGNUM, BAND, SIZE, NLINES, NELES REAL VALUES(SIZE) C ADIMG - GET ONE BAND FROM A MULTI_BAND IMAGE ARGUMENT C PASSED TO EXTERNAL FUNCTION C INPUT: C ARGNUM = ARGUMENT NUMBER ( BETWEEN 1 AND ADNARG() ) C BAND = INDEX OF BAND C ( 1 FOR THE 1ST, 2 FOR THE 2ND, ETC ) C ( THIS IS NOT GOES BAND NUMBER ) C SIZE = SIZE OF VALUES ARRAY C ( ERROR IF NLINES*NELES > SIZE ) C OUTPUT: C VALUES = ARRAY TO HOLD IMAGE DATA C NLINES = NUMBER OF LINES OF DATA C NELES = NUMBER OF PIXELS PER LINE SUBROUTINE ADARAY(ARGNUM, SIZE, VALUES, NELES) INTEGER ARGNUM, SIZE, NELES REAL VALUES(SIZE) C ADARAY - GET 1-D ARRAY ARGUMENT PASSED TO C EXTERNAL FUNCTION C INPUT: C ARGNUM = ARGUMENT NUMBER ( BETWEEN 1 AND ADNARG() ) C SIZE = SIZE OF VALUES ARRAY C ( ERROR IF NELES > SIZE ) C OUTPUT: C VALUES = ARRAY TO HOLD DATA C NELES = NUMBER OF ELEMENTS OF DATA SUBROUTINE ADRTMS() C ADRTMS - SET MISSING AS FUNCTION RETURN VALUE SUBROUTINE ADRTR(VALUE) REAL VALUE C ADRTR - SET REAL NUMBER AS EXTERNAL FUNCTION RETURN VALUE C INPUT: C VALUE = REAL VALUE TO RETURN SUBROUTINE ADRTI(IVALUE) INTEGER IVALUE C ADRTR - SET INTEGER AS EXTERNAL FUNCTION RETURN VALUE C INPUT: C IVALUE = INTEGER VALUE TO RETURN SUBROUTINE ADRTVO(ARGNUM, VALUES, * NLEVS, NLINES, NELES) INTEGER ARGNUM, NLEVS, NLINES, NELES REAL VALUES(SIZE) C ADRTVO - REPLACE VALUES IN VOLUME ARGUMENT AND USE C AS EXTERNAL FUNCTION RETURN VALUE C INPUT: C ARGNUM = ARGUMENT NUMBER ( BETWEEN 1 AND ADNARG() ) C VALUES = ARRAY OF VALUES TO REPLACE IN VOLUME ARGUMENT C NLEVS = NUMBER OF LEVELS OF DATA C NLINES = NUMBER OF LINES OF DATA C NELES = NUMBER OF PIXELS PER LINE SUBROUTINE ADRTIM(ARGNUM, VALUES, NLINES, NELES) INTEGER ARGNUM, NLINES, NELES REAL VALUES(SIZE) C ADRTIM - REPLACE VALUES OF IMAGE ARGUMENT AND USE C AS EXTERNAL FUNCTION RETURN VALUE C INPUT: C ARGNUM = ARGUMENT NUMBER ( BETWEEN 1 AND ADNARG() ) C VALUES = ARRAY OF VALUES TO REPLACE IN IMAGE ARGUMENT C NLINES = NUMBER OF LINES OF DATA C NELES = NUMBER OF PIXELS PER LINE SUBROUTINE ADRTMB(ARGNUM, BAND, VALUES, * NLINES, NELES) INTEGER ARGNUM, BAND, NLINES, NELES REAL VALUES(SIZE) C ADRTIM - REPLACE ONE BAND OF MULTI-BAND IMAGE ARGUMENT C AND USE AS EXTERNAL FUNCTION RETURN VALUE C INPUT: C ARGNUM = ARGUMENT NUMBER ( BETWEEN 1 AND ADNARG() ) C BAND = INDEX OF BAND C ( 1 FOR THE 1ST, 2 FOR THE 2ND, ETC ) C ( THIS IS NOT GOES BAND NUMBER ) C VALUES = ARRAY OF VALUES TO REPLACE IN IMAGE ARGUMENT C NLINES = NUMBER OF LINES OF DATA C NELES = NUMBER OF PIXELS PER LINE SUBROUTINE ADRTAR(ARGNUM, VALUES, NELES) INTEGER ARGNUM, NELES REAL VALUES(SIZE) C ADRTIM - REPLACE VALUES OF 1-D ARRAY ARGUMENT AND USE C AS EXTERNAL FUNCTION RETURN VALUE C INPUT: C ARGNUM = ARGUMENT NUMBER ( BETWEEN 1 AND ADNARG() ) C VALUES = ARRAY OF VALUES TO REPLACE C IN 1-D ARRAY ARGUMENT C NELES = NUMBER OF ELEMENTS OF DATA SUBROUTINE ZDEST(CTEXT, LVAL) CHARACTER*N CTEXT INTEGER LVAL C ZDEST - PRINT TEXT STRING AND INTEGER FOR DEBUGGING C INPUT: C CTEXT = TEXT TO PRINT C ( NOTE THAT N MAY BE ANY NON-NEGATIVE VALUE ) C LVAL = INTEGER TO PRINT AFTER TEXT External functions may be written in C. Since C functions can call FORTRAN functions, an external function written in C may use the FORTRAN library functions listed above for picking up the arguments passed from the VIS-AD call, and for returning a value. Your C and FORTRAN manuals should tell you how to do this. The primary issues are that all arguments are passed by reference and an underscore is appended to the end of the function's name. Thus an external function written in C would pick up a 2-D image as its first argument using ADIMG as follows: int argnum, size, nlines, neles; real values[SIZE]; argnum = 1; size = SIZE; adimg_(&argnum, &size, values, &nlines, &neles); /* now nlines and neles contain the size of the image and the pixel values are in the values array */ 9.4 C-callable library functions There are a number of functions for picking up arguments to external functions that are callable only from C. These can deal with complex data types that do not conform to the types defined for the FORTRAN callable subroutines and functions listed above. These functions are quite complex - it is much easier to use the Fortran- callable functions if they match your data types. These functions allow you to get pointers to data objects, to find out the kind (i.e., scalar, tuple or array) of the object and its size (i.e., how many elements if it is a tuple or an array), to get pointers to sub-objects, to get numerical and strings values from objects, to change values in objects, and to build new scalar objects. If your external function returns a scalar object (i.e., an integer, real number, string, real2d or real3d) you can do this easily by calling the "ad_scalar_out" library function. However, returning complex data objects (i.e., arrays) is more difficult. You need to do one of two things: (1) Build a prototype for this object in your VIS-AD program and pass it as an argument to the external function - then call "ad_obj_out" to set it as the return value and call "ad_val_out" to insert new values in that object. (2) Call the functions "ad_make_array_complex", "ad_make_tuple_object" and "ad_make_array_obj" to build complex data objects, with sample sets that you define using the functions "ad_make_..._set". Then call "ad_obj_out" to set it as the return value and call "ad_val_out" to insert new values in that object. If you use approach (1) your VIS-AD program needs to define the index sample sets for any arrays in the object (note that you can pass a MISSING object as an argument, and the VIS-AD interpreter will expand all arrays in the object to arrays full of MISSING values, and with the default sample sets of the arrays' index scalar types). The "rdscan" and "read_gevents" external functions are examples of approach (1). Their source files are "rdscan.f" and "read_gevents.c" in the "funcs" directory. Approach (2) allows external functions to dynamically define the sizes of arrays in response to information they read from data files. The "rdssmi" external function is an example of approach (2). Its source file is "rdssmi.c" in the "funcs" directory. These C-callable library functions let external functions dynamically determine the data types of their arguments. Thus polymorphic external functions can be written in C (although the data types of values returned by external functions are fixed by the declarations of external functions in VIS-AD programs). Here is a brief description of the C-callable library functions: ad_num_params Get the number of arguments passed to an external function. ad_obj_param Get a pointer (of C type void *) to the data object passed a argument number param_index (between 0 and ad_num_params-1). Several of the C-callable library functions manipulate (void *) pointers to VIS-AD objects in order to navigate their internal structure (i.e. sub-objects) and discover their data type. ad_scalar Get the value of a scalar argument. ad_obj_scalar Get the value of a scalar object. ad_size_array Get the size, dimensionality and other information about an array argument. ad_obj_size_array Get the size, dimensionality and other information about an array object. ad_array_val Get the values of an array argument. ad_obj_array_val Get the values of an array object. ad_array_loc Get the index values of an array argument. ad_obj_array_loc Get the index values of an array object. ad_scalar_out Set a scalar value as the external function's return value. ad_set_obj_scalar Make a scalar object. ad_copy_obj Make a copy of an object. ad_param_out Set an argument as the external function's return value. ad_obj_out Set an object as the external function's return value. ad_val_out Set values in an array object that is the external function's return value. ad_obj_val_out Set values in an array object. ad_obj_kind Get the kind (i.e., scalar, tuple or array) of an object and the number of elements if it is a tuple or an array. ad_obj_tuple Get an element sub-object of a tuple object. ad_set_obj_tuple Set an element sub-object in a tuple object. ad_obj_array Get a value sub-object of an array object. ad_set_obj_array Set a value sub-object in an array object. ad_make_array_obj Make a simple array object (i.e., an array of scalars or an array of tuples of scalars). ad_make_array_complex Make a complex array object (i.e., not an array of scalars or an array of tuples of scalars). ad_make_tuple_obj Make a tuple object. ad_make_linear_set Make a linear sample set (see Section 5.3). ad_make_product_set Make a product sample set (see Section 5.3). ad_make_nav_set Make a sample set based on the image navigation of a McIDAS area file (see Section 5.3). ad_make_float_set Make a sample set of floating point values (see Section 5.3). When the library functions return values to arguments with declarations like "int *type;" they assume that "type" points at an appropriate memory location to receive an integer value. Similarly, when the library function return arrays to arguments with declarations like "void *val[];" they assume that "val" points at a pointer which will receive the location of the array. For example, "ad_scalar" might be called as follows: int param_index = 0; void *val; int type, status; status = ad_scalar(param_index, &val, &type); Now we provide detailed information about the calling sequences of all the C-callable library functions. /* this is the list of return values for these functions: 1 missing value (this is not an error) 0 normal return -1 invalid param_index -2 invalid tuple_index -3 invalid array_index -4 bad object type -5 array size too small -9 bad sample set -10 other error */ int ad_num_params(num) int *num; /* ad_num_params - get number of arguments passed to external function input: num = number of arguments */ ad_obj_param(param_index, pobj) int param_index; void *pobj[]; /* ad_obj_param - get object that is external function argument input: param_index = number of argument ( between 0 and ad_num_params-1 ) output: pobj = pointer to pointer to object */ int ad_scalar(param_index, val, type) int param_index; void *val[]; int *type; /* ad_scalar - get the value of a scalar argument passed to external function input: param_index = index of argument ( between 0 and ad_num_params-1 ) output: val = pointer to returned value array *type = 1 for real or int = 2 for real2d = 3 for real3d = 4+length for string */ int ad_obj_scalar(pobj, val, type) void *pobj; void *val[]; int *type; /* ad_obj_scalar - get the value of scalar object pobj input: pobj = pointer to scalar object output: val = pointer to returned value array type = 1 for real or int = 2 for real2d = 3 for real3d = 4+length for string */ int ad_size_array(param_index, size, num_dim, sizes, num_tuples) int param_index; int *size; int *num_dim; int sizes[3]; int *num_tuples; /* ad_size_array - get information about the size of an array object that is an argument to external function input: param_index = index of argument ( between 0 and ad_num_params-1 ) output: size = number of sub-objects in array num_dim = number of dimensions of array index ( 0 if the index is string ) sizes = array of sizes in each dimension of index num_tuples = number of elements in tuple sub-objects this is 1 if sub-objects are scalars */ int ad_obj_size_array(pobj, size, num_dim, sizes, num_tuples) void *pobj; int *size; int *num_dim; int sizes[3]; int *num_tuples; /* ad_obj_size_array - get information about the size of array object pobj input: pobj = pointer to array object output: size = number of sub-objects in array num_dim = number of dimensions of array index ( 0 if the index is string ) sizes = array of sizes in each dimension of index num_tuples = number of elements in tuple sub-objects this is 1 if sub-objects are scalars */ int ad_array_val(param_index, tuple_index, size, val, type, miss) int param_index; int tuple_index; int *size; void *val[], *miss[]; int *type; /* ad_array_val - get values from an array object that is an argument to external function input: param_index = index of argument ( between 0 and ad_num_params-1 ) tuple_index = index of tuple in array ( between 0 and num_tuples-1 ) output: size = number of array values returned val = pointer to returned value array type = 1 for real or int val is an array of [size] floats = 2 for real2d val is 2 consecutive arrays of [size] floats = 3 for real3d val is 3 consecutive arrays of [size] floats = 4+length (in bytes) for string val is an array of [size] strings of [length] bytes each miss = pointer to returned array of missing data flags ( one byte per flag ) */ int ad_obj_array_val(pobj, tuple_index, size, val, type, miss) void *pobj; int tuple_index; int *size; void *val[], *miss[]; int *type; /* ad_obj_array_val - get values from array object pobj input: pobj = pointer to array object tuple_index = index of tuple in array ( between 0 and num_tuples-1 ) output: size = number of array values returned val = pointer to returned value array type = 1 for real or int val is an array of [size] floats = 2 for real2d val is 2 consecutive arrays of [size] floats = 3 for real3d val is 3 consecutive arrays of [size] floats = 4+length (in bytes) for string val is an array of [size] strings of [length] bytes each miss = pointer to returned array of missing data flags ( one byte per flag ) */ int ad_array_loc(param_index, size, val, type) int param_index; int *size; void *val[]; int *type; /* ad_array_loc - get indices for an array object that is an argument to external function input: param_index = number of argument (between 0 and ad_num_params-1 ) output: size = number of array indices returned val = pointer to returned index array type = 1 for real or int val is an array of [size] floats = 2 for real2d val is 2 consecutive arrays of [size] floats = 3 for real3d val is 3 consecutive arrays of [size] floats = 4+length (in bytes) for string val is an array of [size] strings of [length] bytes each */ int ad_obj_array_loc(pobj, size, val, type) void *pobj; int *size; void *val[]; int *type; /* ad_obj_array_loc - get indices of array in object pobj input: pobj = pointer to array object output: size = number of array indices returned val = pointer to returned index array type = 1 for real or int val is an array of [size] floats = 2 for real2d val is 2 consecutive arrays of [size] floats = 3 for real3d val is 3 consecutive arrays of [size] floats = 4+length (in bytes) for string val is an array of [size] strings of [length] bytes each */ int ad_scalar_out(val, type, smiss) void val[]; int type; char smiss; /* ad_scalar_out - set a scalar as external function return value input: val = value array type = 1 for real or int = 2 for real2d = 3 for real3d = 4+length for string smiss = missing flag (1 if MISSING, 0 if not) */ int ad_set_obj_scalar(val, type, smiss, nobj) void val[]; int type; char smiss; void *nobj[]; /* ad_set_obj_scalar - create a scalar object input: val = value array type = 1 for real or int = 2 for real2d = 3 for real3d = 4+length for string smiss = missing flag output: nobj = pointer to pointer to created object */ ad_copy_obj(pobj, nobj) void *pobj, *nobj[]; /* ad_copy_obj - return a copy of pobj in nobj input: pobj = pointer to object output: nobj = pointer to pointer to created object */ ad_param_out(param_index) int param_index; /* ad_param_out - set an argument of external function as its return value; the values in this object may be modified by ad_val_out input: param_index = number of argument ( between 0 and ad_num_params-1 ) */ ad_obj_out(pobj) void *pobj; /* ad_obj_out - set object pobj as external function return value; the values in this object may be modified by ad_val_out input: pobj = pointer to object */ int ad_val_out(tuple_index, val, type, miss) int tuple_index, type; void val[]; char miss[]; /* ad_val_out - substitute values in array return object (the return object must have been already set by a call to ad_param_out or ad_obj_out) (if it is an array of tuples then only one tuple element is updated in each array element) (num_tuples and size are as returned by ad_size_array or ad_obj_size_array) input: tuple_index = number of tuple element in array ( between 0 and num_tuples-1 ) val = value array type = 1 for real or int val is an array of [size] floats = 2 for real2d val is 2 consecutive arrays of [size] floats = 3 for real3d val is 3 consecutive arrays of [size] floats = 4+length (in bytes) for string val is an array of [size] strings of [length] bytes each miss = array of [size] missing data flags */ int ad_obj_val_out(pobj, tuple_index, val, type, miss) void *pobj; int tuple_index, type; void val[]; char miss[]; /* ad_obj_val_out - substitute values in array object pobj (if it is an array of tuples then only one tuple element is updated in each array element) (num_tuples and size are as returned by ad_size_array or ad_obj_size_array) input: pobj = pointer to object tuple_index = number of tuple in array ( between 0 and num_tuples-1 ) val = value array type = 1 for real or int val is an array of [size] floats = 2 for real2d val is 2 consecutive arrays of [size] floats = 3 for real3d val is 3 consecutive arrays of [size] floats = 4+length (in bytes) for string val is an array of [size] strings of [length] bytes each miss = array of [size] missing data flags */ ad_obj_kind(pobj, kind, size) void *pobj; int *kind, *size; /* ad_obj_kind - get kind and size of object pobj input: pobj = pointer to object output: kind = 0 for missing = 1 for scalar = 2 for tuple = 3 for array = -1 for error size = number of sub-objects */ ad_obj_tuple(pobj, tuple_index, nobj) void *pobj, *nobj[]; int tuple_index; /* ad_obj_tuple - get an element sub-object of tuple pobj input: pobj = pointer to tuple object tuple_index = index of element ( between 0 and num_tuples-1 ) output: nobj = pointer to pointer to element sub-object */ ad_set_obj_tuple(pobj, tuple_index, nobj) void *pobj, *nobj; int tuple_index; /* ad_set_obj_tuple - set an element sub-object of tuple pobj input: pobj = pointer to tuple object tuple_index = index of element ( between 0 and num_tuples-1 ) nobj = pointer to new element sub-object */ ad_obj_array(pobj, array_index, nobj) void *pobj, *nobj[]; int array_index; /* ad_obj_array - get a sub-object of array pobj input: pobj = pointer to array object array_index = index of array element output: nobj = pointer to pointer to array sub-object */ ad_set_obj_array(pobj, array_index, nobj) void *pobj, *nobj; int array_index; /* ad_set_obj_array - set a sub-object of array pobj input: pobj = pointer to array object array_index = index of array element nobj = pointer to new array sub-object */ ad_make_array_obj(dset, rset, num_tuples, nobj) void *dset, *rset[]; void *nobj[]; int num_tuples; /* ad_make_array_obj - make a new array object (it is an array of scalars if num_tuples = 1, and an array of tuples of scalars if num_tuples > 1); it is filled with MISSING values input: dset = pointer to sample set for array domain rset = pointer to array of num_tuples sample sets for array values num_tuples = number of elements in array sub-objects output: nobj = pointer to pointer to new array object */ ad_make_array_complex(dset, pobj, nobj) void *dset; void *pobj[], *nobj[]; /* ad_make_array_complex - make a new array object by combining the sub-objects in pobj input: dset = pointer to sample set for array domain pobj = array of pointers to sub-objects; number of objects in pobj must be the same as the size of dset; the objects in pobj must not be scalars or tuples of scalars (use ad_make_array_obj in that case) output: nobj = pointer to pointer to new array object */ ad_make_tuple_obj(num_tuples, pobj, nobj) int num_tuples; void *pobj[], *nobj[]; /* ad_make_tuple_obj - make a new tuple object by combining the element objects in pobj input: pobj = array of pointers to element objects num_tuples = number of elements in array sub-objects output: nobj = pointer to pointer to new tuple object */ ad_make_linear_set(size, start, last, nset) int size; float start, last; void *nset[]; /* ad_make_linear_set - make a new linear sample set (an arithmetic progression of values) input: size = number of samples in set start = first sample value last = last sample value output: nset = pointer to pointer to new sample set */ ad_make_product_set(dim, fset1, fset2, fset3, nset) int dim; void *fset1, *fset2, *fset3; void *nset[]; /* ad_make_product_set - make a new product sample set (product of linear sets) input: dim = number of factors (dimension, must be 2 or 3) factor1 = first linear_set factor factor2 = second linear_set factor factor3 = third linear_set factor (or NULL) output: nset = pointer to pointer to new sample set */ ad_make_nav_set(area, line, elem, nlines, nelems, lres, eres, nset) int area, line, elem, nlines, nelems, lres, eres; void *nset[]; /* ad_make_nav_set - make a new McIDAS navigation sample set input: area = number of McIDAS area file used as source of satellite navigation line = line offset from McIDAS area elem = element offset from McIDAS area nlines = number of lines in image nelems = number of elements in image lres = factor by which to sub-sample area file in line direction eres = factor by which to sub-sample area file in element direction output: nset = pointer to pointer to new sample set */ ad_make_float_set(nset) void *nset[]; /* ad_make_float_set - make a new float sample set output: nset = pointer to pointer to new sample set */ 9.5 Useful external functions Here we present descriptions of a few useful external functions distributed with the VIS-AD system. There will be more generally useful external functions in future versions of VIS-AD. temp_image irtemp(ir_image a;) ------------------------------ type loc = real2d; type ir = real; type temp = real; type ir_image = array [loc] of ir; type temp_image = array [loc] of temp; return a temp_image containing temperatures (in Kelvin) calculated from the GOES infrared radiances in "a"; the temperatures are between 163.0K and 330.0K in half degree steps; the source code for "irtemp" is in the file "irtemp.f" in the "funcs" directory vis_image albedo(val iarea, nlines, nelems, line, elem; vis_image a;) ------------------------------------------------------- type loc = real2d; type vis = real; type val = int; type vis_image = array [loc] of vis; return a "vis_image" of albedos derived from the GOES visible channel radiances in "a" by normalizing for sun angle; the visible radiances in "a" come from McIDAS area file (image file) number "iarea", offset by "line" and "elem" and of size "nlines" by "nelems"; sun angles are derived from the Earth navigation for "iarea"; the albedos are between 0.0 and 100.0; the source code for "albedo" is in the file "albedo.f" in the "funcs" directory mkimg(image im; num iarea, isarea; val offset, scale; num inv;) ----------------------------------------------------- type loc = real2d; type num = int; type val = real; type vis_image = array [loc] of val; write the "im" array to McIDAS area file number "iarea", copying the image navigation from McIDAS area file number "isarea"; before writing the value in "im" to the McIDAS area file, add "offset" to each pixel and then multiply by "scale"; if "inv" is 1, then invert the order of lines before writing to the McIDAS area file (i.e., flip North and South) - this is useful if the data in "im" have been remapped to a latitude/longitude perspective (i.e., latitude increases from South to North, whereas lines in McIDAS area files are ordered from North to South) The system includes several external functions for reading data from various file formats used by the demo programs. While it is unlikely that these functions will be useful to you in their current form, they may be useful as models for writing your own external functions for reading data. These functions are (their source files are in the "funcs" directory): function name demo program source file ------------- ------------ ----------- rdscan radar.v rdscan.f rdssmi ssmi.v rdssmi.c read_gevents dxs.v read_gevents.c read_mintime dxs.v read_mintime.c read_maxtime dxs.v read_maxtime.c ********** 10. THE FUTURE ********** There are lots of ways we'd like to improve VIS-AD, and we will make these improvements as our time and as financial support allow. Here is a list of some of the things we'd like to do: Port VIS-AD to more different kinds of workstations. Add intrinsic and external fucntions for reading data from (and writing data to) a wide variety of different scientific file formats (e.g., HDF, netCDF). We are hopeful that our user community will contribute external functions for reading various data formats. Add support for a greater variety of sample set formats. Add true volume rendering (VIS-AD already includes a VOLUME icon for controlling this, and we have already implemented volume rendering in our VIS-5D system). Add flow rendering (our VIS-5D system already implements flow rendering). Add support for immediate mode commands - these would have the same syntax as statements in the VIS-AD programming language but could be typed in and executed at any time. Expand the on-line help window to help users diagnose problems with program execution or data displays. Simplify the process of writing external functions. Expand the ways that VIS-AD exploits parallelism to give users faster response times. Expand the VIS-AD data model to support recursively defined data types (i.e., data types implemented with pointers). This would also entail an expansion of the VIS-AD display model. We are also interested to hear suggestions about improvements from our users. Send these via email to whibbard@macc.wisc.edu. ******************* 11. COPYRIGHT STATEMENT ******************* VIS-AD (VISualization for Algorithm Development) Copyright (C) 1992-1994 Bill Hibbard, Brian Paul This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *************** 12. ACKNOWLEDGMENTS *************** This work was supported by NASA grant NAG8-828, and by the National Science Foundation and the Defense Advanced Research Projects Agency under Cooperative Agreement NCR- 8919038 with the Corporation for National Research Initiatives. The work was performed at the University of Wisconsin-Madison Space Science and Engineering Center. We also must thank the early users of VIS-AD: Bob Rabin, Wilt Sanders, Dick Edgar, Roland Stull, Mike Botts and Chris Crosiar. They helped us to understand how the system would be used in real applications and helped us find bugs and user interface problems. We also want to thank the authors of several software packages that are included with VIS-AD (these packages are not covered by the VIS-AD copyright statement). They are: 1. Parts of the LINPACK and BLAS libraries for linear algebra. These packages are in the public domain. We obtained them by anonymous ftp from netlib.att.com. 2. The DTM package for socket communications, produced by the National Center for Supercomputer Applications. This package is in the public domain. We obtained it by anonymous ftp from ftp.ncsa.uiuc.edu. 3. The LUI package of user interface widgets, produced by Stellar Computer Inc. This package is Stellar proprietary, but we use it by their permission. We originally obtained this package with AVS Version 1.0, and have made extensive modifications to it. 4. The McIDAS library for accessing McIDAS data files, produced by the Space Science and Engineering Center. This package is distributed as library files (libmcidas4.a and libmcidas5.a), but not as source code. It is covered by the following statement: This software is the property of the University of Wisconsin-Madison, Space Science and Engineering Center (SSEC). It is intended for the free use of the scientific community. It is not to be decompiled or remarketed or sold without the express written permission of the SSEC. ********** 13. REFERENCES ********** Hibbard, W., C. Dyer and B. Paul, 1992; Display of scientific data structures for algorithm visualization. Visualization '92, Boston, IEEE, 139-146. Hibbard, W., C. Dyer and B. Paul, 1992; Using VIS-AD to visualize a cloud discrimination algorithm. Video proceedings of Visualization '92, Boston, IEEE. Hibbard, W. L., B. E. Paul, D. A. Santek, C. R. Dyer, A. L. Battaiola, and M-F. Voidrot-Martinez 1994; Interactive visualization of computations in the Earth and space sciences. IEEE Computer, July special issue on scientific visualization, Accepted for Publication. Hibbard, W., C. Dyer and B. Paul, 1994; A lattice model for data display. IEEE Visualization '94. Accepted for Publication.