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