Writing Plugins

Contents


Overview

It is impossible to provide a system that will do absolutely everything, so QSAS provides an interface into user written modules to keep the system extensible.
Many of these have been bundled into the QSAS distribution, and we are grateful to the community for supplying them.

Plugins are user supplied extensions written in c++ (or with a C++ wrapper) and loaded dynamically by QSAS. An ASCII template file (ending .qtpl) must accompany the library to instruct QSAS on inputs and outputs to expect. Plugins accept QSAS data objects as inputs, and return new objects as outputs. They may provide extra manipulation capabilities, but can also be used to provide read and write access for non-supported data sets.

A plugin can unpack the data for processing, but may also use the DVOS methods to manipulate objects directly.

When a plugin is run, QSAS reads the template (.qtpl) file, and provides a user interface window with appropriate input object slots and output name slots. The dynamic library is loaded when the run button on this window is pressed, so the template file may be checked before the library is written.



QSAS Plugin Template File

A template file will describe a single plugin. Multiple plugin declarations in a template are not permitted.
C++ syntax comments (starting // ending with the new line) are permitted anywhere in a template file.

As an example the template for the setAttribute plugin is shown below:

EXTERNAL SetAttribute
 ( INPUT  ANY_DATA_OBJECT inobj,
   INPUT  STRING att_name,
   INPUT  ANY_DATA_OBJECT att_ptr,
   OUTPUT ANY_DATA_OBJECT outobj
 )
  
{
   SetAttribute  {
       PUBLIC_NAME { "Add/Change Attribute"}
      DESCRIPTION { "This plugin will add a new attribute to the input object. If the attribute already exists, it will be changed.
Typical uses include fixing UNITS or LABLAXIS text, adding FRAME information, etc." }
       ENTRY_NAME  { "DvSetAttribute" }
       OBJECT_FILE { "SetAttribute.so" }
   }

   inobj {
           PUBLIC_NAME {"Input QSAS Data Object"}
            DESCRIPTION {"Input the QSAS Data Object of which a text (string) attribute needs to be added or changed."}
            DEFAULT_VALUE {" "}
          }

   att_name {
PUBLIC_NAME {"Attribute Name"}
            DESCRIPTION {"The name of the attribute."}
        }
  
   att_ptr {
PUBLIC_NAME {"Attribute"}
  DESCRIPTION {"An object, number or un-quoted string specifying the new attribute or replacement text for an existing attribute"}
    }
  
   outobj { PUBLIC_NAME {"(Optional) new data object name"}
    DESCRIPTION {"Optional
One of:
  The text 'None' (to make changes to the original Working List data object)
  '<name>' to retain the old object and create a new copy with added/changed attribute."}
}

   outobj { DEFAULT_VALUE {"None"} }
  
}

The plugin declaration


The first block identifies the names of plugin and its arguments (both inputs and outputs):

EXTERNAL SetAttribute
 ( INPUT  ANY_DATA_OBJECT inobj,
   INPUT  STRING att_name,
   INPUT  ANY_DATA_OBJECT att_ptr,
   OUTPUT ANY_DATA_OBJECT outobj
 )

The plugin is called setAttribute, with three inputs called inobj, att_name and att_ptr, and one output called outobj. These names are used within the template, but are not used by the loaded library which just takes the inputs in the order given. Each named element is further described in the template, but the input and output object types are set here. Understood Object Types are described later.

The body of the plugin description is contained within { }.

The plugin Description

The plugin function called setAttribute is further described as follows:

SetAttribute  {
       PUBLIC_NAME { "Add/Change Attribute"}
      DESCRIPTION { "This plugin will add a new attribute to the input object. If the attribute already exists, it will be changed.
Typical uses include fixing UNITS or LABLAXIS text, adding FRAME information, etc." }
       ENTRY_NAME  { "DvSetAttribute" }
       OBJECT_FILE { "SetAttribute.so" }
   }

PUBLIC_NAME is a name that will be displayed as the title of the plugin window when the plugin template is loaded.

DESCRIPTION is text (with new lines as needed for clarity) that will appear when the Help/This Plugin Help... menu item is selected, and should describe what the plugin does and any useful hints and algorithm used if applicable.

ENTRY_NAME must be the name of a function inside the library. It will be called when the run button is pushed, and control will return to QSAS when it completes. QSAS plugins all start 'Dv' but this is not necessary.

OBJECT_FILE is the name of the dynamic library. This is the name given to the library file when it is compiled. Since QSAS loads it explicitly the extension does not need to follow platform conventions.

The Data Object Descriptions

Each input and output object needs a description to assist QSAS in showing the plugin window, e.g.:
 
inobj {
           PUBLIC_NAME {"Input QSAS Data Object"}
           DESCRIPTION {"Input the QSAS Data Object of which a text (string) attribute needs to be added or changed."}
           DEFAULT_VALUE {" "}
}

PUBLIC_NAME will appear above the input/output slot and should be more descriptive than a variable name. This name is local to the plugin window.

DESCRIPTION is optional, but will appear below the slot and should provide more explicit information on the object described.

DEFAULT_VALUE is optional and is text that will appear in the data slot (or output name slot) when the plugin window first appears. It may be the path and name of an object expected on the Working List or a default numeric value (as quoted text).


Input/Output Object Types

If an object type other than ANY_DATA_OBJECT is specified, then the input slots on the Plugin Window created by QSAS will try to restrict inputs to the type specified. Pulldowns will allow reduction to the type requested if possible. If, however, ANY_DATA_OBJECT is specified the plugin must do its own type checking to ensure the objects can be handled safely.

Object types allowed are:
For output objects, the type acts only as a useful feedback to the user of the sort of object to be returned. The slot itself is only used to provide the object with a name.


Plugin C++ coding

The plugin function declaration must be of the following form:

extern "C" QplugReturnStatus DvSetAttribute(vector<DvObject_var>& inputs, vector<DvObject_var>& outputs)
{

if (fails some test ) return QPLUG_FAILURE

... data processing ...

outputs.push_back(outObj1);
outputs.push_back(outObj2);

return QPLUG_SUCCESS;

}

Note that the function name is the same as the ENTRY_NAME in the template file. This function takes only two arguments, a vector of QSAS var pointers for the input objects and a vector which will contain var pointers to all the returned objects.

Checking the number and validity of input objects should be done by the code, e.g.:

     if(inputs.size() < 2) {
        QplugAppendTextDisplay ("insufficient inputs\n");
        return QPLUG_FAILURE;
     }
 
    //    Unpack and check the input data objects
    DvObject_var  inobj_ptr = inputs[0];
    if(inobj_ptr->is_nil()){
        QplugAppendTextDisplay ("Input object is empty");
        return QPLUG_FAILURE;
    }

Messages may be written to the plugin window via calls to QplugAppendTextDisplay(const char *) and the overloaded version QplugAppendTextDisplay(DvString).

Return codes may be one of
QPLUG_SUCCESS,
QPLUG_WARNING,
QPLUG_FAILURE
The input data objects may be operated on directly using DVOS methods, or the data extracted as a valarray or element by element using appropriate DVOS methods (see the next section).

A new object may be created as a result of a DVOS operaion, e.g.

DvObject_var outObj = inobj_ptr->normalize();

Alternatively a copy of an object may be created and the data manipulated directly:

DvObject_var outObj = new DvObject (inobj_ptr);   // copies input object and attributes
for(size_t n=0; n<outObj->seqSize(); n++) {
   double r = sqrt( outObj->dbl(n,0) * outObj->dbl(n,0) + outObj->dbl(n,1) * outObj->dbl(n,1)  + outObj->dbl(n,2) * outObj->dbl(n,2) );
   for(size_t i=0; i<3; i++) outObj->dbl(n,i) /= r;
}
Which performs the same operation as above, but in this case the test outObj->is_dbl() should be performed first since the double data valarray is directly accessed.

In either case the new object may be returned to QSAS by pushing it back on the outputs list:
outputs.push_back(outObj);

Outputs must be returned in the order expected in order to collect their assigned name correctly.  On completion of execution return control to QSAS with
return QPLUG_SUCCESS;
which will put returned objects on the Working List.

Below is an annotated simple plugin.


Sample Plugin

// useful standard headers
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>

// necessary QSAS headers
#include "qplug_if.h"
#include "DvObj.h"

// useful QSAS header
#include "qdutil.h"

// Plugin declaration
extern "C" QplugReturnStatus  DvTSSubset  ( vector<DvObject_var>& inputs, vector<DvObject_var>& outputs )
{

DvEvent start_end;  // event specifying time range to be returned
DvObject_var ts_in;  // Input data object
DvObject_var ts_out;  // output data object

if(inputs.size() < 2) {
        QplugAppendTextDisplay ("Needs 2 inputs\n");
        return QPLUG_FAILURE;
}

//    unpack the input data objects
//    ..first item is input Time Series

ts_in = inputs[0];

//    2nd item is time interval for start and stop

start_end = inputs[1]->getTimeRange();  // the get time range method will return a time range for either a time series or an event object
 
// No need to test the inputs are valid since the DVOS call is safe against bad or missing objects.

// Use DVOS method to subset input series on event

ts_out = ts_in->subSequence(start_end);
 
// put result on the return list
 
outputs.push_back(ts_out);

// put feedback message on Plugin Window

QplugAppendTextDisplay ("TSSubset >> data object created - exiting successfully\n");

return QPLUG_SUCCESS;





Some Useful DVOS Methods

The DVOS library provides all the data handling within QSAS, including joining and metadata testing. When DVOS operators are used the essential metadata is adjusted accordingly and attached to the output. These operators also check the metadata to ensure the object is valid for the operation (same frame for vectors, same units for addition etc) and will join the objects onto a common timeline if needed. Working from code examples in shipped plugins will help make sense of this section.

The DVOS object class, DvObject, may hold data of the type double, int, DvTime, DvEvent and DvString. In the following a single value means one of these quantities, such as a double or a DvTime. Each object may be viewed as a sequence (possibly of length 1) of arrays (possibly of size 1) with an arbitrary number of dimensions (also possibly just 1), and with time tags attached via the DEPEND_0 attribute (xref in DVOS parlance). Other metadata (xrefs) are attached and may be used to ensure processing is safe.

The DvObject should always be accessed via a var pointer, DvObject_var, which acts like a normal pointer to an object except that the underlying object will delete itself when no longer pointed to by any var pointer, that is all references to it have been deleted or gone out of scope.

The operator = will create a new pointer to an object without duplicating the object itself,
The assignment
DvObject_var myObj = inputs[0];
just allocates a new local var pointer to the object being passed from QSAS. Modifying the object pointed to by myObj will modify the object on the Working List.
The var pointer may, however, be safely re-assigned to a new object.

To copy an object one must use a copy constructor:
DvObject_var newObj = new DvObject(oldObj);
In this case oldObj may be a var pointer, a DvObject or a regular pointer (DvObject*) to a DvObject.

The operators *=, -=, *= and /= are available with the RHS taking either a single value or a DVOS object (or var pointer to a DVOS object).
hence
newObj *= newObj;
will multiply newObj by itself. Note that the object being pointed to by newObj is modified, so if this were the object passed from QSAS, the original Working List object would have been modified.

The operators +, -, *, / are all available between DvObjects (and var pointers) and between an object and a single value. A new object is returned and must be assigned to a var pointer:
DvObject newObj = myObj * 3.5;
or
DvObject newObj = myObj1 * myObj2;
are valid. In the first form each element is multiplied by 3.5, in the second values are multiplied record by record, after joining onto the timeline of the first if necessary.

Most common mathematical operations are supported (see QSAS analysis menus for examples as these all use DVOS methods directly). For example, trig functions, square root (sqrt), exp(), log etc:

DvObject_var newObj = myObj->sqrt();
or
newObj->sqrtThis();
will operate on the object itself. Most operators also exist in the opThis() form.

New metadata may be attached, or old metadata changed, by using
newObj->change_xref("AttrName", object);
where object may be a DvString, const char * or DvObject_var. The xref need not already exist.
Note, however, that metadata is usually taken care of already if DVOS methods are used directly.
Metadata may be read using
DvObject_var get_xref( xref_name );
where xref_name may be const char * or DvString.
There is also a convenience call to retrieve a xref as text:
DvString getXrefText(xref_name);

Metadata may be copied across explicitly using
copy_xrefs_from(DvObject_var &from_obj);
for all xrefs in from_obj, or just a named xref using
copy_xref_from( const char *xref_name, DvObject &from_obj);
For convenience important metadata names have been #defined in Xrefs.h in src/Utilities/dvos.

It is often convenient to access the time tags of an object explicitly, and this is done using
DvObject_var getDep0();
which will return the DEPEND_0 object (which occasionally may be a scalar sequence).

Accessing data elements requires that testing be done first to ensure the data is as expected. The following tests are self explanatory:
  bool is_ok()
  bool is_nil()
  bool is_dbl()
  bool is_int()
  bool is_str()
  bool is_time()
  bool is_event()
  bool not_dbl()
  bool not_int()
  bool not_str()
  bool not_time()
  bool not_event()
Also the following methods are needed to find the dimensions and sequence length:
  size_t seqSize()  length of sequence for data sequence
  vector <size_t> & Dims()  dimensions of arrays in sequence
  size_t nDims() number of dimensions (for loops)
  size_t arraySize() length of array in sequence, will be == 1 for scalars
  size_t totalSize() product of arraySize and seqSize

Other useful tests are:
  bool isThreeVector();
  bool isDeg();
  bool isRad();

Accessing data directly for reading and writing is provided through a set of methods:
double value = myObj->dbl(n,i);
or
myObj->int(n,i,j,k) = myObj->int(0,i,j,k);

The full list is:
  double & dbl(size_t nRec, size_t i, size_t j, size_t k, size_t m);
  double & dbl(size_t nRec, size_t i, size_t j, size_t k);
  double & dbl(size_t nRec, size_t i, size_t j);
  double & dbl(size_t nRec, size_t i);
  double & dbl(size_t posn=0);  element at this location in valarray, single scalar can use dbl()
  double & dbl(size_t nRec, vector <size_t> &index);  element at record and index posn
 
  int & itg(size_t nRec, size_t i, size_t j, size_t k, size_t m);
  int & itg(size_t nRec, size_t i, size_t j, size_t k);
  int & itg(size_t nRec, size_t i, size_t j);
  int & itg(size_t nRec, size_t i);
  int & itg(size_t posn=0);  element at this location in valarray, single scalar can use itg()
  int & itg(size_t nRec, vector <size_t> &index);  element at record and index posn

  DvString & str(size_t nRec, size_t i, size_t j, size_t k, size_t m);
  DvString & str(size_t nRec, size_t i, size_t j, size_t k);
  DvString & str(size_t nRec, size_t i, size_t j);
  DvString & str(size_t nRec, size_t i);
  DvString & str(size_t posn=0);  element at this location in valarray, single string can use str()
  DvString & str(size_t nRec, vector <size_t> &index);  element at record and index posn

  DvTime & time(size_t nRec, size_t i, size_t j, size_t k, size_t m);
  DvTime & time(size_t nRec, size_t i, size_t j, size_t k);
  DvTime & time(size_t nRec, size_t i, size_t j);
  DvTime & time(size_t nRec, size_t i);
  DvTime & time(size_t posn=0);  element at this location in valarray, single time can use time()
  DvTime & time(size_t nRec, vector <size_t> &index);  element at record and index posn

  DvEvent & event(size_t nRec, size_t i, size_t j, size_t k, size_t m);
  DvEvent & event(size_t nRec, size_t i, size_t j, size_t k);
  DvEvent & event(size_t nRec, size_t i, size_t j);
  DvEvent & event(size_t nRec, size_t i);
  DvEvent & event(size_t posn=0);  element at this location in valarray, single event can use event()
  DvEvent & event(size_t nRec, vector <size_t> &index);  element at record and index posn

In addition to these, there are safe access-only methods that return the requested type of value irrespective of the data type stored, and converted where meaningful e.g.
double asDouble(n,i,j); this may take up to 4 array index arguments as well as record number n (or single arg as for others)
int asInt(n); n is the position in the total array, allowing for record and array position
DvString asStr(n); n is the position in the total array, allowing for record and array position
DvString asText(n);
same as asStr() but if the data is DvString the returned string is in quotes
DvTime asTime(n); n is the position in the total array, allowing for record and array position

There are many constructors for DvObjects with the data type determined by the type of data in the argument:
  // empty constructors
  DvObject();

  // create sequence nRecs of value
  // Defaults are single value constructors
  DvObject(double value, size_t nRecs=1);
  DvObject(int value, size_t nRecs=1);
  DvObject(DvString value, size_t nRecs=1);
  DvObject(const char * value, size_t nRecs=1);
  DvObject(DvTime value, size_t nRecs=1);
  DvObject(DvEvent value, size_t nRecs=1);

  // create sequence nRecs of values in valarray
  DvObject(valarray <double> &from);
  DvObject(valarray <int> &from);
  DvObject(valarray <DvString> &from);
  DvObject(valarray <DvTime> &from);
  DvObject(valarray <DvEvent> &from);

  // create object of dimensions dims and length nRecs filled with value
  DvObject(double value, const vector <size_t> &dims, size_t nRecs=1);
  DvObject(int value, const vector <size_t> &dims, size_t nRecs=1);
  DvObject(const char *value, const vector <size_t> &dims, size_t nRecs=1);
  DvObject(DvString value, const vector <size_t> &dims, size_t nRecs=1);
  DvObject(DvTime value, const vector <size_t> &dims, size_t nRecs=1);
  DvObject(DvEvent value, const vector <size_t> &dims, size_t nRecs=1);

  // copy constructors
  DvObject(DvObject_var &inObj, bool withXrefs=true);
  DvObject(DvObject &inObj, bool withXrefs=true);
  DvObject(DvObject *inObj, bool withXrefs=true);
  DvObject(DvObject_var &inObj, size_t nRecs, bool withXrefs=true);
  DvObject(DvObject *inObj, size_t nRecs, bool withXrefs=true);
  DvObject(valarray <double> &from, vector <size_t> &dims, size_t nRecs=1);
  DvObject(valarray <int> &from, vector <size_t> &dims, size_t nRecs=1);
  DvObject(valarray <DvString> &from, vector <size_t> &dims, size_t nRecs=1);
  DvObject(valarray <DvTime> &from, vector <size_t> &dims, size_t nRecs=1);
  DvObject(valarray <DvEvent> &from, vector <size_t> &dims, size_t nRecs=1);

Specific methods are provided for sub-sequencing, array slicing, sub-sampling and masking. There are also unit conversion methods and explicit joining methods, as well as
several Matrix operations and other algorithms such as Minimum Variance routines. These are described fully in DvObject Class Reference.

This is only a short summary of some of the DVOS capabilities. Complete documentation, including details of the DvTime, DvEvent and DvString classes as well as joining and metadata handling are fully described in the DvObject Class Reference.


Compiling a Plugin

QSAS plugins are built from a Makefile. Sample Makefiles are included in the QSAS bin directory for different platforms, these will need renaming to 'Makefile'. Edit this to set the name of your plugin and, if necessary, the installation location of QSAS.  Typing:
make new
at the command line will then make the plugin dynamic library, and if the install commands in the Makefile are uncommented, will also install into QSAS.


Deploying a Plugin

A plugin may either be installed into the QSAS directory or the dynamic library and template file (.qtpl) can be left in the same directory together. To install into the QSAS tree the dynamic library should be placed in lib and the template file in either Analysis or Geophysics directories of qtpl.  The new plugin will then show up when Plun-Ins/Refresh Menu is selected, and can be run from the appropriate Plug-Ins menu. If left in their own directory, then the Plug-Ins/Browse menu item will allow the user to navigate to the plugin template and run it from there (in which case the dynamic library must be in the same directory).

Whichever way the plugin is run a GUI window will be launched which has the input data slots and output object name slots specified in the template file, with tooltips for the data object types and the text specified in the template. On selecting >> Run >> the input data are passed to the dynamic library by calling the named entry function. If this completes successfully,  the outputs are placed on the Working List.



The buttons at the bottom of the Plugin window also allow the inputs/outputs to be reset to their default values (if provided in the plugin template) as well as saving the feedback text area to a file and clearing the text area.

Tips/FAQ


Last up-dated:
November  2016 Tony Allen