Creating a new geostatistics plug-in for S-GeMS

New (geostatistics) algorithms can be added by writing a plug-in. The plug-in is composed of two elements: a shared library ( ".so" file on Unix, ".dll" file on Windows) and a textual description of the graphical interface used to prompt for parameters (".ui" file).
Defining a plug-in involves two steps:
The first step in writing an algorithm plugin is to define a new class deriving from class Geostat_algo and to redefine the three virtual functions initialize, execute and name. The initialize function receives the parameters input by the user and initializes the parameters of the algorithm. These parameters are usually stored as member variables of the algorithm class. The execute function runs the algorithm using the parameters  previously initialized. The name function specifies the name of the algorithm.
Experience has shown that it is more practical to proceed in the following order:
Throughout this text the example of a moving average algorithm will be used. The class for the moving average algorithm is defined as follows:





#include <GsTLAppli/geostat/geostat_algo.h>

class MovingAverage : public Geostat_algo {
 public:
  virtual bool initialize( const Parameters_handler* parameters,
                           Error_messages_handler* errors );

  // Runs the algorithm.
  virtual int execute() ;
 
  // Tells the name of the algorithm
  virtual std::string name() const { return "moving_average"; }

 public:
  static Named_interface* create_new_interface(std::string&);

 
 private:
  // declare here all the parameters used by the algorithm
  // (i.e. the execute function)
};






The execute function

The execute function implements the algorithm itself. It returns an integer indicating whether the algorithm succeeded (returns 0) or failed (returns any value different from 0).
The moving average algorithm proceeds as follows:
  1. Loop through all the nodes of the grid.
  2. At each node u:
    1. retrive the neighbors of u
    2. compute the average of all the neighbors
    3. store the result and proceed to the next node
A possible implementation of this algorithm could be:



int MovingAverage::execute() {
  grid_->select_property( new_prop_name_ );
  neighborhood_->select_property( prop_name_ );

  // iterate through the grid
  for( Geostat_grid::iterator current_node_ptr = grid_->begin() ;
       current_node_ptr != grid_->end() ; ++current_node_ptr ) {

    // find the neighbors of the current node
    neighborhood_->find_neighbors( *current_node_ptr );
   
    // if the current node has no neighbors, skip it
    if( neighborhood_->is_empty() ) continue;


    // compute the average of the neighboring values.
    float sum = 0.0;
    for( Neighborhood::iterator neighbor_ptr = neighborhood_->begin();
               neighbor_ptr != neighborhood_->end() ; ++neighbor_ptr ) {
      sum += neighbor_ptr->property_value();
    }

    // compute the average: divide the sum by the number of values
    sum /= static_cast<float>( neighborhood_->size() );

    // assign the computed average to the current node
    current_node_ptr->set_property_value( sum );
  }
  return 0;
}





In writing this function the following parameters are identified:
These parameters are declared as member variable to the MovingAverage class (the changes are highlighted in red):



#include <GsTLAppli/geostat/geostat_algo.h>
#include <GsTL/utils/smartptr.h>

#include <string>

class Geostat_grid;
class Neighborhood;

class MovingAverage : public Geostat_algo {
public:
  // initializes the parameters of the algorithm
  virtual bool initialize( const Parameters_handler* parameters,
                           Error_messages_handler* errors );

  // Runs the algorithm.
  virtual int execute() ;

  // Tells the name of the algorithm
  virtual std::string name() const { return "moving_average"; }

public:
  static Named_interface* create_new_interface(std::string&);

private:
  // declare here all the parameters used by the algorithm
  // (i.e. the execute function)

  Geostat_grid* grid_;
  SmartPtr<Neighborhood> neighborhood_;
  std::string new_prop_name_;
  std::string prop_name_;
};

 



Defining the user interface

The most convenient solution to define the user interface is to use the Qt Designer. Refer to the Qt documentation for a user's manual to Designer.
In S-GeMS, parameters for an algorithm are characterized by a keyword and one or several attributes. The developer of the algorithm is free to choose the keyword as he likes, however the name and number of attributes is imposed by the widgets used.
To specify the keyword for a parameter in Designer use the Name property of the parameter widget:



To define an interface for an algorithm, layout the widgets as desired (see the user's manual to Designer), give a keyword name to each input entry, and set up the signals and slots of the interface (this last step is optionnal). While there is no explicit rule to choose a keyword name, it is advisable to use a naming scheme consistent with the other algorithms interfaces: try to use the same keywords for the same parameters, e.g. Grid_Name for the grid name parameter, etc.

S-GeMS-specific widgets

S-GeMS defines a new set of widgets which can be helpful to design an interface for a new algorithm. They can be found in the GsTLWidget section of the Designer widgets. Following is a brief description of each new widget:

The algorithm tag of the .ui file

To choose in which category the new algorithm will appear, add an algorithm tag at the top of the .ui file (using a text editor). The algorithm tag can have two attributes: category and description. The category tag contains the name of the section under which the new algorithm will appear in S-GeMS, and description a short description of the new algorithm. For example, to add the new algorithm to sub-section "smoothing" of section "Image Processing", with description "my new plug-in", write:
  <algorithm   category="Image Processing/smoothing"   description="my new plug-in" />




The category attribute can contain any path. If the path does not exist, the corresponding sub-sections will be created.

Note that the algorithm tag is not part of Designer, hence will be erased each time the .ui file is saved from Designer.


The initialize function

The initialize function is passed two arguments: a list of all parameters input by the user and an object to which errors in the parameters are reported. The aim of the function is to initialize the member variables identified when implemented the execute function. Errors in the parameters are reported to the errors handler object. In order to retrieve the value of a parameter, it is necessary to know the parameter keyword and attributes. The keywords were input to Designer, but the attribute names are imposed by the Designer widgets. For example, a QLineEdit parameter has a single attribute called "value". An InputVariogram widget has a "nb_of_structures" and a "nugget_effect" attribute. In order to know the attribute names, the easiest solution is to compile the plug-in (only the name and create_new_interface functions have to be defined fot the plug-in to loaded by S-GeMS), start S-GeMS, select the new algorithm input some parameters and save them to a file. The saved parameter file will contain all parameters with their keywords and attributes.
Once the keywords and attributes are known, it is possible to extract the parameter values and initialize the parameters of the algorithm.
Following is the initialize function for the MovingAverage class:


bool MovingAverage::initialize( const Parameters_handler* parameters,
                        Error_messages_handler* errors ) {
 
  // get the name of the grid we will work on
  std::string grid_name =
    parameters->value( "Simul_Grid.value" );

  // use utility function "create" to initialize our grid_ pointer.
  // "create" will retrieve the address of grid "grid_name" and set pointer
  // grid_ to point to the requested grid. If the requested grid does not
  // exist, the function return false;
  bool ok = geostat_utils::create( grid_, grid_name, "Simul_Grid", errors );
  if( !ok ) return false;

  // get the name of the property we will work on
  std::string property_name =
    parameters->value( "Hdata_Property.value" );
 
GsTLGridProperty* prop = grid_->property( property_name );
  errors->report( prop==0, "
Hdata_Property", "no such property in selected grid" );

  // get the name of the new property that will contain the result
  std::string new_property_name =
    parameters->value( "Simul_Property.value" );
 
  // tell the grid to add a new property
  // If the name of the new property is already used, the grid won't add
  // the new property. Utility function "add_property_to_grid" makes sure
  // that the name of the new property is unique (if the name input by the
  // user is already used, "add_property_to_grid" modifies that name to make
  // it unique).
  GsTLGridProperty* new_property =
    geostat_utils::add_property_to_grid( grid_, new_property_name );

  // get the 3 anisotropy ranges. The 3 numbers are in a single string and
  // need to be converted into numbers (float).
  std::string ranges_str =
    parameters->value( "Ranges.value" );

  // function "to_number" will convert the string into a vector of numbers
  // vector ranges will then contain all the number in string "ranges_str"
  std::vector<float> ranges = String_Op::to_numbers<float>( ranges_str );
 
  // tell the grid to create a new neighborhood
  neighborhood_ = grid_->neighborhood( ranges[0], ranges[1], ranges[2],
                                       0, 0, 0 );

  // set-up the neighborhood: max_size() set the maximum number of neighbors
  // to consider, select_property tells the neighborhood which property to
  // work on, and includes_center tells the neighborhood to consider the center
  // of the neighborhood as a neighbor: if we search for neighbors of node u,
  // u will be one of the neighbors of u.
  neighborhood_->max_size( 100000 );
  neighborhood_->select_property( property_name );
  neighborhood_->includes_center( true );

  // tell the grid to work on the new property
  grid_->select_property( new_property->name() );

  if( !errors->empty() ) return false;

  return true;

 
The initiliaze function returns true if everything was fine, false otherwise.

The name function

The name function simply returns the name of the algorithm. Note that the ".ui" file must be the same as the algorithm name. For example, if the name function returns "moving_average", the interface file must be called "moving_average.ui".


The create_new_interface function

This is a static function that creates a new instance of the algorithm object. For example, in the case of MovingAverage:


Named_interface* MovingAverage::create_new_interface( std::string& ) {
  return new MovingAverage;
}



Final Step

The last step is to add the GEOSTAT_PLUGIN macro at the end of the implementation file (.cpp). The macro takes as argument the name of the class that defines the new algorithm:

Named_interface* MovingAverage::create_new_interface( std::string& ) {
  return new MovingAverage;
}

GEOSTAT_PLUGIN(MovingAverage);




Compile the C++ files to obtain a .so (or .dll) library file and place it along with the .ui file into the plugins/Geostat directory.


Of course, it is possible to define several algorithms into a single plug-in. The plug-in will then be composed of a .so (or .dll) file and one .ui file for each algorithm. To define several algorithms into a single plug-in, define a function called plugin_init as extern "C" in one of your .cpp files (this file must include all the files defining the algorithms to be included into the plug-in). Then use macro BIND_GEOSTAT_ALGO to add each algorithm. The maccro is defined in <GsTLAppli/utils/gstl_plugins.h>. For example, if we have 2 algorithms implemented in classes MovingAverage and CubicSmoothing, add the following function to one of the cpp files:


extern "C" int plugin_init() {
  BIND_GEOSTAT_ALGO( MovingAverage );
  
BIND_GEOSTAT_ALGO( CubicSmoothing );
  return 0;
}





 
sourceforge.net logo