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.
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
.
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.
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.
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 | |
---|---|
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" );
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.
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;
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();
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
.
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_ */
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"
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
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.
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.