OpenIndy Dev Documentation bio photo

OpenIndy Dev Documentation

Open source software solution for industrial measurement (metrology, laser tracker, quality control)

Facebook Github

Plugins / OiPluginTemplate

Plugins / OiPluginTemplate Overview


Overview  Concept and Architecture  Server Interface  Software Reference Documentation

Plugin loader

plugin loader


##OiPluginTemplate Tutorial

OiPluginTemplate is a predefined Qt project which we recommend you to use if you want to write a plugin for OpenIndy. In the next chapters it is assumed that you know the terminology of OpenIndy (like element, feature, function, sensor etc.). If you do not know the concept of OpenIndy yet, you may want to inform yourself here. Read the following steps to learn how to write and run your own plugins using this project as a template.

##Get started

First you need to get a copy of the Qt-project “OiTemplatePlugin” on your local machine. Therefor you have two options available:

On the one hand you might fork this repository (OiPluginTemplate) and get a copy of it on your local machine. If you are new to GitHub you may want to follow the guidance at https://help.github.com/articles/fork-a-repo. On the other hand you might also fork and clone the OpenIndy repository. That repository contains a folder plugins which may look like this:

oiPluginFolders

To get your own plugin you have to copy the folder OiPluginTemplate and paste it again next to it. You may choose any name for your new folder.

No matter which option you choose you get one Qt-project for your own plugin. Every Qt-project is represented by a *.pro-file. In your case it is the file OiTemplatePlugin.pro. If you choose the first option the folder structure of the Qt-project will look like this:

oiPluginFolders

However if you choose the second option the folder structure will look as follows:

oiPluginFolders

How to work on your own plugin and what you have to take in account is explained in the next chapters.

Furthermore you now need to install Qt Creator. You can download it here. To configure Qt Creator you may follow the instructions at the Qt documentation.

You are now ready to get started with the implementation of your own plugin. Notice that the content of the folders src and lib, which came with the template (only if you have chosen option 1 above), must not be changed. You may add other libraries to the lib folder, but existing content has to stay as it is if you do not want to get undesirable behaviour.

##Write plugins

OpenIndy provides interfaces for functions, sensors and network adjustments. How to implement them and integrate them in a plugin is described in the next three subchapters. It is explained in detail what you have to take in account when implementing such a plugin. In contrast there is also a step by step guide in the next chapter that leads you through a tutorial where you implement a small plugin with a function and a sensor.
In general it is important to know that neither the name of the pro-file OiTemplatePlugin.pro nor the names of the files p_factory.h, p_factory.cpp and metaInfo.json must be changed.

Plugin meta data

The file metaInfo.json should contain meta information about your plugin. The meaning of each attribute is described in the table below.

attribute description
isOiPlugin Set this parameter to true so that OpenIndy recognises your plugin as valid.
name The name of your plugin.
pluginVersion The version of your plugin. This version has to correspond with the version OiPlugin_iidd which is defined in the file pi_oiplugin.h.
author You can enter your name or the name of your company here.
compiler This attribute has to contain the compiler with which you are going to compile your plugin. You might choose MVSC 64bit (Microsoft Visual Studio), minGW (GNU GCC/G++), clang 64bit (Clang/LLVM), intel (Intel ICC/ICPC), intelC (IBM XL C/C++), hewlett (Hewlett-Packard C/aC++), portland (Portland Group PGCC/PGCPP) or oracle (Oracle Solaris Studio). This is important for OpenIndy to know, because a plugin can only be loaded in OpenIndy if OpenIndy has been compiled with the same compiler.
operatingSystem Here you have to enter the operating system on which you compile your plugin. There are three options: “windows”, “linux” or “mac os”.
description A description of your plugin.
dependencies Set this parameter to true if your plugin has some dependencies that have to be copied to the executable of OpenIndy.
libPaths If you set the above parameter to true then you can add here as many dependencies as you want. One dependency is represented by a name and a path. Set name to the file name of the library (including the file extension) or the name of a hole folder. That library or folder has to be placed next to your plugin library (e.g. *.dll). You might also enter a hole path to a library like “myLib/dependency.dll”. And in addition set the attribute path to “app” so that the dependency (a library or a folder) will be put next to the executable of OpenIndy.



Now that you have set up the Json-file you can start with the actual implementation of your plugin.

Plugin factory

The class OiTemplatePlugin, which is defined in files p_factory.h and p_factory.cpp, implements the actual interface for every plugin which itself is defined in the header file pi_oiplugin.h.

That interface looks like this:

class OiPlugin
{
public:
    virtual ~OiPlugin(){}

    virtual QList<Sensor*> createSensors() = 0;
    virtual QList<Function*> createFunctions() = 0;
    virtual QList<NetworkAdjustment*> createNetworkAdjustments() = 0;
    virtual Sensor* createSensor(QString name) = 0;
    virtual Function* createFunction(QString name) = 0;
    virtual NetworkAdjustment* createNetworkAdjustment(QString name) = 0;
};

As you can see there are six methods which must be implemented. The methods createSensors, createFunctions and createNetworkAdjustments return a list of pointers to sensors, functions and network adjustments respectively. These lists shall contain all sensors, functions and network adjustments which you have implemented in your plugin. The methods createSensor, createFunction and createNetworkAdjustment, which expect the string name as a parameter, return only one sensor, function and network adjustment respectively. The parameter name represents the name of the sensor, function or network adjustment. So each of these three methods shall return only the proper pointer or NULL if no sensor, function or network adjustment with the given name is available. Certainly this becomes more clear when looking at an example:

Assuming that you have implemented two sensors called “sensor a” and “sensor b” and in addition two functions called “function a” and “function b” before, your implementation of the six methods above might look as follows:

QList<Sensor*> OiTemplatePlugin::createSensors(){
    QList<Sensor*> resultSet;
    Sensor *a = new SensorA();
    Sensor *b = new SensorB();
    resultSet.append(a);
    resultSet.append(b);
    return resultSet;
}

QList<Function*> OiTemplatePlugin::createFunctions(){
    QList<Function*> resultSet;
    Function *a = new FunctionA();
    Function *b = new FunctionB();
    resultSet.append(a);
    resultSet.append(b);
    return resultSet;
}

QList<NetworkAdjustment*> OiTemplatePlugin::createNetworkAdjustments(){
    QList<NetworkAdjustment*> resultSet;
    return resultSet;
}

Sensor* OiTemplatePlugin::createSensor(QString name){
    Sensor *result = NULL;
    if(name.compare("sensor a") == 0){
        result = new SensorA();
    }else if(name.compare("sensor b") == 0){
        result = new SensorB();
    }
    return result;
}

Function* OiTemplatePlugin::createFunction(QString name){
    Function *result = NULL;
    if(name.compare("function a") == 0){
        result = new FunctionA();
    }else if(name.compare("function b") == 0){
        result = new FunctionB();
    }
    return result;
}

NetworkAdjustment* OiTemplatePlugin::createNetworkAdjustment(QString name){
    NetworkAdjustment *result = NULL;
    return result;
}

If you do not add your functions, sensors and network adjustments, which are implemented in your plugin, to the six methods above then OpenIndy will not be able to use them. In other words you can think of the class OiTemplatePlugin as a factory which creates and delivers function-, sensor- and network adjustment objects from the plugin to OpenIndy as soon as it is required.

Implement functions, sensors and network adjustments

Now you know how to implement the plugin interface of OpenIndy, what is important to instantiate your function-, sensor- and network adjustment classes and deliver the resulting objects to OpenIndy. To learn how to implement your own functions, sensors and network adjustment you can follow the links below.

##Function

Introduction

Every feature in OpenIndy can have any number of functions. There are construct-, fit- and geodetic functions and furthermore object transformations and system transformations. The list below gives you a review of those function types.

function type description
fit function If overdetermination is present, fit functions have the purpose to calculate the adjusted parameters of a geometry by using observations. So for example a fit function can calculate the center of n xyz-observations.
construct function Construct functions are used to calculate geometries by using other geometries. For example the intersection of a line and a plane results in a new point.
geodetic function Geodetic functions are intended to calculate special geodetic tasks like spatial intersection etc..
object transformation In contrast to fit- and construct functions, object transformations do not define a geometry, but change a previously defined one. A point which has been fit before can for instance be moved along a line by a certain amount.
system transformation System transformations are used to calculate the parameters of a transformation from one coordinate system to another one.



These function types are implemented as classes which extend the class Function. When you want to write your own function in your plugin then you have to extend one of the classes FitFunction, ConstructFunction, GeodeticFunction, ObjectTransformation or SystemTransformation. Please do not try to extend the class Function itself. While this currently works for some kind of function types for others it does not because additional methods and attributes are necessary which are defined only in the subclasses.

Let us first look at the class Function itself. There are some methods that you always have to override (pure virtual methods). This methods are pure virtual in the five subclasses of the class Function, too.:

Pure virtual methods

virtual QList<InputParams> getNeededElements() = 0;
virtual QList<Configuration::FeatureTypes> applicableFor() = 0;
virtual PluginMetaData* getMetaData() = 0;

The method getNeededElements has to return a list of InputParams. InputParams is a struct and is defined in the upper part of the header file from class Function. An input parameter represents an element that is required for the calculation of the actual function. For example if you want to implement a construct function which calculates the intersection of a line and a plane, you might implement the method as follows:

QList<InputParams> IntersectLinePlane::getNeededElements(){
    QList<InputParams> result;
    InputParams param1;
    param1.index = 0;
    param1.description = "Select a line to calculate the intersection.";
    param1.infinite = false;
    param1.typeOfElement = Configuration::eLineElement;
    result.append(param1);
    InputParams param2;
    param2.index = 1;
    param2.description = "Select a plane to calculate the intersection.";
    param2.infinite = false;
    param2.typeOfElement = Configuration::ePlaneElement;
    result.append(param2);
    return result;
}

The class Configuration contains the enumeration ElementTypes which has a value for each available element. Each input parameter that you add to the result list has to “know” which element type it represents (typeOfElement) and how often this element type can be included (infinite). If infinite is true a user can enter as many elements of the specified type as he wants. Otherwise only one element is expected. In addition you can optionally define a description for each input parameter, so that the user of your plugin will know about the use of each parameter.

The second method applicableFor has to return a list of feature types. As well as ElementTypes, FeatureTypes is an enumeration defined in the class Configuration and has one value for each available feature. The result list has to contain every feature type which this function can be assigned to. Assuming you wrote a function called “TranslateByLine” which is able to move a point or a sphere along a line by a specified amount then you have to add the enum values of the features point and sphere to the result list. Your implementation of the method might look like this:

QList<Configuration::FeatureTypes> TranslateByLine::applicableFor(){
    QList<Configuration::FeatureTypes> result;
    result.append(Configuration::ePointFeature);
    result.append(Configuration::eSphereFeature);
    return result;
}

Last but not least there is the method getMetaData which has to return a pointer to an object of the type PluginMetaData. In the meta data object that is returned in your implementation of this method you can define for example a name and a description for your function. It is not necessary that you fill all attributes that you can find in the class PluginMetaData, but a minimal implementation should contain an iid, a name, a description and the name of the plugin which the function belongs to. The name and the description that you chose for your function are later shown in the function plugin loader of OpenIndy (after you have successfully installed your plugin) where users can assign functions to features. As the name of the plugin you have to chose the name which you already defined in the json file (next to the pro file of your Qt project). The iid has a special structure:

de.openIndy.Plugin.Function.[TYPE_OF_FUNCTION].[VERSION_OF_PLUGIN]

Replace [TYPE_OF_FUNCTION] by “FitFunction”, “ConstructFunction”, “GeodeticFunction”, “ObjectTransformation” or “SystemTransformation” for your specific case. As [VERSION_OF_PLUGIN] please chose the same as you did in your json file, but format it like “v001”.

Virtual methods

virtual bool exec(Station&);
virtual bool exec(CoordinateSystem&);
virtual bool exec(TrafoParam&);
virtual bool exec(Point&);
virtual bool exec(Line&);
virtual bool exec(Plane&);
virtual bool exec(Sphere&);
virtual bool exec(Circle&);
virtual bool exec(Cone&);
virtual bool exec(Cylinder&);
virtual bool exec(Ellipsoid&);
virtual bool exec(Hyperboloid&);
virtual bool exec(Nurbs&);
virtual bool exec(Paraboloid&);
virtual bool exec(PointCloud&);
virtual bool exec(ScalarEntityAngle&);
virtual bool exec(ScalarEntityDistance&);  

virtual QMap<QString, int> getIntegerParameter();
virtual QMap<QString, double> getDoubleParameter();
virtual QMap<QString, QStringList> getStringParameter();  

virtual QStringList getResultProtocol();  

virtual void clear();
virtual void clearResults();

For each feature that is currently defined in OpenIndy there is an exec method in the class Function. OpenIndy will call the exec method of a function each time a feature which that function is assigned to has to be recalculated. A recalculation of a feature is necessary whenever one or more elements which that feature depends on are changed. This is the case for example when you add a new observation to the fit function of the feature or indirectly when your feature is constructed from another feature which is now changed (for example because of a new observation added). Therefore your exec methods have to contain the actual implementation of your algorithm (of course you can subdivide your implementation into your own methods which you then call from the exec method). As the exec methods are not pure virtual you do not have to implement all of them. If you want to implement the intersection of a line and a plane then of course you only have to implement the exec method for the type point. But be sure that your choice of exec methods which you want to implement is consistent with how you implemented the method applicableFor. Read the part about the implementation of exec methods below to get a detailed description on what you have to consider when implementing an exec method.
The methods getIntegerParameter, getDoubleParameter and getStringParameter are meant to give you the chance to define special parameters for your function. As you learned before the method getNeededElements allows you to define elements (e.g. observations, points, planes etc.) as input parameters. Your function can use this elements to calculate whatever you want. But in some cases you maybe need to ask an user for special parameters (like wether you shall calculate a transformation with or without scale). Therefore you can implement those three methods and thereby tell OpenIndy to ask users for those parameters. Let us look at an example:

QMap<QString, int> TranslateByLine::getIntegerParameter(){
    QMap<QString, int> result;
    QString key = "paramA";
    int defaultValue = 1;
    result.insert(key, defaultValue);
    return result;
}

QMap<QString, double> TranslateByLine::getDoubleParameter(){
    QMap<QString, double> result;
    QString key = "paramB";
    double defaultValue = 12.34;
    result.insert(key, defaultValue);
    return result;
}

QMap<QString, QStringList> TranslateByLine::getStringParameter(){
    QMap<QString, QStringList> result;
    QString key = "paramC";
    QStringList defaultValues;
    defaultValues.append("option A");
    defaultValues.append("option B");
    defaultValues.append("option C");
    result.insert(key, value);
    return result;
}

You can define three different types of parameters: integer-values, double-values and string-values. Each parameter you define has a key and a default value. In contrast to integer- and double-parameters string-parameters are defined as lists. So you define multiple options and users can select one of those optionse or they can type in a custom string. Default value is always the first entry in the list, in this case that would be the string “option A”. We recommend you to use only one single string as a key and define the description of each parameter in the description of the whole function. In OpenIndy your parameters appear as follows:


extra Parameter Example

In the above illustration you can see a string-parameter with the key “invert” and the possible options “yes” and “no”.

Furthermore there is the method getResultProtocol which you can override. Your implementation of this method might return a list of strings which shows special results or information on your calculations that otherwise would not have been shown.

Last but not least you might override the methods clear and clearResults. As the name implies the method clearResults erases all results of the last execution of the function. That concerns for example the statistic of that function. On the contrary the method clear erases not only those results, but the whole setup of the function. This also includes all elements which were assigned to the function. If you plan to reimplement this two methods then you might want to take a look at the implementation in the class Function.

Further non-virtual methods

There are some further methods which you cannot reimplement, but which are important to know and use when implementing a function. First of all there is the method setUseState. In your implementation you mostly use other elements to calculate one feature. As you will learn in the next part not every element is valid, so it may occur that while most of the given elements are used for calculation some are not. So for each element you should call this method and tell it wether to use the element or not. Moreover there is the method isValid which returns true if all elements that are necessary for calculation are present. This method uses your implementation of getNeededElements to decide wether all elements are present or not.

It is also possible to access the console of OpenIndy from the function class. Therefore you can use the method writeToConsole by passing your message as a parameter. This might be useful for execptions for example. Furthermore there are methods to get a special element by its id or a whole list of all elements of a special type (e.g. observation, point, plane etc.).

As you can see when looking at the definition of the class Function there are a a few other methods not mentioned yet. Those methods are not important when implementing a function. They are called from OpenIndy to get information on the current setup of the function (e.g. statistic or assigned elements etc.).

Implementation of exec methods

As you have learned recently an exec method contains the actual implementation of you algorithm. As input you get a reference to the feature which this function is assigned to and which your implementation has to modify. This input feature may have been solved by previous functions. If so you can access this feature’s attributes to get the result of those functions. If the feature is a geometry there is a statistic object, defined in the class Geometry, with the current statistic of the geometry. Besides that input feature you also need the elements which you defined in your implementation of the method getNeededElements for your calculation.

##Sensor

sensor

##Network adjustment

network adjustment

##Plugin implementation -
A step by step guide

This section leads you through the implementation of a simple plugin for OpenIndy. You will implement a function and you will learn how to make your plugin ready for the use in OpenIndy.

- Initial steps

After you have completed the Get Started chapter you have got a local copy of the OiTemplatePlugin-project. The folder structure of your local project should look like the left image if you have chosen to fork the OiPluginTemplate repository. Otherwise, if you have forked the OpenIndy repository, the folder structure of your local project should look like the right image.

Before you open the plugin-project you should open and compile the “openIndyLib”-project. That project contains the classes for linear algebra. If you have forked the OiPluginTemplate repository you can find the openIndyLib.pro-file under /lib/openIndyLib. In contrast if you have forked the OpenIndy repository you can find the openIndyLib.pro-file under ../../lib/openIndyLib. In both cases it is assumed that you navigate to the openIndyLib-folder starting at the folder which is shown in the images above respectively.

You can open Qt projects (in Qt Creator) by clicking “File” > “Open File or Project…” and selecting your *.pro-file. To compile a project select “Build” > “Run qmake” and afterwards “Build” > “Rebuild Project xy”.

Now, after you have compiled the “openIndyLib”-project, please open the OiTemplatePlugin-project.

Plugin meta data

Let us first set up the file metaInfo.json. Please copy the following lines to your metaInfo.json-file:

{
    "isOiPlugin": true,

    "name"  : "Plugin Tutorial",
    "pluginVersion" : "1.0.0",
    "author"  : "OpenIndyOrg",
    "compiler" : "MVSC 64bit",
    "operatingSystem" : "windows",

    "description" : "This is an example plugin. It is meant as a reference for you to create your own plugins.",

    "dependencies" : false,
    "libPaths" : [
        { "name": "dependencies", "path": "app" }
    ]
}

If you are not working on a Windows system with the Microsoft Visual Studio Compiler you have to replace “windows” or “MSVC 64bit” respectively with the appropriate value. You might replace “windows” by either “linux” or “mac os” if you are working on a Linux or Mac system. The following table gives you a review of all possible values for the compiler.

compiler value
Microsoft Visual Studio MVSC 64bit
GNU GCC/G++ minGW
Clang/LLVM clang 64bit
Intel ICC/ICPC intel
IBM XL C/C++ intelC
Hewlett-Packard C/aC++ hewlett
Portland Group PGCC/PGCPP portland
Oracle Solaris Studio oracle


So for example if you are working on a linux system with the GNU GCC/G++ compiler you have to replace “windows” by “linux” and “MSVC 64bit” by “minGW”.
As there are no dependencies (e.g. external libraries) for this example plugin the parameter “dependencies” is set to false.

You are now ready to start with the implementation of your first function.

- Your first function

In this subchapter you will implement a function for fitting a point. As input that function expects any number of XYZ-observations. The function then calculates the central point and the covariance matrix of the averaged point.

Set up the function

Initially you have to create two empty files pointFit.h and pointFit.cpp next to the OiTemplatePlugin.pro-file. Afterwards you can add that two files to your Qt-project by right clicking on the project entry (OiTemplatePlugin) in the project treeview of Qt Creator and selecting the menu item “add existing files”. Browse to those two files, pointFit.h and pointFit.cpp, and add them to your project. They also appear in the OiTemplatePlugin.pro-file now, so you do not have to modify it yourself.

Now you can start implementing your function. Please copy the following lines to the pointFit.h-file.

#ifndef POINTFIT_H
#define POINTFIT_H

#include "pi_fitfunction.h"
#include "configuration.h"
#include "pluginmetadata.h"

class PointFit : public FitFunction
{

public:
    PluginMetaData* getMetaData();
    QList<InputParams> getNeededElements();
    QList<Configuration::FeatureTypes> applicableFor();

};

#endif // POINTFIT_H

The corresponding cpp-file pointFit.cpp has to look as follows:

#include "pointFit.h"

PluginMetaData* PointFit::getMetaData(){
    PluginMetaData* metaData = new PluginMetaData();
    metaData->name = "PointFit";
    metaData->pluginName = "Plugin Tutorial";
    metaData->author = "OpenIndyOrg";
    metaData->description = QString("%1 %2")
            .arg("This function calculates an adjusted point.")
            .arg("You can input as many observations as you want which are then used to find the best fit 3D point.");
    metaData->iid = "de.openIndy.Plugin.Function.FitFunction.v001";
    return metaData;
}

QList<InputParams> PointFit::getNeededElements(){
    QList<InputParams> result;
    InputParams param;
    param.index = 0;
    param.description = "Select observations to calculate the best fit point.";
    param.infinite = true;
    param.typeOfElement = Configuration::eObservationElement;
    result.append(param);
    return result;
}

QList<Configuration::FeatureTypes> PointFit::applicableFor(){
    QList<Configuration::FeatureTypes> result;
    result.append(Configuration::ePointFeature);
    return result;
}

Now you have a minimum implementation of a function. The name and the description you have defined in the method getMetaData are important, because they appear later when using your function in OpenIndy. That description tells the users of your function how this function works and what it does. In the method getNeededElements it is determined what elements your function needs to be able to calculate something. As your function calculates the center of n observations, of course, you need some observations. This is defined in the parameter typeOfElement.

Again there is a description parameter which will also appear later in OpenIndy. That description tells the user of your function what the element (observation) is used for in the function. The parameter infinite determines that the user of your function might add as many observations to the function as he wants. At last there is the method applicableFor which tells OpenIndy that your function can only be assigned to a point feature.

For test purpose you might also compile your plugin now, if you want. There is no actual algorithm implemented, so this function is useless at the moment. Therefor let us continue by adding the algorithm for fitting a point.

Add the actual functionality to the function

Please add the definition of an exec method for a point feature to the header file pointFit.h. Your header file should look like this now:

#ifndef POINTFIT_H
#define POINTFIT_H

#include "pi_fitfunction.h"
#include "configuration.h"
#include "pluginmetadata.h"

class PointFit : public FitFunction
{

public:
    PluginMetaData* getMetaData();
    QList<InputParams> getNeededElements();
    QList<Configuration::FeatureTypes> applicableFor();

    bool exec(Point&);

};

#endif // POINTFIT_H

At the very bottom of the corresponding cpp-file (pointFit.cpp) please add the implementation of that exec method as follows:

bool PointFit::exec(Point &p){
    if(this->isValid()){

    }
    return false;
}

The method isValid, which is called here, is defined in the function class and checks wether all needed elements are available. In this exec method you can now implement the calculation of the central point. The first thing you need is a list of all observations that shall be used for calculation. Therefor please change the implementation of your exec method as follows:

bool PointFit::exec(Point &p){
    if(this->isValid() && this->featureOrder.contains(0)){
        QList<Observation*> myObservations;
        QList<InputFeature> myObservationIds = this->featureOrder.value(0);
        foreach(InputFeature obs, myObservationIds){
            Observation *myObservation = this->getObservation(obs.id);
            if(myObservation != NULL && myObservation->isValid){
                myObservations.append(myObservation);
            }else{
                this->setUseState(obs.id, false);
            }
        }
        if(myObservations.size() > 0){
            //TODO
        }
    }
    return false;
}

The attribute featureOrder is a map, defined in the function class, which contains the id’s of all elements that were assigned to your function by the user of OpenIndy in a special order. In this case that map has only got the key “0”, because you only defined one needed element (observations) in the method getNeededElements. You get a list of objects, which hold the id’s of the observations, by accessing the value of the map with the key “0”.

To get a list with the actual observations you have to iterate through a loop and call the method getObservation each time with the id of the observation you are searching for. The result is a pointer to an observation object. You should check for NULL and you should also check wether that observation is valid or not. An observation is valid if it is available in the current coordinate system. If an observation is not valid the method setUseState is called to tell OpenIndy that your function won’t include that observation in its calculations. To not overfill the exec method please define a private method in the header file pointFit.h as follows:

#ifndef POINTFIT_H
#define POINTFIT_H

#include "pi_fitfunction.h"
#include "configuration.h"
#include "pluginmetadata.h"

class PointFit : public FitFunction
{

public:
    PluginMetaData* getMetaData();
    QList<InputParams> getNeededElements();
    QList<Configuration::FeatureTypes> applicableFor();

    bool exec(Point&);

private:
    bool calculateCentralPoint(Point&, QList<Observation*>);

};

#endif // POINTFIT_H

Again you can add the following implementation at the very bottom of the cpp-file pointFit.cpp:

bool PointFit::calculateCentralPoint(Point &p, QList<Observation*> myObservations){
    //TODO
    return false;
}

Now, please replace the //TODO in the exec method by calling the method you just created and returning its result:

return this->calculateCentralPoint(p, myObservations);

As you will see the classes OiVec and OiMat, mentioned before, are used to do all calculations. At first we create and fill the l vector with the XYZ-coordinates of all observations. For this purpose please replace the //TODO in the method calculateCentralPoint by the following lines:

    //set up l vector with all observations
    OiVec l;
    foreach(Observation *obs, myObservations){
        l.add( obs->myXyz.getAt(0) );
        l.add( obs->myXyz.getAt(1) );
        l.add( obs->myXyz.getAt(2) );
    }
    //TODO

The next step is to set up the A matrix. Again please replace the //TODO by the following lines:

    OiMat a(l.getSize(), 3);
    for(int i = 0; i < l.getSize(); i++){
        if( (i%3) == 0 ){
            a.setAt(i, Point::unknownX, 1.0);
        }else if( (i%3) == 1 ){
            a.setAt(i, Point::unknownY, 1.0);
        }else if( (i%3) == 2 ){
            a.setAt(i, Point::unknownZ, 1.0);
        }
    }
    //TODO

The three values unknownX, unknownY and unknownZ are enumeration values. The corresponding enumeration is defined in the class Point. This enumeration values specify the order of unknowns for the A matrix and thus also the order of the covariance matrix. OpenIndy itself and further functions have to know about this order, because otherwise the covariance matrix you calculate would be useless.
Again please replace the //TODO by the following lines:

    //calculate coordinates of central point and corresponding statistic
    OiMat n = a.t() * a;
    OiVec c = a.t() * l;
    try{

        OiMat qxx = n.inv();
        OiVec x = qxx * c; //coordinates of central point
        OiVec v = a * x - l;
        x.add(1.0); //x vector as homogeneous coordinates
        //calculate statistic
        double stdv = 0.0;
        if(v.getSize() > 3){
            double sum_vv = 0.0;
            for(int i = 0; i < v.getSize(); i++){
                sum_vv += v.getAt(i) * v.getAt(i);
            }
            stdv = qSqrt(sum_vv / (v.getSize() - 3));
        }else{
            stdv = 0.0;
        }
        //set up result
        p.myStatistic.s0_apriori = 1.0;
        p.myStatistic.s0_aposteriori = stdv;
        p.xyz = x;
        p.myStatistic.qxx = qxx;
        p.myStatistic.v.replace(v);
        p.myStatistic.stdev = stdv;

        //TODO

        p.myStatistic.isValid = true;
        this->myStatistic = p.myStatistic;

        return true;

    }catch(logic_error e){
        this->writeToConsole(e.what());
        return false;
    }catch(runtime_error e){
        this->writeToConsole(e.what());
        return false;
    }

In the above code snippet the actual algorithm is implemented. The x vector holds the coordinates of the central point and the matrix qxx is the cofactor matrix of that central point. As the point feature p, which your function shall calculate, is passed as a reference you can easily set the results to the reference. Furthermore the point p has got an attribute myStatistic which you can assign your statistical results to. Not only the point, but also the function itself has got an attribute myStatistic.

After you have set the points statistic you have to assign that statistic object to the statistic object of your function, too. This is because the statistic of the point might later be changed by another function that is assigned to the point. The statistic of a function is only modified by the function itself (and no other functions) and can be reviewed via special dialogs in OpenIndy.

Now one last step is missing. For the residuals to be displayed you have to fill the attribute displayResiduals of the point object. Therefor please replace the //TODO by the following snippet:

//display the residuals in OpenIndy
for(int i = 0; i < p.myStatistic.v.getSize() / 3; i++){
    Residual r;

    r.addValue("vx", v.getAt(i*3), UnitConverter::eMetric);
    r.addValue("vy", v.getAt(1+i*3), UnitConverter::eMetric);
    r.addValue("vz", v.getAt(2+i*3), UnitConverter::eMetric);

    p.myStatistic.displayResiduals.append(r);
}

Maybe you ask yourself why OpenIndy does not simply display the residuals which you have previously assigned to the v vector. This is because OpenIndy does not no anything about your algorithm. The v vector could contain anything in any unit. The residual objects you add to the list displayResiduals above define what a residual represents by specifying a header (e.g. “vx”) and they also determine the unit in which the residuals are saved.

Summary

To make sure that your implementation equals the reference implementation of this function you can compare your one to the implementations below.

#ifndef POINTFIT_H
#define POINTFIT_H

#include "pi_fitfunction.h"
#include "configuration.h"
#include "pluginmetadata.h"

class PointFit : public FitFunction
{

public:
    PluginMetaData* getMetaData();
    QList<InputParams> getNeededElements();
    QList<Configuration::FeatureTypes> applicableFor();

    bool exec(Point&);

private:
    bool calculateCentralPoint(Point&, QList<Observation*>);

};

#endif // POINTFIT_H
#include "pointFit.h"

PluginMetaData* PointFit::getMetaData(){
    PluginMetaData* metaData = new PluginMetaData();
    metaData->name = "PointFit";
    metaData->pluginName = "Plugin Tutorial";
    metaData->author = "OpenIndyOrg";
    metaData->description = QString("%1 %2")
            .arg("This function calculates an adjusted point.")
            .arg("You can input as many observations as you want which are then used to find the best fit 3D point.");
    metaData->iid = "de.openIndy.Plugin.Function.FitFunction.v001";
    return metaData;
}

QList<InputParams> PointFit::getNeededElements(){
    QList<InputParams> result;
    InputParams param;
    param.index = 0;
    param.description = "Select observations to calculate the best fit point.";
    param.infinite = true;
    param.typeOfElement = Configuration::eObservationElement;
    result.append(param);
    return result;
}

QList<Configuration::FeatureTypes> PointFit::applicableFor(){
    QList<Configuration::FeatureTypes> result;
    result.append(Configuration::ePointFeature);
    return result;
}

bool PointFit::exec(Point &p){
    if(this->isValid() && this->featureOrder.contains(0)){
        QList<Observation*> myObservations;
        QList<InputFeature> myObservationIds = this->featureOrder.value(0);
        foreach(InputFeature obs, myObservationIds){
            Observation *myObservation = this->getObservation(obs.id);
            if(myObservation != NULL && myObservation->isValid){
                myObservations.append(myObservation);
            }else{
                this->setUseState(obs.id, false);
            }
        }
        if(myObservations.size() > 0){
            return this->calculateCentralPoint(p, myObservations);
        }
    }
    return false;
}

bool PointFit::calculateCentralPoint(Point &p, QList<Observation*> myObservations){
    //set up l vector with all observations
    OiVec l;
    foreach(Observation *obs, myObservations){
        l.add( obs->myXyz.getAt(0) );
        l.add( obs->myXyz.getAt(1) );
        l.add( obs->myXyz.getAt(2) );
    }
    //Fill A matrix
    OiMat a(l.getSize(), 3);
    for(int i = 0; i < l.getSize(); i++){
        if( (i%3) == 0 ){
            a.setAt(i, Point::unknownX, 1.0);
        }else if( (i%3) == 1 ){
            a.setAt(i, Point::unknownY, 1.0);
        }else if( (i%3) == 2 ){
            a.setAt(i, Point::unknownZ, 1.0);
        }
    }
    //calculate coordinates of central point and corresponding statistic
    OiMat n = a.t() * a;
    OiVec c = a.t() * l;
    try{

        OiMat qxx = n.inv();
        OiVec x = qxx * c; //coordinates of central point
        OiVec v = a * x - l;
        x.add(1.0); //x vector as homogeneous coordinates
        //calculate statistic
        double stdv = 0.0;
        if(v.getSize() > 3){
            double sum_vv = 0.0;
            for(int i = 0; i < v.getSize(); i++){
                sum_vv += v.getAt(i) * v.getAt(i);
            }
            stdv = qSqrt(sum_vv / (v.getSize() - 3));
        }else{
            stdv = 0.0;
        }
        //set up result
        p.myStatistic.s0_apriori = 1.0;
        p.myStatistic.s0_aposteriori = stdv;
        p.xyz = x;
        p.myStatistic.qxx = qxx;
        p.myStatistic.v.replace(v);
        p.myStatistic.stdev = stdv;

        //display the residuals in OpenIndy
        for(int i = 0; i < p.myStatistic.v.getSize() / 3; i++){
            Residual r;

            r.addValue("vx", v.getAt(i*3), UnitConverter::eMetric);
            r.addValue("vy", v.getAt(1+i*3), UnitConverter::eMetric);
            r.addValue("vz", v.getAt(2+i*3), UnitConverter::eMetric);

            p.myStatistic.displayResiduals.append(r);
        }

        p.myStatistic.isValid = true;
        this->myStatistic = p.myStatistic;

        return true;

    }catch(logic_error e){
        this->writeToConsole(e.what());
        return false;
    }catch(runtime_error e){
        this->writeToConsole(e.what());
        return false;
    }
    return false;
}

Now you have implemented your first function. With this function you are able to calculate the central point out of any number of XYZ-observations. Next you will learn how to make your plugin ready for the use in OpenIndy.

- Make your plugin ready

To make your plugin ready you have to update the files p_factory.h and p_factory.cpp first. Please add the include #include "pointFit.h" at the upper part of the file p_factory.h. In the file p_factory.cpp you can find the following two methods:

QList<Function*> OiTemplatePlugin::createFunctions(){
    QList<Function*> resultSet;
    return resultSet;
}
Function* OiTemplatePlugin::createFunction(QString name){
    Function *result = NULL;
    return result;
}

You have to instantiate your function there, so that OpenIndy is able to work with it. Please change these two methods as follows:

QList<Function*> OiTemplatePlugin::createFunctions(){
    QList<Function*> resultSet;
    resultSet.append(new PointFit());
    return resultSet;
}
Function* OiTemplatePlugin::createFunction(QString name){
    Function *result = NULL;
    if(name.compare("PointFit") == 0){
        result = new PointFit();
    }
    return result;
}

Now please compile your plugin and then it will be ready for the use in OpenIndy. The last step is to learn how your plugin can be installed in OpenIndy.

##Install Plugins

To install a plugin in OpenIndy you first have to compile and run OpenIndy itself. Afterwards in OpenIndy select “Plugin” > “load plugins”. This opens a dialog where you can enter the path to the plugin you want to load. After the plugin was checked by OpenIndy you can click “Ok” to load the plugin.

If you have completed the step by step guide you can now load your own plugin as described above. Then you might create a point feature in OpenIndy and mark it as the active feature. Accordingly select “Function” > “set function” which opens another dialog. Switch to the tab “new function”. There you can see the function “PointFit” that you created in your own plugin before.

function Plugin Loader Tutorial