Chapter 6. Create RePro's

RePro's (Reserach Programs) are the central elements of RELACS. In programming your own RePro you can create and output stimuli, analyze the recorded data and the detected events, save data and results in your preferred format, and display the data in the RePro's widget. For your own experiments you definitely want to define your own RePros to have RELACS doing the experiment on the click of a single button. The following sections explain how to write your own RePro.

6.1. Preparations

In plugins/ there are two shell scripts that help you in setting up the Makefiles and other infra structure needed for compiling RELACS plugins.

If you want to start a new plugin set, then call

./newpluginset

and answer all the questions. This creates a new directory with all the necessary Makefile.am and modifies configure.ac accordingly.

To actually write a new RELACS plugin call

./newplugin

also from the plugins/ directory and answer all the questions. This creates skeleton .cc and .h files and adds the necessary lines to Makefile.am.

6.2. Writing the Code - Basic Concepts

RePro's are C++ classes that inherit the RePro base class from repro.h. If you are not familiar with programming C++ you may want to read A short Introduction to C++ first. However, you basically have to write code for a single function. In addition, RELACS provides some libraries with usefull predefined data types and functions. Most sophisticated C++ programming techniques are therefore hidden behind the scenes.

Before you start writing your code, read Naming Conventions.

In the RePro's constructor you initialize all member variables (if you have some), create the RePro's Options, and setup the widgets the RePro is using. Usually this is the MultiPlot widget which allows you to have several plots. The constructor of a RePro is called once at startup of RELACS. Therefore you do not want to allocate a lot of memory within the constructor.

The main() function does all the work. In this function you usually read out the RePro's options, allocate necessary memory for the data analysis, initialize the plot widget, and display a message in the status bar first. Then you create a stimulus, and repeatedly send the stimulus to the data acquisition board, sleep, analyze the acquired data and detected events, plot and save the results of the analysis, and modify the stimulus. Of course, you do not have to stick to this pattern. Everything else is possible as well.

See also the documentation of the RePro - class for more details.

6.2.1. Options

Usually a RePro has several parameter that determine what the RePro is supposed to do. The Options class which is inherited by the RePro class through the ConfigClass class provides a simple interface that allows to save those parameters to the configuration file, set them via a dialog window, or from a Macro definition.

To add an option, do something like this in the RePro's constructor:

addNumber( "duration", "Duration of signal", 0.1, 0.01, 1000.0, 0.01, "seconds", "ms" );

To retrieve the value of an option in the RePro's main() function do:

double duration = number( "duration" );

Keep in mind, that the user could change the RePro's Options while the RePro is running. With settings() you can retrieve the RePro's options as there were set right at the start of the RePro.

6.2.2. Stimuli

To generate a stimulus, create an OutData variable:

OutData signal;
signal.setSampleRate( 20000.0 );
signal.sineWave( 5000.0, 1.0 );
signal.setIdent( "sine wave 5kHz 1s" );
signal.setTrace( "Left-Speaker" );
signal.setIntensity( 2.0 );

In this example, a signal is created with a sampling rate of 20kHz that is a 5kHz sine wave of 1 second durration. The signal gets an identifier that is stored in the trigger.dat file where all stimuli are recorded. The intensity is used to set the attenuation of the signal.

Send it to the data acquisition board with

write( signal );

After having called write() you usually sleep for at least the duration of the stimulus:

sleep( signal.duration() );
[Warning]Warning
You have to make sure that the stimulus you passed to write(), i.e. the OutData variables, exists and is not modified while the stimulus is put out! Be in particular carefull with locally declared OutData instances!

There is another function which allows you to set the output voltage of an output line to zero:

writeZero( "Right-Speaker" );

6.2.3. Messages

A message can be displayed in the status bar of RELACS with the message function:

message( "Loop: <b>5</b>" );

You can use simple markup to format the message. The message string is also written to standard error and logged into the RELACS log file.

6.2.4. Warnings

You can open a simple window displaying a warning message with the warning() function:

warning( "Unable to create stimulus!" );

The warning string is also written to standard error and logged into the RELACS log file.

You can also specify a time in seconds after which the warning window is closed automatically:

warning( "Unable to create stimulus!", 4.0 );

If you want to terminate the RePro right after issuing the warning you do

warning( "Unable to create stimulus!", 4.0 );
return Failed;

6.2.5. Adjust the Input Gain

Many data acquisition boards have adjustable input gains. There are some functions provided by the RePro class that allow you to set the input gain of each input channel individually. For example, to adjust the input gain for the first data trace such that a maximum value of 2.54 can be read in, do

adjustGain( trace( 0 ), 2.54 );
activateGains();

6.3. Walktrough Example

Create a class ExampleRePro (replace in the following ExampleRePro by an appropriate name) which inherits RePro. Save it as file myrepro.h. Implement all the necessary methods in a file myrepro.cc.

6.3.1. Header File

Any C/C++ header file should start with

#ifndef _RELACS_EXAMPLES_REPROEXAMPLE_H_
#define _RELACS_EXAMPLES_REPROEXAMPLE_H_

to avoid double inclusion of that header file. Here, "EXAMPLES" is the name of the pluginset and "REPROEXAMPLE" the name of the header file. At the very end of your header file you have to close the #ifndef with #endif.

Then we need to include the header file repro.h header file

#include <relacs/repro.h>

work within the relacs namespace

using namespace relacs;

and define the new RePro within the "examples" namespace

namespace examples {

(replace "examples" by the name of your plugin set).

Now we can define our new RePro class

class ReProExample : public RePro

which inherits the RePro base class. Since the GUI of RELACS is using the Qt-library, we need tho call the Q_OBJECT macro

Q_OBJECT

Then we can define some public member functions:

public:

First we need a constructor and a destructor

ReProExample( void );
~ReProExample( void );

The constructor does not get any arguments.

Most importantly, we need to implement the main() function:

virtual int main( void );

That is all we need in our RePro-class definition. Close the class and the namespace and don't forget the #endif:

};


}; /* namespace examples */

#endif /* ! _RELACS_EXAMPLES_REPROEXAMPLE_H_ */

6.3.2. Source File

First, the header file needs to be included

#include < relacs/examples/reproexample.h>

the relacs namespace to be opened

using namespace relacs;

and the new RePro is defined within the "examples" namespace

namespace examples {

(replace "examples" by the name of your plugin set).

Then the constructor can be defined

ReProExample::ReProExample( void )
  : RePro( "ReProExample", "RePro - Example", "Examples", "Jan Benda", "1.0", "July 8, 2008" )
{
  // add some parameter as options:
  addNumber( "duration", "Duration", 0.1, 0.01, 1000.0, 0.02, "sec", "ms" );
}

By calling the constructor of the RePro class, we give the RePro a name ("ReProExample") that is used to access the RePro from the menus, config file, and macros, a title ("RePro - Example") that appears on top of the RePro widget, the name of the plugin set the RePro belongs to ("Examples"), the name of the person who wrote the code ("Jan Benda"), a version string ("1.0"), and the date of the last modification ("July 8, 2008"). In the body of the constructor we just define a single option. This option can be modified from the RePro's dialog, the config file, and the macros.

In our simple example, nothing needs to be done in the destructor

ReProExample::~ReProExample( void )
{
}

Now it is time for the main() function. This is the place where everything happens.

int ReProExample::main( void )
{

We read out the option

  // get options:
  double duration = number( "duration" );

sleep for the desired duration

  sleep( duration );

and return

  return Completed;
}

That's it.

Of course this is a stupid and useless RePro. It simply does nothing for the requested time.

Don't forget to add the following line to the source code:

addRePro( ReProExample );

This line ensures that RELACS detects your class as a RePro.

If there are several RePro classes implemented in a single .cc file you need for each of these RePros such a line:

addRePro( ExampleRePro1 );
addRePro( ExampleRePro2 );
addRePro( ExampleRePro3 );

Finally, the "examples" namespace needs to be closed

}; /* namespace examples */

and the file generated by Qt's moc needs to be included

#include "moc_reproexample.cc"

6.4. Compile your RePro

Simply call

make

in the base directory of the plugin set (or below).

Check your code by running RELACS locally:

./relacslocal -3

Once satisfied install the new plugin

sudo make install

6.5. Adding a new RePro to RELACS

Make sure that the path, where the shared library of your RePro resides, is specified in pluginpathes in relacs.cfg.

You may want your RePro to appear as a button in the RELACS program. For that purpose edit the macros configuration file appropriately. Read Macros for an introduction.

6.6. Debugging your RePro

If RELACS crashes (due to an error in a RePro), you may want to know why this happened. You cannot use a debugger directly, since the LD_LIBRARY_PATH environment variable must be set beforehand such that the RELACS executable relacsexec finds the necessary libraries and plugins.

For your convinience, the relacs script supports a -d and -D option that does all the necessary things and finally starts the GNU debugger gdb. -d directly runs RELACS from within the debugger, whereas -D starts the debugger but you have to run RELACS manually. If you do not want to use gdb as the debugger, you can edit the DEBUGGER variable in the relacs script.

After RELACS crashed, gdb tells you where this happened, i.e. in which function on which line of which file. Usefull commands for gdb are:

  • bt (backtrace): shows you all the functions on the stack

  • up, down: Select the calling or called function

  • info thread: List all the threads

  • thread n: Switch to thread n

  • print: Show the value of variables

  • quit: Quit the debugger

  • help: Help. You can call help with a command name in order to find out more about the command, like help bt.