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:
  - ANY_DATA_OBJECT 
 
- ANY_TIME_SERIES
- INTEGER
- FLOAT
- VECTOR
- MATRIX
- STRING
- TIME
- TIMETAGS
- TIME_INTERVAL
- SCALAR_SERIES
- VECTOR_SERIES
- MATRIX_SERIES
- SCALAR_TIME_SERIES
- VECTOR_TIME_SERIES
- MATRIX_TIME_SERIES
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
		
			
				- 
					The easiest way to create a new plugin is to copy and edit an existing one. The SetAttribute and Statistics plugins are simple and either is a good starting point. 
- If a plugin crashes it will crash QSAS, so take note of any defensive programming in the shipped plugins.
- It is well worth reading the DVOS Documentation, DvObjectClass.html,
 as many operations may be performed directly on objects using the DVOS 
methods, and these handle joining and metadata automatically. 
 
		
		
			Page created by Tony Allen, csc-support-dl@imperial.ac.uk
		
		
			Last up-dated: 
November  2016 Tony Allen