Plugins for alibava-gui

6.1. The Plugin object

As already mentioned, alibava-gui allows to load plugins that will enable the end-user to perform non-standard actions at very specific points of the data acquisition process. These stages are:

  1. New file: every time we open a new file to store the data
  2. Start of run: at the beginning of the run
  3. New event: at the beggining of each event.
  4. End of event: right before the event is going to be dumped to the output file. This gives the oportunity to filter the events or, even, change the data format (for instance filtering out unwanted channels)
  5. End of run

Figure 20 shows the places, during the acquisition loop, in which the plugin methods are called.

The plugins can be written in C++, as shared libraries, or as Python scripts. Examples can be found in the test folder of the distribution.

In C++ the plugins are nothing but a class that derives from Plugin in Plugin.h, which is shown in Example 1. The test folder in the distribution bundle has an example of a C++ plugin together with a make file (UserMakefile). The example is described in Example 3. In the make files use the pkg-config program to get the compilation flags and the path to to the alibava include files.

Example 1Plugin C++ class definition
class Plugin
{
:
    Plugin();

virtual:
    ~Plugin();

:
    enum BlockType = {NewFile=0, StartOfRun, DataBlock, CheckPoint,
            EndOfRun};

    string new_file();
    int start_of_run(int run_type, int nevts, int sample_size);
    bool new_event(int ievt);
    string new_point();
    string end_of_run();
    string filter_event(const EventData & data);
}

The main methods in a Plugin class are:

:
    string new_file();

This method is called at the beginning of a file. This will only happen when data logging is activated. The method returns a string that will be included in the NewFile data block of the data file. See Section 8.1 ― Data format to understand the data blocks.

:
    int start_of_run(int run_type, int nevts, int sample_size);

This method is called at the beginning of each run. The parameters are:

  • run_type: this tells you the current run type (See AlibavaGUI::Runtype in AlibavaGUI.h for the possible values)
  • nevts: the number of events for the current run as set by the user on the alibava-gui GUI.
  • sample_size: this is the number or events that alibava will acquire in each acquisition

start_of_run returns an integer value that is the actual number of events that alibava-gui will consider. If the method is not superseeded by your plugin it will just return nevts. The idea behind this bizarre implementation is to allow the user to perform scans on different parameters and redefine this way the total number of events from the number of scan poitns and the number of events per point.

:
    string end_of_run();

This is called at the end of each run. It can return a buffer with some user data that will be included in the EndOfRun data block of the data file. See Section 8.1 ― Data format to understand the data blocks.

:
    bool new_event(int evt);

This method is called at the beggining of each event. The argument evt is the current event number.

It returns a boolean which is

  • true: when we want to right a CheckPoint block in the data before the current event. The actual content of the CheckPoint block will be given by the new_point method which will only be called when new_event returns true.
  • false: new_point will not be called for this event.

The idea behind this is first, to have a handle right at the beginning of an event and, second, to decide whether we want to add extra information before this event on the data file. This extra information could be the time of the day or, more interesting, the values of the parameters of a scan when a new scan point is going to start.

:
    string new_point();

This method is called only when new_event has returned true as explained above. It returns a string that will be included in a CheckPoint data block right before the current event DataBlock (See Section 8.1 ― Data format to understand the data blocks). This is usefull to store in the file the parameters of a user-defined scan or some information that you would like to write periodically, like humidity (if you can measure it), detector current, etc.

If you use AsciiRoot (see Section 8.3 ― The AsciiRoot class ), to access the CheckPoint data you will have to write your own class deriving from AsciiRoot implementing the method check_point.

:
    string filter_event(const EventData & data);

This is called at the end of an event. The idea is that the user can change the information and the format of a normal DataBlock (see Section 8.1 ― Data format). A good example could be a laser scan in which you would only be interested in very few channels. The method returns a string which, if not empty, will be writen in the DataBlock instead of the normal data.

If you change the BlockData format then you will have to use a class which derives from AsciiRoot (see Section 8.3 ― The AsciiRoot class ) and implements the new_data_block method. Also the pedestal and noise values sotred int he data file will loose their meaning and will be unsusable.

In Python the plugin class is as shown in Example 2.

Example 2Definition of a Python plugin class
class Plugin(extends object):
    def new_file(self) -> string
    def start_of_run(self, run_type, nevts, sample_size) -> int
    def new_event(self, ievt) -> bool
    def new_point(self) -> string
    def end_of_run(self) -> string
    def filter_event(self, time, temperature, value, data) -> string

The parameters, return values and names of the Python methods are like in C++. The only different method is filter_event since it has a different signature.

:
    string filter_event(self, time, temperature, value, data);

The parameters of this method are:

  • time: an integer with the value of the Alibava TDC
  • temperature: an integer with the value of the temperature measured by Alibava
  • value: the value of the scan variable in the predefined scans (delay in laser synchronization and injected charge in calibration)
  • data: an array of 256 integers with the ADC values

Note that the values of time, temperature and value are not decoded and therefore their meaning is as described in Table 4. See the description of the C++ method for more information and warnings.

6.2. Plugin Examples

Plugin examples can be found in the folder test on the distribution bundle.

6.2.1. C++ example

In order to make a useful plugin, you have to create your own class implementing some of the methods in Plugin. An example of such a class implementing a user defined scan is shown in Example 3.

Example 3A C++ plugin to perform a scan
/*
 * test_plugin.cc
 *
 * This is an example of a plugin writen in C++.
 * Look at the documentation in PLugin.h
 *
 *  Created on: Jul 24, 2009
 *      Author: lacasta
 */
#include <iostream>
#include <sstream>
#include <Plugin.h>
#include "NewPoint.h"

/**
 * This is an implementation of the Plugin class. 
 * It is a simple example that will make a scan. 
 * We may use the new_file method to store the parameters
 * of the scan, new_event to determine when a new
 * point is the scan is needed and new_point to store 
 * the actual values of
 * the scan variables.
 */
class MyPlugin : public Plugin
{
    private:
        int npoints;          // N. of pts we want for the scan
        int nevt_per_point;   // N. of pts acquired in each point
        int run_type;         // type of run
        int current_event;    // current event number
        EventCntr handler;    // The object that decides when 
                              // to change to the next point

    public:
        // Constructor with default values
        MyPlugin() :
            npoints(50), nevt_per_point(1000), run_type(-1),
            handler(nevt_per_point), current_event(0) {}

        // destructor
        ~MyPlugin() {}

        /**
         * Declaration of Plugin methods to be implemented
         */
        std::string new_file();
        int start_of_run(int run_type, int nevts, int sample_size);
        std::string end_of_run();
        bool new_event(int evt);
        std::string new_point();
};

std::string MyPlugin::new_file()
{
    std::string rc("New file");
    std::cout << "new_file" << std::endl;
    return rc;
}

int MyPlugin::start_of_run(int runtype, int nevts, int sample_size)
{
    run_type = runtype;
    std::cout << "start_of_run " << nevts << " events. "
              << "Runtype " << run_type
              << std::endl;

    if (sample_size > handler.value())
    {
        handler.value(sample_size);
        nevt_per_point = sample_size;
    }
    handler.reset();
    return npoints*nevt_per_point;
}

std::string MyPlugin::end_of_run()
{
    std::string rc("end_of_run");
    std::cout << "end_of_run" << std::endl;
    return rc;
}

bool MyPlugin::new_event(int ievt)
{
    current_event = ievt;
    return handler(ievt);
}

std::string MyPlugin::new_point()
{
    std::ostringstream ostr;
    ostr << "new point: " << current_event << std::endl;
    std::cout << ostr.str();
    return ostr.str();
}

/*
 * This is the factory function or "hook" in terms of the 
 * Plugin dialog box where the instance of you Plugin 
 * implementation is created. 
 */
extern "C"
{
    Plugin *create_plugin()
    {
        MyPlugin *plugin = new MyPlugin();
        return plugin;
    }
}

In addition to that, we need a factory function that will create the class instance. The name of that function should be specified in the Plugin dialog box when the hook name is required. An example of such a factory function is shown at the very end of Example 3. Note that the function is declared as extern "C". This is important since otherwise alibava-gui will not be able to find it when the shared library is loaded. See the complete example, together with the make file (UserMakefile) in the test folder of the distributed software. In your make files use the pkg-config program to get the compilation flags and the path to to the alibava include files.

6.2.2. Python example

As already mentioned, plugins can also be written as Python scripts. An example similar to the previous C++ plugin is shown in

Example 4An example of a Python plugin
""" An example of an alibava plugin
    This example implements a user defined scan
"""

import time
import inspect

#
# Define some usefull constants
# 
# Block types
NewFile,StartOfRun, DataBlock, CheckPoint, EndOfRun = range(0,5)

# Run types
Unknown,Calibration,LaserSync,Laser,RadSource,Pedestal,LastRType = range(0,7)

        
class MyPlugin(object):
    """ This is an object that can be loaded by alibava to
        be called at certain stages of the DAQ process.
        
    """
    def __init__(self):
        """ Initialization 
        """
        self.current_point = 0
        self.current_event = 0
        self.npoints = 50
        self.nevt_per_point = 1000
        self.handler = EventCounter(self.nevt_per_point)
        self.run_type = -1
        
    def new_file(self):
        """ This is called at the beginning of each file
            It should return a string with information 
            that will be stored in the file header
        """
        print "new_file"
        return "Hola !!!"
    
    def start_of_run(self, run_type, nevts, sample_size):
        """ This is called at the beginning of each run. 
            It should return the total number of events 
            that we want to acquire. As an extra input we 
            have the size of the data chunk that Alibava
            acquires each time we activate the acquisition.
            
            Run types are predefined in the variables:
            Unknown,Calibration,LaserSync,Laser,RadSource,Pedestal
        """
        info_msg("Starting a new run")
        self.handler.start()
        self.run_type = run_type
        write_msg( "start_of_run %d events. Run type: %d" 
                   % 
                   (nevts, run_type ) )
        write_msg( "...sample size %d" % (sample_size) )
        
        self.handler.reset()
        if sample_size > self.handler.nevts:
            print "Changing handler.nevts"
            self.handler.nevts = sample_size
            print "...new value", self.handler.nevts
            self.nevt_per_point = sample_size
            
        if run_type!= RadSource and run_type!=Laser:
            return nevts
        else:
            # Here we return the number of events we really
            # want to acquire given that we need to scan npoints
            # with nevt_per_point events per point.
            return self.npoints * self.nevt_per_point
    
    def end_of_run(self):
        """ Called at the end of a run
        """
        write_msg("end_of_run")
        return "end_of_run"
    
    def new_event(self, ievt):
        """ This is called at the beginning of each event.
            Should return True if we want alibava to call 
            the method new_point.
            
            The input parameter is the current event number
        """
        self.current_event = ievt
        if self.handler.check(ievt):
            return True
        else:
            return False
    
    def move_axis(self):
        """ A dummy function where we could, for instance,
            move the axis that hole the laser or the source
        """
        pass
    
    def new_point(self):
        """ Called every time that new_event returns True
        """
        self.current_point += 1
        self.move_axis()
        print "new_point %d event %d" % (self.current_point, 
                                         self.current_event)
        return "Current axis position is %d" % self.current_point
        
def create_plugin():
    """ This is the 'hook'. This is the method called to
        create an instance of the MyPlugin class
    """
    
    write_msg("Loading %s" % __name__)
    plugin = MyPlugin()
    return plugin

Note that as in the case of C++ we also need here a factory function or hook to create the instance of the Plugin and pass it to Alibava.