Cumulvs Viewer Notes


Introduction:  CUMULVS is a middle ware library for the extraction of data from distributed applications. It provides a global view of distributed data and a protocol for coordinating data extraction and parameter steering in a running, distributed application. There are two parts to this software library. One part is used to instrument an application. The routines in this part are imbedded in an application and are used to identify which arrays and arameters are to be viewed or steered and how the data is distributed. This part of the library is fairly well documented in the CUMULVS distribution documents. The other part of the library is used to construct VIEWER programs which communicate with an application and are "where the data goes" when it is extracted from the running application. This part of the library is... well not really documented very well at all. I have, over the course of several months and through my good fortune of being personal friends with the developers, become familiar enough with the viewer portion of the library to attempt to write this guide to constructing cumulvs viewers.  For further information one can consult the source code, or several viewer examples included in the examples subdirectory of the CUMULVS distribution.;



 

Data Extraction:

Viewer programs are typically event driven beasts. Event driven programming is a little different then the sequential coding scientific programmers are used to. One way to describe this concept is to describe what a "CUMULVS session"
might be like. Imagine you have an application running and that you want to examine the data that is being produced. The first thing one would do (after
starting the viewer program) would be to tell the viewer to "attach" to the application. CUMULVS uses a user defined application name to identify which application to attach to. During attachment information about the application is returned to the viewer program. This information consists of the names and global bounds of variable arrays that have been designated for viewing in the instrumentation phase ( using the first part of the library) and other information such as parameters that might be steered. The viewer program must present this information to the user in some way. The user then selects which fields (variable arrays) they wish to view and what the extent of those arrays are. Once this is done the viewer communicates this information to the application in the form of a view field request. If this is successfull the viewer makes repeated calls to "receive frames" from the application. This is all pretty sequential up to now. At this point we are receiving visualization information and rendering it at regular intervals. This is not the end of the story. CUMULVS viewers can be written to change the extent or the variable that is being viewed. This is where the event model comes in. The viewer software must check for an event and act accordingly. This mechanism can be in the form of a loop (see viewer.c in the examples directory of the distribution) or one can write event handlers and bind them to window events such as button clicks or mouse movements (see the slicer tcl application incluuded in the distribution). An event can be a change in scalar field or a change in the visualization region. There are several issues that a viewer program must address for various events. For example, memory must be allocated before a visualization frame can be received. The size of a visualization region can be changed on the fly. If the region size is increased the memory allocation must be adjusted before more frames are received. If a non-blocking receive is being done and the visualization region size is changed before the receive is complete the frame will be inconsistent and should be discarded. All these issues must be delt with by the programmer. The remainder of this document will describe how that is done.

Stvtcl loadable package of cumulvs TCL commands:

Jim Kohl ( oak ridge cumulvs developer) and I have put together a loadable package of TCL commands which allow one to access CUMULVS functionality from inside a TCL script. This makes it much easier to construct TCL based GUI interfaces for viewer programs. I was inspired to do this by Jim's slicer CUMULVS viewer application. This application is part of the CUMULVS distribution. It is built as a stand alone TCL program. It comes with a nice assortment of TCL extension commands. These commands are somewhat tuned to the special needs of the slicer program itself and I wanted something more general. I also wanted to build the commands as a loadable package so any TCL script could incorporate them without having to build a special interpreter. I consulted Jim Kohl many times during the construction of the package and it is really a joint effort between myself and Jim. Many thanks to him for the help.
This is, of course, BETA software. The usual caveats apply. Feel free to use it and please send me mail if you find problems with it. This section is sort of a man page for cumulvs tcl commands provided in the stvtcl.so loadable package.  To use the package one would include a line like, "load /home/person/lib/Stvtcl.so", at the beginning of their tcl script. This allows access to the following new tcl commands.
 
STVattach -  Attach to application. This command is used to attach a viewer to an application. It has two arguments. The first is the application name and the second is the returned set of field names associated with this application.  The field names are available from the STV_VIEWER data structure (as are all the other data) and this command need not return the field list, however I thought it was convenient to do it here reather than provide another command to collect the field list. The field list is a tcl list and can be accessed as such.
 
example:
set appname spoot_app;          # set the application name
STVattach $appname fieldlist;  # attach and retrieve the field list
foreach i $fields {
        puts $i;                                 # spit out the field names
 }
The example above shows the use of STVattach. The foreach loop just shows how to access the field list. In the vtk slicer example the field list is used to make a radio button selection dialog.

STVselectfield -  "Select a field". The STVslectfield command is used to select a field for viewing. Selecting the field just marks the selected flags in the appropriate data structure. This is part of constructing a view field group which then must be explicitly requested. A view field group may contain several fields each of which is selected by calling STVselectfield. STVselectfield clears any previous selections.
 

example:
set  field  "density  pressure";                    # some field I made up
STVselectfield $field;                                # mark the field
The above example shows how to select some fields. It is given here that the field names are known and hard coded into the field variable. A call to STVselectfield clears all previously selected fields. The command returns the name of the last field found or "field not found" if it cant find any of the names you request.

STVaddvfg -  Make a view field group request. This call makes the view field group request. Once this call is made you are ready to receive frames. It takes no arguments but returns an integer field group id. This fgid is used later to identify a particular vfg in a collection of vfgs. For example one might want to change a vis region on a particular view field group out of several possible groups. STVaddvfg allocates memory for this particular group. Other functions are provided to access this memory.
 

example:
 
set fgid [STVaddvfg]
 
Not much to this example. Once the fields are "selected" in the data structure they are requested by the STVaddvfg call.

STVdelvfg - If you can add 'em you gotta be able to delete 'em too. STVdelvfg also whacks any memory associated with this group. It also sends a halt message to the application to tell it to stop sending data.
 

example:
 
STVdelvfg $fgid;   # fgid contains the field group id of this vfg
 
Again, not much to say. This is a good example of why we want to keep track of field group ids. Remember that the memory associated with this vfg is gone so dont try to ref it.

STVreceiveframe - This command does the actual receiving of the data and is called after calling the other setup routines defined above. This command checks to see if the viewer is connected and it is ok to receive a frame of data. The latter condition is only met if there is memory for the data to be received into. The command takes no arguments but returns the field group id of the vfg it receives if a complete frame is received. Otherwise it returns a StatusIncomplete or BadFrame. It is a nonblocking call at this time.
 

example:
 
set result [STVreceiveframe];   #receive it
if { $result != StatusIncomplete } {
      render $result;                       #rendering procedure
}
This example shows how to receive a frame of data. The above commands are imbedded into a loop. The STVreceiveframe call sends an xon to the application as required. It is assumed that "render" knows what to do in the instance of each result.

STVsetvisregion - This routine is used to reset the visualization region bounds. Calling this routine reallocates the memory for the view field group. The pointer to the data will change and should be treated as volitile.

example:
set xlb 1
set ylb 1
set zlb 1
set xub 10
set yub 10
set zub 1
eval "STVsetvisregion $fgid $xlb $xub $ylb $yub $zlb $zub"
The arguments to STVsetvisregion are the field group id, fgid, and the lower and upper bounds of the visualization region in each coordinate direction.

STVsetviscell - This routine sets the stride size in each of the coordinate directions. It also reallocates the memory for the view field group with the same results as for the setvisregion call. Be carefull how you use the pointer to the data.
 

example:
set xcell 1
set ycell 2
set zcell 3
eval "STVsetviscell $fgid $xcell $ycell $zcell"
Similar to the vis region call this call takes a group id and the cell sizes in each of the coordinate directions. Again be carefull how you use the data.

Here is the code for building the CUMULVS tcl commands.

//# Copyright (C) 1995 Board of Trustees of the University of Illinois
//#
//# This software, both binary and source, is copyrighted by The
//# Board of Trustees of the University of Illinois.  Ownership
//# remains with the University.  You should have received a copy 
//# of a licensing agreement with this software.  See the file 
//# "COPYRIGHT", or contact the University at this address:
//#
//# 	Visualization & Virtual Environments
//# 	National Center for Supercomputing Applications
//# 	University of Illinois
//# 	405 North Mathews Ave.
//# 	Urbana, IL 61801

/* 
   c declarations for stvTcl package

*/

#include "Stvtcl.h"


int Stvtcl_Init(Tcl_Interp *interp) 
{

/* Initialize some cumulvs global stuff.. */


    STVTCL_VIEWER      = (STV_VIEWER) NULL;
    STVTCL_VFG         = (STV_VIEW_FIELD_GROUP) NULL;
    STVTCL_AVFG        = (STV_VIEW_FIELD_GROUP) NULL;
    STVTCL_CURRENT_VFG = (STV_VIEW_FIELD_GROUP) NULL;
    STVTCL_VF          = (STV_VIEW_FIELD) NULL;
    STVTCL_F           = (STV_FIELDPTR) NULL;
    stv_init_region(STVTCL_VISREGION);
    stv_init_cell(STVTCL_VISCELL);
    STVTCL_RESTART     = stvFalse;
    STVTCL_freq        = 1;
    STVTCL_no_sync     = 0;

/*  stv_setopt(stvOptNull, stvOptVerbose, stvTrue); */
/*  enrole in pvm and get tid */

   stv_myid(&STVTCL_MYTID);
   if(STVTCL_MYTID < 0 )
   {
     return(TCL_ERROR);
   }

/* create some commands */

    Tcl_CreateCommand(interp,"STVattach",attach_to_app, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp,"STVselectfield",select_view_field, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp,"STVsetvisregion",set_vis_region, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp,"STVsetviscell",set_vis_cell, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp,"STVsetvisfreq",set_vis_freq, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp,"STVaddvfg",add_view_field_group, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp,"STVdelvfg",del_view_field_group, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp,"STVreceiveframe",receive_frame, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp,"STVsteering",steer, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);



    return(TCL_OK);
}


/* command definitions */

int steer(ClientData clientdata, Tcl_Interp *interp, int argc, char *argv[])
{
        STV_VIEW_PARAM VP;
        STV_PARAMPTR P;
        double dval;
        int newsteer;
        int changed;
        int nsteer;
        int rcount;
        int count;
        int cc;
        int i;
        char *varValue;

        /* Initialize Steered Param Count */

        newsteer = 0;

        nsteer = 0;
        VP = STVTCL_VIEWER->vparams;
        rcount = 0;
        count = 0;
        while ( VP != NULL )
        {
             if ( VP->token == stvSteerNot )
                {
                        printf( "\t%s\n", VP->param->name );
                    varValue = Tcl_SetVar(interp,argv[1],VP->param->name,TCL_LIST_ELEMENT|TCL_APPEND_VALUE);

                    count++;
                }
                else
                    rcount++;
                VP = VP->next;
        }
        interp->result = varValue;

        return(TCL_OK);
}

int receive_frame(ClientData clientdata, Tcl_Interp *interp, int argc, char *argv[])
{
   int cc;
   if(STVTCL_CONNECTED && !STVTCL_RESTART) {

      if( STVTCL_GET_FRAME == stvTrue)
         cc = stv_viewer_receive_frame( &STVTCL_VFG, STVTCL_VIEWER, &STVTCL_RESTART,stvFalse );
      else
         cc = stvStatusIncomplete;
  
      if( cc == stvStatusOk || cc == stvStatusBadFrame ) {
         stv_viewer_send_XON( STVTCL_VFG, STVTCL_VIEWER );
         if(cc == stvStatusBadFrame)
            interp->result = "BadFrame";
         else
            sprintf(interp->result,"%d",STVTCL_VFG->fgid);
      } else
         interp->result = "StatusIncomplete";
   }
   return(TCL_OK);
}

int del_view_field_group(ClientData clientdata, Tcl_Interp *interp, int argc, char *argv[])
{
      int fgid; 
      STV_VIEW_FIELD_GROUP avfg;
      Tcl_GetInt(interp,argv[1],&fgid);
      avfg = stv_get_view_field_group_id(STVTCL_VIEWER->vfglist,STVTCL_VIEWER->nvfgs,fgid);
       if( avfg != NULL) {
              stv_viewer_send_FieldHaltGroup(avfg,STVTCL_VIEWER);                   // Send the field halt group
              stv_viewer_release_field(&avfg,STVTCL_VIEWER);                        // Release this view field group 
              return(stvStatusOk);
           }

}
int add_view_field_group(ClientData clientdata, Tcl_Interp *interp, int argc, char *argv[])
{
    
    STVTCL_AVFG = stv_viewer_request_field(STVTCL_VIEWER,STVTCL_VISREGION,STVTCL_VISCELL,STVTCL_freq,STVTCL_no_sync,&STVTCL_RESTART);
    if(STVTCL_AVFG != NULL) {
       if( allocate_vfg_memory(STVTCL_AVFG) != stvFalse) {
           STVTCL_GET_FRAME = stvTrue;
           sprintf(interp->result,"%d",STVTCL_AVFG->fgid);
       } else {
           STVTCL_GET_FRAME = stvFalse;
       }
    } else {
      STVTCL_GET_FRAME = stvFalse;
      interp->result = "bad vfg request";
      return(TCL_OK);
    }
    return(TCL_OK);
}

int
allocate_vfg_memory(STV_VIEW_FIELD_GROUP avfg)
{
int state,i,nfields;
/* allocate memory for the vfg */
   if(avfg != NULL) {
      nfields = avfg->nvfields;
      for(i=0;ivfglist,STVTCL_VIEWER->nvfgs,fgid);
    stv_viewer_set_VisRegion( avfg, STVTCL_VIEWER,STVTCL_VISREGION, STVTCL_VISCELL );
    nfields = avfg->nvfields;
      for(i=0;ivfglist,STVTCL_VIEWER->nvfgs,fgid);
    stv_viewer_set_VisRegion( avfg, STVTCL_VIEWER,STVTCL_VISREGION, STVTCL_VISCELL );
    nfields = avfg->nvfields;
      for(i=0;ivfglist,STVTCL_VIEWER->nvfgs,fgid);
    stv_viewer_set_VisFrequency( avfg, STVTCL_VIEWER, STVTCL_freq );
    return(TCL_OK);
}

int select_view_field(ClientData clientdata, Tcl_Interp *interp, int argc, char *argv[])
{
/* select a number of fields by flipping the appropriate switches in the 
   view field strucure. The actual view field request is made elsewhere.
*/
   int nfields;
   int i;
   stv_clear_view_field_select(STVTCL_VIEWER);         /* clear the current selections */
   nfields = argc - 1;
   STVTCL_VF = (STV_VIEW_FIELD) NULL;
   for(i=1;i<=nfields;i++) {                           /* find the view field pointer by name */
      STVTCL_VF = stv_get_view_field_name(argv[i],STVTCL_VIEWER->vfields,STVTCL_VIEWER->nvfields);
      if(STVTCL_VF != (STV_VIEW_FIELD) NULL) {
         STVTCL_VF->selected = stvTrue;
         STVTCL_VF->view_type = stvFloat;
      }
   }
   if(STVTCL_VF != NULL) {
      interp->result = STVTCL_VF->field->name;  /* return the name of the last field found */
   } else
      interp->result = "field not found";  
   return(TCL_OK);
      
}

int attach_to_app(ClientData clientdata, Tcl_Interp *interp, int argc, char *argv[])
{
   char *varValue;

   if(STVTCL_CONNECTED) {
     if ( STVTCL_AVFG != NULL ) 
        stv_viewer_send_FieldHaltAll(STVTCL_AVFG,STVTCL_VIEWER);
     STVTCL_CONNECTED = stvFalse;
   }

   STVTCL_INITIALIZED = (stv_viewer_init(&STVTCL_VIEWER,STVTCL_MYTID,argv[1],stvFalse) == stvStatusOk);

   if(STVTCL_INITIALIZED) {
/*    get the field list and pass it back to the application. 
      the command should be used like this: STVattach appname fieldlist, where fieldlist is the 
      tcl variable that will contain the list of fields. 
*/
      STVTCL_F = STVTCL_VIEWER->fields;
      while(STVTCL_F != NULL) {     
         varValue = Tcl_SetVar(interp,argv[2],STVTCL_F->name,TCL_LIST_ELEMENT|TCL_APPEND_VALUE);
         STVTCL_F = STVTCL_F->next;
         if(varValue == NULL) {
           return(TCL_ERROR);
         }
      }

      STVTCL_CONNECTED = stvTrue;
      interp->result = "CONNECTED";
   } else {
     STVTCL_CONNECTED = stvFalse;
     interp->result = "FAILED";
   }
   return(TCL_OK);
}