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.
Last up-dated:
November 2016 Tony Allen