This commit is contained in:
2021-10-14 13:47:35 +02:00
commit 6625a8dfaa
4026 changed files with 844291 additions and 0 deletions
@@ -0,0 +1,5 @@
# Add all the subdirs as projects of the named branch
OV_ADD_PROJECTS("CONTRIB")
@@ -0,0 +1,3 @@
# Add all the subdirs as projects of the named branch
OV_ADD_PROJECTS("CONTRIB_APPLICATIONS")
@@ -0,0 +1,3 @@
# Add all the subdirs as projects of the named branch
OV_ADD_PROJECTS("CONTRIB_APPLICATIONS_DEVELOPER-TOOLS")
@@ -0,0 +1,113 @@
compilation.log
# Created by https://www.gitignore.io/api/python
# Edit at https://www.gitignore.io/?templates=python
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# End of https://www.gitignore.io/api/python
@@ -0,0 +1,98 @@
///-------------------------------------------------------------------------------------------------
///
/// \file NewBoxPattern.h
/// \brief Class NewBoxPattern
/// \author Thibaut Monseigne (Inria) & Jimmy Leblanc (Polymont) & Yannis Bendi-Ouis (Polymont)
/// \version 1.0.
/// \date 12/03/2020.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/%22%3EGNU Affero General Public License v3.0</a>.
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include "CPolyBox.hpp"
#if defined TARGET_HAS_ThirdPartyPython3 && !(defined(WIN32) && defined(TARGET_BUILDTYPE_Debug))
#if defined(PY_MAJOR_VERSION) && (PY_MAJOR_VERSION == 3)
namespace OpenViBE { namespace Plugins
{
namespace PyBox
{
class CBoxAlgorithmNewBoxPattern final : public CPolyBox
{
public:
CBoxAlgorithmNewBoxPattern() { m_script = "NewScript.py";}
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm < IBoxAlgorithm >, OVP_ClassId_BoxAlgorithm_NewBoxPattern)
};
class CBoxAlgorithmNewBoxPatternListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
bool onInputAdded(Kernel::IBox& box, const size_t index) override
{
box.setInputType(index, OV_TypeId_StreamedMatrix);
return true;
}
bool onOutputAdded(Kernel::IBox& box, const size_t index) override
{
box.setOutputType(index, OV_TypeId_StreamedMatrix);
return true;
}
_IsDerivedFromClass_Final_(Toolkit::TBoxListener < IBoxListener >, CIdentifier::undefined())
};
class CBoxAlgorithmNewBoxPatternDesc final : virtual public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("NewBoxPattern"); }
CString getAuthorName() const override { return CString("NewAuthor"); }
CString getAuthorCompanyName() const override { return CString("NewCompany"); }
CString getShortDescription() const override { return CString("Default Python Description"); }
CString getDetailedDescription() const override { return CString(""); }
CString getCategory() const override { return CString("Scripting/Pybox/"); }
CString getVersion() const override { return CString("0.1"); }
CString getStockItemName() const override { return CString("gtk-convert"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_NewBoxPattern; }
IPluginObject* create() override { return new CBoxAlgorithmNewBoxPattern; }
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmNewBoxPatternListener; }
void releaseBoxListener(IBoxListener* pBoxListener) const override{ delete pBoxListener; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addSetting("Clock frequency (Hz)", OV_TypeId_Integer, "64");
// <tag> settings
prototype.addFlag(Kernel::BoxFlag_CanAddInput);
prototype.addFlag(Kernel::BoxFlag_CanModifyInput);
prototype.addFlag(Kernel::BoxFlag_CanAddOutput);
prototype.addFlag(Kernel::BoxFlag_CanModifyOutput);
prototype.addFlag(Kernel::BoxFlag_CanAddSetting);
prototype.addFlag(Kernel::BoxFlag_CanModifySetting);
prototype.addInputSupport(OV_TypeId_Signal);
prototype.addInputSupport(OV_TypeId_Stimulations);
prototype.addInputSupport(OV_TypeId_StreamedMatrix);
prototype.addOutputSupport(OV_TypeId_Signal);
prototype.addOutputSupport(OV_TypeId_Stimulations);
prototype.addOutputSupport(OV_TypeId_StreamedMatrix);
// <tag> input & output
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_NewBoxPatternDesc)
};
}
}
}
#endif // #if defined(PY_MAJOR_VERSION) && (PY_MAJOR_VERSION == 3)
#endif // TARGET_HAS_ThirdPartyPython3
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@@ -0,0 +1,57 @@
PROJECT(openvibe-plugins-contrib-pybox)
SET(PROJECT_VERSION_MAJOR ${OV_GLOBAL_VERSION_MAJOR})
SET(PROJECT_VERSION ${OV_GLOBAL_VERSION_STRING})
FILE(GLOB_RECURSE source_files src/*.cpp src/*.hpp src/*.h src/*.inl)
ADD_LIBRARY(${PROJECT_NAME} SHARED ${source_files})
SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
FOLDER ${PLUGINS_FOLDER}
COMPILE_FLAGS "-DOVP_Exports -DOVP_Shared")
INCLUDE_DIRECTORIES("src")
# ---------------------------------
# OpenVibe Modules (uncomment usefull package)
# ---------------------------------
# OpenViBE Base
INCLUDE("FindOpenViBE")
INCLUDE("FindOpenViBECommon")
INCLUDE("FindOpenViBEToolkit")
# OpenViBE Module
INCLUDE("FindOpenViBEModuleEBML")
INCLUDE("FindOpenViBEModuleSystem")
# OpenViBE Third Party
INCLUDE("FindThirdPartyBoost")
INCLUDE("FindThirdPartyPython3")
# ---------------------------------
# Target macros
# Defines target operating system
# Defines target architecture
# Defines target compiler
# ---------------------------------
SET_BUILD_PLATFORM()
# -----------------------------
# Install files
# -----------------------------
INSTALL(TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION ${DIST_BINDIR}
LIBRARY DESTINATION ${DIST_LIBDIR}
ARCHIVE DESTINATION ${DIST_LIBDIR})
INSTALL(DIRECTORY box-tutorials DESTINATION ${DIST_DATADIR}/openvibe/scenarios/)
INSTALL(DIRECTORY share/ DESTINATION ${DIST_DATADIR}/openvibe/plugins/python3)
# ---------------------------------
# Test applications (uncomment to enable your test directory)
# ---------------------------------
#IF(OV_COMPILE_TESTS)
# ADD_SUBDIRECTORY(test)
#ENDIF(OV_COMPILE_TESTS)
@@ -0,0 +1,287 @@
# OpenViBE Python
This Project aims to bring modifications to OpenVibe and to widen its data-oriented functionnalities. OpenVibe is a signal processing software allowing the use of machine learning algorithms, however their number is reduced. Thus, we woud like to benefit from the Openvibe Python scripting box (which allows the use of python scripts in OV) in order to allow users to use Scikit-learn Machine Learning algorithms.
## Dependancies
`pip install pyqt5 pandas numpy natsort pygame sklearn matplotlib pyriemann`
## What OpenViBE Python allows
- The use of components already implemented in OpenVibe that will allow you to :
- The use of ML algorithms from Scikit-learn and Pyriemann (Library centered on the use of Riemannian geometry) that will allow you to train and store models.
- Visualization of your data in 2D/3D via a PCA or LDA.
- Easily create datasets compatible with OpenVibe's operation
- The use of a manager that allows you to simply :
- Create new boxes in OpenVibe
- Create new types of openvibe settings that can be used in your new boxes.
- The reuse of our scripts in order to simply implement your own python data management
1. The new boxes
TrainerML / ML Class Boxes
Box ProcessML
Box DataViz
DatasetCreator Box
2. The Pybox Manager
Box Manager
Stimulations / Labels Manager
Custom Settings Manager
3. Internal functioning and details
Our box model: PolyBox
Input management with PolyBox, two modes possible
Duplicating the Python Scripting Box
PolyBox: Automatic data storage
Managing Custom Settings
Translated with www.DeepL.com/Translator (free version)
## Table des matières
- [OpenViBE Python](#openvibe-python)
- [Dependancies](#dependancies)
- [What OpenViBE Python allows](#what-openvibe-python-allows)
- [Table des matières](#table-des-mati%c3%a8res)
- [1. The new boxes](#1-the-new-boxes)
- [TrainerML Class / ML Boxes](#trainerml-class--ml-boxes)
- [Scikit-learn](#scikit-learn)
- [Pyriemann](#pyriemann)
- [Box ProcessML](#box-processml)
- [Box DataViz](#box-dataviz)
- [Box DatasetCreator](#box-datasetcreator)
- [2. The Pybox Manager](#2-the-pybox-manager)
- [Box Manager](#box-manager)
- [Stimulations / Labels Manager](#stimulations--labels-manager)
- [Custom Settings Manager](#custom-settings-manager)
- [3. Internal Functioning and Details](#3-internal-functioning-and-details)
- [Our Box model : PolyBox](#our-box-model--polybox)
- [Input management with PolyBox, two possible modes](#input-management-with-polybox-two-possible-modes)
- [Duplicating the Python Scripting Box](#duplicating-the-python-scripting-box)
- [Automatic data storage](#automatic-data-storage)
- [Managing Custom Settings](#managing-custom-settings)
## 1. The new boxes
### TrainerML Class / ML Boxes
The TrainerML class defined in TrainerML.py is a class that inherits from [PolyBox](#our-box-model-polybox), its purpose is to be used in boxes that will be configured to allow the use of certain learning machine algorithms. The following parameters can be passed to it:
- **Filename to save model to** : Path to the file in which to save the model. If no file is indicated, then the model will not be saved.
- **Filename to load model from** : Path to the file in which the model to be loaded is saved. If a file is specified and exists, no model will be created and the model contained in the file will be loaded and used for the current session.
- **classifier** : Algorithm you wish to use. The various Algorithms are from Scikit-learn or Pyriemann.
- **discriminator** : In case the chosen classifier is `TangentSpace`, it is necessary to provide a second algorithm which will be used to classify after the projection on the tangent space. All the previous algorithms can be used except `TangentSpace` and `MDM`. If no algorithm is specified, `LinearDiscriminantAnalysis` will be used by default.
Similarly for `MDM`, a second algorithm may be used, but is not mandatory.
- **labels**: If the read mode is Poly-Mode, you must specify the list of labels as `my label1, my label2, my label3`, otherwise, the labels will be as follows `1, 2, 3, 4, ...`. If the read mode is ov-mode, you can leave the field blank, the labels will be extracted from the stimuli.
- **Test set share**: float between 0 and 1 (1 not included) which represents the proportion of the input dataset transformed into a test set, allowing us to evaluate our model once the training on the set train is done. If 0 is filled in, then all the data will constitute the train set and no metrics will be displayed.
Here are the new algorithms / boxes implemented in OV :
#### Scikit-learn
<https://scikit-learn.org/stable/>
> | Box Name | Algorithm |
> | :-: | :-: |
> | Nearest Centroid | NearestCentroid |
> | Nearest Neighbors Classifier | KNeighborsClassifier |
> | Gaussian Naive Bayes | GaussianNB |
> | Stochastic Gradient Descent | SGDClassifier |
> | Logistic Regression | LogisticRegression |
> | Decision Tree Classifier | DecisionTreeClassifier |
> | Extra Trees | ExtraTreesClassifier |
> | Bagging | BaggingClassifier |
> | Random Forest | RandomForestClassifier |
> | Support Vector Machine | LinearSVC |
> | Linear Discriminant Analysis | LinearDiscriminantAnalysis |
> | AdaBoost | AdaBoostClassifier |
> | Multi Layer Perceptron | MLPClassifier |
> | Linear SVC | LinearSVC |
#### Pyriemann
<https://pyriemann.readthedocs.io/en/latest/index.html>
> | String | Algorithm |
> | :-: | :-: |
> | Riemann Minimum Distance to Mean | MDM |
> | Riemann Tangent Space | TangentSpace |
You can easily find information on each of these methods in the docs of their library.
For each of these classifiers, an Openvibe box using TrainerML with the appropriate parameters has been created. So, if you want to train with the Random Forest algorithm of Scikit-learn for example, you just have to look for the associated box in Openvibe and you will be able to use it directly, and modify the parameters related to the algorithm. You will find the information concerning all these parameters on the respective pages of the algorithms, ex: <https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.html.>
> ![random-forest.PNG](Assets/Doc/random-forest.PNG)
>
> Example of parameterization of a box implementing Random Forest
### Box ProcessML
This box allows you to make classification predictions on new data using a previously trained model (via TrainerML or the boxes that inherit it, e.g. SVM, LDA, RandomForest ...).
- **Model Filename**: Path where the model you want to use is stored.
- **Filename to save predictions** : Path to the file where to save the predictions. If no file is specified, then the predictions will not be saved. The predictions are saved as a string where each prediction is separated from the others by a comma: `pred1,pred2,pred3 ...`.
Once the model is loaded, the predictions will be made on each chunk of data received, they can be used in real time, and if a path is given, they can be saved for later use.
### Box DataViz
The DataViz Box allows the visualization of our data. To do this, it applies a dimension reduction via an LDA or PCA and then displays our data using the matplotlib library. It inherits from PolyBox. It requires the following parameters:
- **Path to save the model**: Path to the file in which to save the model. If no file is specified, then the model will not be saved.
- **Path to load the model** : Path to the file in which is saved the model you want to load. If a file is specified and exists, then no model will be created and the model contained in the file will be loaded and used for the current session.
- **Algorithm (PCA or LDA)** : Name of the algorithm to be used to reduce dimensions. Accepted values are `PCA` or `LDA`. The default algorithm used is LDA.
- **Dimension reduction**: Number of dimensions to display. The different possible values are 2 and 3. By default, if no number is given or if the field is filled incorrectly, the number of dimension is 2.
- **labels** : List of labels to indicate as `my label1, my label2, my label3` if the read mode is poly-mode. Otherwise, the labels will be in the form `1, 2, 3, 4, ...`.
### Box DatasetCreator
To facilitate data acquisition during our experiments, we created the python box `DatasetCreator`. This box takes a signal as an input and outputs a `OVTK_StimulationId_ExperimentStop' stimulation when it has finished creating the dataset.
It works in the following way: the user first chooses some labels, then the box will randomly determine an order between them. It will then record the user's brain activity while verbally indicating the current action. In this way, we will be able to create a labeled dataset that can be used for learning.
The recording of an action takes place in the following way:
1. Audible warning of the beginning of the action.
2. 2 second wait.
3. Recording for 10 seconds.
4. Audible warning of the end of the action.
5. Wait for 3 seconds.
It can be configured by indicating :
- The path to the directory that will contain the data.
- The number of folds you wish to obtain.
- The number of actions you wish to record. That is to say 30 if you wish to obtain 30 recordings of 10 seconds distributed among the different labels.
- The names of the labels you want to record (/!\ Attention, these names must have a corresponding mp3 file in `pybox-manager/Assets/Sounds/`, you can create new labels with the manager).
- A boolean indicating whether you want several CSVs or only one CSV. If you enter "true", then the data will be split into as many CSVs as there are actions, one CSV per action. If you enter "false", then the data will all be recorded in a single CSV, in which the start of a new recording for a label will be indicated in the stimuli.
## 2. The Pybox Manager
The Pybox Manager allows you to simply create and incorporate new boxes running a Python script of your choice, new labels/stimulations and new Custom Settings for your python boxes into OpenVibe.
To run it: `python pybox_manager.py`.
(Python 2.7 and python 3.X compatible.)
An option is available to enable the so-called "developer settings" i.e. the ones you created.
To do this, run the manager with the `-mode=developer` option.
### Box Manager
The Box Manager looks like this:
> ![box_manager.png](Assets/Doc/box_manager.png)
>
> PyBox Manager.
- New: Create a new box
- Duplicate: duplicates the currently selected box.
- Reset Box: resets the selected box, cancels all changes made since the last compilation.
- Category : category in which the box will be stored in OV.
- Author : add the author's name in the box's references.
- Settings: Allows you to manage all the settings necessary to use the box. These can be types from Openvibe (String, Float etc.) or types created by yourself with the [Custom settings Manager](#custom-settings-manager)
- Inputs: Used to fill in the different inputs that your box will receive.
- Outputs : Used to fill in the different outputs that your box will send.
- Enable settings / inputs / outputs modifications : Allows you to prevent the users of your box from modifying these elements later on.
- Mode : allows you to quickly configure the inputs of your box according to the mode you want to use. [Inputs Polybox](#input-management-with-polybox-two-possible-modes)
Once your modifications are finished you can press Build to have the modifications taken into account, and the compilation is done (mandatory to have your modifications and new boxes). The compilation.log file contains information from the last compilation.
Delete Box deletes the currently selected box (a compilation is necessary to take the deletion into account).
### Stimulations / Labels Manager
When reading and processing our signals, OpenViBE can handle stimulations. These can be used to label our data during a classification/training procedure.
All these stimulations are indicated in the file `pybox-manager/share/PolyStimulations.py`.
The DatasetCreator box we have created allows you to monitor the creation of a labeled dataset for the user. To do this, the box plays a sound during each action, indicating the action the user should think about. Basically, the user will have at his disposal about ten different labels.
We allow the user to add labels (stimulations) via the `Stimulations/Labels Manager` interface present in the manager.
To do this, the manager must provide the name of the label, and a corresponding `.mp3` file, which will be played by the DatasetCreator.
> ![add_label.jpeg](Assets/Doc/add_label.jpeg)
>
> Add a label/stimulation to OpenViBE.
### Custom Settings Manager
The `Custom Settings Manager` allows you to create or delete special types of settings, as well as possible values for these settings, according to your needs.
This is especially useful to allow easy use on OpenVibe using a drop-down list. For example, if you want to be able to choose one algorithm among several, directly in your OpenVibe box configuration, you just have to create this new type as well as the associated values via the manager as shown in the following illustration.
To be able to use them with the manager when defining the parameters of a box, you have to launch the manager with the option `mode=developer`.
> ![manage_custom_settings.png](Assets/Doc/manage_custom_settings.png)
>
> Add/Remove/Manage Custom Settings.
Result in OpenVibe :
> ![classifiers.png](Assets/Doc/classifiers.png)
>
> Example with a new type 'classifier'.
## 3. Internal Functioning and Details
### Our Box model : PolyBox
To facilitate our development phases, we have added in `pybox-manager/share/PolyBox.py` a class called `PolyBox`. This class inherits from the OVBox class. It automates the reception and storage of an input signal and allows the development of simple methods called at key moments:
- **on_initialize(self) :** called at initialization, this method aims at allowing the user to define a particular behavior during initialization.
- **on_header_received(self, header) :** called at each reception of a header, this method aims to allow the user to define a particular behavior when receiving a header.
- **on_chunk_received(self, chunk, label, shape) :** called at each reception of a chunk, this method aims to allow the user to define a particular behavior when receiving a chunk.
- **on_end_box(self) :** called just before the box finishes its work, this method aims to allow the user to define a particular behavior when the box finishes.
We have created this box in order to be able to attribute behaviors adapted to our use cases, in particular to easily create boxes proposing Machine Learning algorithms based on its architecture.
#### Input management with PolyBox, two possible modes
Any box inheriting the PolyBox automatically has two possible read modes to retrieve input data.
The first mode (`ov-mode`) corresponds to the classic OpenViBE reading mode: the different classes are all included in the same .csv file, and we use input stimuli to separate our data into different classes. To use this mode, you just have to give the box only two inputs: 1 StreamedMatrix and 1 Stimulation.
For example:
> ![ov-mode.jpeg](Assets/Doc/ov-mode.jpeg)
>
> **Figure 1** - ov-mode
The second mode (`poly-mode`) consists in considering as many files as there are classes, i.e. one .csv file per class. To read all these files, the box then needs to have at least as many StreamedMatrix inputs as there are different classes. To use this mode, you just have to create only StreamedMatrix inputs.
(Often, this mode requires a `label` parameter in which to fill in the labels of our different classes in the form: `my label1, my label2, my label3`).
For example:
> ![poly-mode.jpeg](Assets/Doc/poly-mode.jpeg)
> **Figure 2** - poly-mode
Thus, these two reading modes are transparent for the user and allow him to operate the `PolyBoxes` either with a single file containing all the actions, or with several files: one per action.
The user doesn't need to indicate anything for the box to choose the right behavior to adopt, the box chooses its behavior according to its inputs.
### Duplicating the Python Scripting Box
Toutes les modifications ont été faite, on peut maintenant compiler. Toutes ces étapes sont implémentées dans `pybox-manager/ov-manager.py`.
All the modifications we have made to the software are based on the duplication of the Python Scripting box. Since the latter allows the use of a python script, we decided to create a manager that allows us to automatically make changes in the OV code to duplicate the C++ files needed to duplicate the Python Scripting Box. This way, we can definitively associate a script to a box, and let the user configure it and integrate it into OpenViBE.
Each time a python box is created, the manager performs the following tasks:
1. Go to the root directory of the python boxes: `pybox-manager/src/`.
2. We duplicate `pybox-manager/Assets/BoxManager/NewBoxPattern-skeletton.h` into `ovpBoxName.h`
3. Insert the CIdentifier declaration into `src/defines.hpp`.
4. Add to `src/main.cpp` the imports of the newly created files for the box creation as well as the declarations.
5. In `src/box-algorithms/ovpBoxName.h`, replace the box name and includes/declarations.
6. Change the box name and description in `src/box-algorithms/ovpBoxName.h`.
7. Set the path of the script to be executed in `src/box-algorithms/ovpBoxName.h`.
8. Remove the possibility to change the path.
9. Add the parameters of our box to `src/box-algorithms/ovpBoxName.h`.
10. Add the box's inputs and outputs to `src/box-algorithms/ovpBoxName.h`.
All changes have been made, we can now compile. All these steps are implemented in `pybox-manager/ov-manager.py`.
### Automatic data storage
Automatically, the box stores all received chunk in `self.data`. This can be handy if the user wants to use all the data at the end of the box (to train a learning machine model for example).
However, it is possible to prevent this behavior voluntarily. To do this, simply give as a parameter when creating the PolyBox `record=False`. By default, record is set to True.
### Managing Custom Settings
We add to `meta/sdk/toolkit/include/toolkit/ovtk_defines.hpp` the declaration of our new Custom Settings and to `meta/sdk/toolkit/src/ovtk_main.cpp` the creation of the custom setting and the declaration of its different values.
@@ -0,0 +1,266 @@
# OpenViBE Python
Ce projet a pour but d'apporter des modifications à OpenViBE et d'ouvrir ses possibilités d'un point de vue Data Science. OpenViBE est un logiciel de traitement du signal permettant l'utilisation d'algorithmes de machine learning, cependant ces algorithmes sont limités en choix. Ainsi, nous souhaitons utiliser l'ouverture laissée par la Python scripting box d'OpenViBE (qui permet d'utiliser des scripts python dans OV) pour permettre aux utilisateurs d'utiliser les algorithmes implémentés par des librairies tierces.
## Dépendances
`pip install pyqt5 pandas numpy natsort pygame sklearn matplotlib pyriemann`
## Ce que permet OpenViBE Python
- L'utilisation de composants déjà implémentés dans OpenVibe qui vous permettront :
- l'utilisation d'algorithmes de ML issus de Scikit-learn ainsi que Pyriemann (Librarie centrée sur l'utilisation de la géométrie Riemannienne) qui vous permettront d'entrainer et de stocker des modèles.
- La visualisation de vos données en 2D/3D via une PCA ou une LDA.
- Créer facilement des datasets compatibles avec le fonctionnement d'OpenVibe
- L'utilisation d'un manager qui permet de simplement :
- Créer des nouvelles boites dans OpenVibe
- Créer des nouveaux types de "settings" openvibe qui pourront être utilisés dans vos nouvelles boites
- La réutilisation de nos scripts afin d'implenter simplement votre propre gestion de la donnée en python
## Table des matières
- [OpenViBE Python](#openvibe-python)
- [Dépendances](#d%c3%a9pendances)
- [Ce que permet OpenViBE Python](#ce-que-permet-openvibe-python)
- [Table des matières](#table-des-mati%c3%a8res)
- [1. Les nouvelles boites](#1-les-nouvelles-boites)
- [Classe TrainerML / ML Boxes](#classe-trainerml--ml-boxes)
- [Scikit-learn](#scikit-learn)
- [Pyriemann](#pyriemann)
- [Box ProcessML](#box-processml)
- [Box DataViz](#box-dataviz)
- [Box DatasetCreator](#box-datasetcreator)
- [2. Le Pybox Manager](#2-le-pybox-manager)
- [Box Manager](#box-manager)
- [Stimulations / Labels Manager](#stimulations--labels-manager)
- [Custom Settings Manager](#custom-settings-manager)
- [3. Fonctionnement interne et détails](#3-fonctionnement-interne-et-d%c3%a9tails)
- [Notre modèle de boite : PolyBox](#notre-mod%c3%a8le-de-boite--polybox)
- [Gestion des inputs avec PolyBox, deux modes possibles](#gestion-des-inputs-avec-polybox-deux-modes-possibles)
- [Duplication de la Python Scripting Box](#duplication-de-la-python-scripting-box)
- [PolyBox : Stockage automatique des données](#polybox--stockage-automatique-des-donn%c3%a9es)
- [Gestion des Custom Settings](#gestion-des-custom-settings)
## 1. Les nouvelles boites
### Classe TrainerML / ML Boxes
La classe TrainerML définie dans TrainerML.py est une classe qui hérite de [PolyBox](#notre-modèle-de-boite-polybox), son but est d'être utilisée dans des boîtes qui vont être paramétrées afin de permettre l'utilisation de certains algorithmes de machine learning. On peut lui passer les paramètres suivants :
- **Filename to save model to** : Chemin vers le fichier dans lequel sauvegarder le modèle. Si aucun fichier n'est indiqué, alors le modèle ne sera pas enregistré.
- **Filename to load model from** : Chemin vers le fichier dans lequel est sauvegardé le modèle que l'on souhaite charger. Si un fichier est renseigné et qu'il existe, aucun modèle ne sera créé et le modèle contenu dans le fichier sera chargé et utilisé pour la session en cours.
- **classifier** : Algorithme que l'on souhaite utiliser. Les différents Algorithmes sont issus de Scikit-learn ou bien de Pyriemann.
- **discriminator** : Dans le cas où le classifier choisi est `TangentSpace`, il est nécessaire de fournir un second algorithme qui servira de classifier après la projection sur l'espace tangent. Tous les algorithmes précédents peuvent être utilisés sauf `TangentSpace` et `MDM`. Si aucun algorithme n'est indiqué, `LinearDiscriminantAnalysis` sera utilisé par défault.
De même pour `MDM`, un second algorithme peut être utilisé, mais n'est pas obligatoire.
- **labels** : Si le mode de lecture est Poly-Mode, vous devez indiquer la liste de labels sous la forme `mon label1, mon label2, mon label3`, sinon, les labels seront de la forme `1, 2, 3, 4, ...`. Si le mode de lecture est ov-mode, vous pouvez laissez le champs vide, les labels seront extraits des stimulations.
- **Test set share** : float compris entre 0 et 1 (1 non inclus) qui représente la proportion du dataset d'entrée transformée en test set, nous permettant d'évaluer notre modèle une fois l'entrainement sur le train set effectué. Si 0 est renseigné, alors toutes les données constitueront le train set et aucune métrique ne sera affichée.
Voici les nouveaux algorithmes / boites implémentés dans OV :
#### Scikit-learn
<https://scikit-learn.org/stable/>
| Nom de la Box | Algorithme |
| :-: | :-: |
| Nearest Centroid | NearestCentroid |
| Nearest Neighbors Classifier | KNeighborsClassifier |
| Gaussian Naive Bayes | GaussianNB |
| Stochastic Gradient Descent | SGDClassifier |
| Logistic Regression | LogisticRegression |
| Decision Tree Classifier | DecisionTreeClassifier |
| Extra Trees | ExtraTreesClassifier |
| Bagging | BaggingClassifier |
| Random Forest | RandomForestClassifier |
| Support Vector Machine | LinearSVC |
| Linear Discriminant Analysis | LinearDiscriminantAnalysis |
| AdaBoost | AdaBoostClassifier |
| Multi Layer Perceptron | MLPClassifier |
| Linear SVC | LinearSVC |
#### Pyriemann
<https://pyriemann.readthedocs.io/en/latest/index.html>
| String | Algorithme |
| :-: | :-: |
| Riemann Minimum Distance to Mean | MDM |
| Riemann Tangent Space | TangentSpace |
Vous trouverez facilement des informations sur chacune de ces méthodes dans la doc de leur librairies.
Pour chacun de ces classifiers, une boite Openvibe utilisant TrainerML avec les paramètres adéquats a été créé. Ainsi, si vous souhaitez réaliser un entrainement avec l'algorithme Random Forest de Scikit-learn par exemple, il vous suffit de chercher la boite associée dans Openvibe et vous pourrez directement l'utiliser, et modifier les paramètres relatifs à l'algorithme. Vous trouverez les informations concernant tous ces paramètres sur les pages respectives des algorithmes, ex : <https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.html>
> ![random-forest.PNG](Assets/Doc/random-forest.PNG)
>
> Exemple de paramétrisation d'une boite implémentant Random Forest
### Box ProcessML
Cette box permet de réaliser des prédictions de classification sur des nouvelles données en utilisant un modèle préalablement entraîné (via TrainerML ou les boites qui en héritent, e.g. SVM, LDA, RandomForest ... )
- **Model Filename** : Chemin où le modèle que l'on souhaite utiliser est stocké.
- **Filename to save predictions** : Chemin vers le fichier dans lequel sauvegarder les prédictions. Si aucun fichier n'est indiqué, alors les prédictions ne seront pas sauvegardées. Les prédictions sont enregistrées sous la forme d'une chaîne de caractère où chaque prédiction est séparée des autres par une virgule : `pred1,pred2,pred3 ...`
Une fois le modèle chargé, les prédictions seront réalisées sur chaque chunk de donnée reçu, elles pourront être utilisées en temps réel, et si un chemin est indiqué, elles pourront être sauvegardées pour un usage ultérieur.
### Box DataViz
La Box DataViz permet la visualisation de nos données. Pour ce faire, elle applique une réduction de dimension via une LDA ou une PCA, puis affiche nos données en utilisant la bibliothèque matplotlib. Elle hérite de PolyBox.
Elle nécessite les paramètres suivants :
- **Path to save the model** : Chemin vers le fichier dans lequel sauvegarder le modèle. Si aucun fichier n'est indiqué, alors le modèle ne sera pas enregistré.
- **Path to load the model** : Chemin vers le fichier dans lequel est sauvegardé le modèle que l'on souhaite charger. Si un fichier est renseigné et qu'il existe, aucun modèle ne sera créé et le modèle contenu dans le fichier sera chargé et utilisé pour la session en cours.
- **Algorithm (PCA or LDA)** : Nom de l'algorithme à utiliser pour réduire les dimensions. Les valeurs acceptées sont `PCA` ou `LDA`. L'algorithme utilisé par défaut est LDA.
- **Dimension reduction** : Nombre de dimension à afficher. Les différentes valeurs possibles sont 2 et 3. Par défaut, si aucun nombre n'est indiqué ou si le champs est mal rempli, le nombre de dimension est 2.
- **labels** : Si le mode de lecture est Poly-Mode, vous devez indiquer la liste de labels sous la forme `mon label1, mon label2, mon label3`, sinon, les labels seront de la forme `1, 2, 3, 4, ...`. Si le mode de lecture est ov-mode, vous pouvez laissez le champs vide, les labels seront extraits des stimulations.
### Box DatasetCreator
Pour faciliter l'acquisition de données lors de nos expériences, nous avons créé la box python `DatasetCreator`. Cette dernière prend en entrée un signal et émet en sortie une stimulation de type `OVTK_StimulationId_ExperimentStop` lorsqu'elle a fini de créer le dataset.
Elle fonctionne de la manière suivante : l'utilisateur choisit au préalable certains labels, puis la box va aléatoirement déterminer un ordre entre ceux-ci. Elle va ensuite monitorer l'utilisateur sur les actions à penser en indiquant oralement l'action courante. De cette manière, nous allons pouvoir créer un jeu de données labelisées qui pourra être utilisé pour de l'apprentissage.
L'enregistrement d'une action se déroule de la manière suivante :
1. Avertissement sonore du début de l'action.
2. Attente de 2 secondes.
3. Enregistrement pendant 10 secondes.
4. Avertissement sonore de la fin de l'action.
5. Attente de 3 secondes.
On peut la configurer en indiquant :
- Le path jusqu'au répertoire devant contenir les données.
- Le nombre de folds que l'on souhaite obtenir.
- Le nombre d'action que l'on souhaite enregistrer. C'est à dire 30 si vous souhaitez obtenir 30 enregistrement de 10 secondes répartis parmi les différents labels.
- Le nom des labels que vous souhaitez enregistrer. (/!\ Attention, ces noms doivent avoir un fichier mp3 correspondant dans `pybox-manager/Assets/Sounds/`, vous pouvez créer de nouveaux labels avec le manager.)
- Un boolean indiquant si vous souhaitez plusieurs CSV ou un seul CSV. Si vous renseignez "true", alors les données seront réparties en autant de CSV qu'il y a d'action, un CSV par action. Si vous renseignez "false", alors les données seront toutes enregistrées en un seul CSV, dans lequel le début d'un nouvel enregistrement pour un label sera indiqué dans les stimulations.
## 2. Le Pybox Manager
Le Pybox Manager permet de simplement créer et incorporer à OpenVibe de nouvelles boites éxécutant un script Python de votre choix, de nouveaux labels/stimulations et de nouveau Custom Settings pour vos box python.
Pour l'éxécuter : `python pybox_manager.py`
(Compatible python 2.7 et python 3.X.)
Une option est disponible pour activer les settings dit "développeurs" c-à-d ceux que vous avez créés.
Pour ceci lancer le manager avec l'option `mode=developer`
### Box Manager
Le Box Manager se présente comme ceci :
> ![box_manager.png](Assets/Doc/box_manager.png)
>
> Visualisation du PyBox Manager.
- New : Créer une nouvelle boite
- Duplicate : duplique la boite actuellement séléctionnée.
- Reset Box : réinitialise la box séléctionnée, annule toutes les modifications effectuées depuis la dernière compilation.
- Category : catégorie dans laquelle la boîte sera rangée dans OV.
- Author : ajoute le nom des auteurs dans les références de la boite.
- Settings : Permet d'administrer tous les settings nécessaires afin d'utiliser la box. Ceux-ci peuvent être des types issus d'Openvibe (String, Float etc.) ou bien des types créés par vous même grâce au [Custom settings Manager](#custom-settings-manager)
- Inputs : Sert à renseigner les différents inputs que va recevoir votre boite
- Outputs : : Sert à renseigner les différents outputs que va envoyer votre boite.
- Enable settings / inputs / outputs modifications : permet d'empêcher les utilisateurs de votre boite de modifier ultérieurement ces éléments.
- Mode : permet de configurer rapidement les inputs de votre box selon le mode que vous souhaitez utiliser. [Inputs Polybox](#gestion-des-inputs-avec-polybox-deux-modes-possibles)
Une fois que vos modifications sont terminées vous pouvez appuyer sur Build pour que les modifications soient prises en compte, et que la compilation s'effectue (obligatoire pour avoir vos modifications et nouvelles boites). Le fichier compilation.log contient les informations issus de la dernière compilation.
Delete Box supprime la boite actuellement séléctionnée (une compilation est nécéssaire pour prendre la suppression en compte)
### Stimulations / Labels Manager
Lors de la lecture et du traitement de nos signaux, OpenViBE peut gérer des stimulations. Ces dernières peuvent servir à indiquer le label de nos données lors d'une procédure de classification/entraînement.
L'ensemble de ces stimulations sont indiquées dans le fichier `pybox-manager/share/PolyStimulations.py`.
La box DatasetCreator que nous avons créée permet de monitorer la création d'un dataset labelisé pour l'utilisateur. Pour ce faire, la box joue un son lors de chaque action, indiquant l'action auquel l'utilisateur doit penser. De base, l'utilisateur aura a disposition une dizaine de labels différents.
Nous permettons à l'utilisateur d'ajouter des labels (stimulations) via l'interface `Stimulations/Labels Manager` présent dans le manager.
Pour ce faire, ce dernier doit fournir le nom du label, et un fichier `.mp3` correspondant, qui sera joué par le DatasetCreator.
> ![add_label.jpeg](Assets/Doc/add_label.jpeg)
>
> Add a label/stimulation to OpenViBE.
### Custom Settings Manager
Le `Custom Settings Manager` permet de créer ou de supprimer des types de paramètres spéciaux, ainsi que des valeurs possibles pour ces paramètres, selon vos besoins.
C'est notamment utile pour permettre une utilisation facile sur OpenVibe à l'aide d'une liste déroulante. Par exemple, vous voulez pouvoir choisir un algorithme parmis plusieurs, directement dans la configuration de votre boite sur openvibe, il vous suffit de créer ce nouveau type ainsi que les valeurs associées via le manager comme dans l'illustration suivante.
Pour pouvoir les utiliser avec le manager lors de la définition des paramètres d'une box, il faut lancer le manager avec l'option `mode=developer`.
> ![manage_custom_settings.png](Assets/Doc/manage_custom_settings.png)
>
> Add/Remove/Manage Custom Settings.
Résultat dans OpenVibe :
> ![classifiers.png](Assets/Doc/classifiers.png)
>
> Exemple avec un nouveau type 'classifier'.
## 3. Fonctionnement interne et détails
### Notre modèle de boite : PolyBox
Pour faciliter nos phases de développement, nous avons ajouté dans `pybox-manager/share/PolyBox.py` une classe appelée `PolyBox`. Cette dernière hérite de la classe OVBox. Elle automatise la réception et le stockage d'un signal en entrée et permet le développement de méthodes simples appelées à des moments clés :
- **on_initialize(self) :** appelée à l'initialisation, cette méthode a pour but de permettre à l'utilisateur de définir un comportement particulier lors de l'initialisation.
- **on_header_received(self, header) :** appelée à chaque réception d'un header, cette méthode a pour but de permettre à l'utilisateur de définir un comportement particulier lors de la réception d'un header.
- **on_chunk_received(self, chunk, label, shape) :** appelée à chaque réception d'un chunk, cette méthode a pour but de permettre à l'utilisateur de définir un comportement particulier lors de la réception d'un chunk.
- **on_end_box(self) :** appelée juste avant que la box finisse son travail, cette méthode a pour but de permettre à l'utilisateur de définir un comportement particulier lors de la fin de la box.
Nous avons créée cette box afin de pouvoir lui attribuer des comportements adaptés à nos cas d'utilisations, en particulier pour facilement créer des boites proposant des algorithmes de Machine Learning en se basant sur son architecture.
### Gestion des inputs avec PolyBox, deux modes possibles
Toute box héritant de la PolyBox possède automatiquement deux modes de lectures possibles pour récupérer des données en entrée.
Le premier mode (`ov-mode`) correspond au mode de lecture classique de OpenViBE : les différentes classes sont toutes comprises dans un même fichier .csv, et on utilise des stimulations reçues en entré pour séparer nos données en différentes classes. Pour utiliser ce mode, il suffit de n'accorder à la box que deux inputs : 1 StreamedMatrix et 1 Stimulation.
Exemple :
> ![ov-mode.jpeg](Assets/Doc/ov-mode.jpeg)
>
> ov-mode
Le second mode (`poly-mode`) consiste à considérer autant de fichiers qu'il y a de classes, soit un fichier .csv par classe. Pour lire tous ces fichiers, la box a alors besoin d'avoir au moins autant d'input StreamedMatrix qu'il existe de classes différentes. Pour utiliser ce mode, il suffit donc de ne créer que des entrées StreamedMatrix.
(Souvent, ce mode nécessite un paramètre `label` dans lequel renseigner les labels de nos différentes classes sous la forme : `mon label1, mon label2, mon label3`.)
Exemple :
> ![poly-mode.jpeg](Assets/Doc/poly-mode.jpeg)
>
> poly-mode
Ainsi, ces deux modes de lectures sont transparents pour l'utilisateur et lui permettent d'exploiter les box `PolyBox` soit avec un seul fichier contenant l'ensemble des actions, soit avec plusieurs fichiers : un par action.
L'utilisateur n'a donc rien besoin d'indiquer pour que la box choisisse le bon comportement à adopter, celle-ci choisit son comportement en fonction de ses inputs.
### Duplication de la Python Scripting Box
L'ensemble des modifications que nous avons apportées au logiciel repose sur la duplication de la box Python Scripting. Cette dernière permettant l'utilisation d'un script python, nous avons décidé de créer un manager permettant d'effectuer automatiquement des modifications dans le code d'OV pour dupliquer les fichiers C++ nécessaires à la duplication de la Python Scripting Box. De cette manière, nous pouvons associer définitivement un script à une box, et laisser l'utilisateur la configurer et l'intégrer à OpenViBE.
Lors de chaque création de box python, le manager effectue les tâches suivantes :
1. On se place à dans le dossier racine des box python : `pybox-manager/src/`.
2. On duplique `pybox-manager/Assets/BoxManager/NewBoxPattern-skeletton.h` en `box-algorithms/ovpBoxName.h`
3. On insère dans `src/defines.hpp` la déclaration des CIdentifier.
4. On ajoute dans `src/main.cpp` les imports des fichiers récemment créés pour la création de la box ainsi que les déclarations.
5. On remplace dans `src/box-algorithms/ovpBoxName.h` le nom de la boite et celui des includes/déclarations.
6. On change le nom, la description, les auteurs et la catégorie de la boite dans `src/box-algorithms/ovpBoxName.h`.
7. On définit le path du script à éxécuter dans `src/box-algorithms/ovpBoxName.h`.
8. On efface la possibilité de modifier le path.
9. On ajoute les paramètres de notre boite dans `src/box-algorithms/ovpBoxName.h`
10. On ajoute dans `src/box-algorithms/ovpBoxName.h` les inputs et outputs de la boite.
### PolyBox : Stockage automatique des données
Automatiquement, les box héritant de PolyBox stockent tous les chunk reçus dans `self.data`. Cela peut s'avérer pratique si l'utilisateur souhaite utiliser l'ensemble des données à la fin de la boite (pour entraîner un modèle de machine learning par exemple).
Il est cependant possible d'empêcher ce comportement. Pour ce faire, il suffit de donner comme paramètre lors de la création de la PolyBox `record=False`. Par défaut, record est à True.
### Gestion des Custom Settings
On ajoute à `meta/sdk/toolkit/include/toolkit/ovtk_defines.hpp` la déclaration de nos nouveaux Custom Settings et à `meta/sdk/toolkit/src/ovtk_main.cpp` la création du custom setting ainsi que la déclaration de ses différentes valeurs.
@@ -0,0 +1,263 @@
<OpenViBE-Scenario>
<FormatVersion>2</FormatVersion>
<Creator>OpenViBE Designer</Creator>
<CreatorVersion>2.2.0</CreatorVersion>
<Settings></Settings>
<Inputs></Inputs>
<Outputs></Outputs>
<Boxes>
<Box>
<Identifier>(0x00001a44, 0x00001322)</Identifier>
<Name>CSV File Reader</Name>
<AlgorithmClassIdentifier>(0x336a3d9a, 0x753f1ba4)</AlgorithmClassIdentifier>
<Outputs>
<Output>
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
<Name>Output stream</Name>
</Output>
<Output>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>Output stimulation</Name>
</Output>
</Outputs>
<Settings>
<Setting>
<TypeIdentifier>(0x330306dd, 0x74a95f98)</TypeIdentifier>
<Name>Filename</Name>
<DefaultValue></DefaultValue>
<Value>${Player_ScenarioDirectory}/datas/data-tutorial.csv</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>32</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>496</Value>
</Attribute>
<Attribute>
<Identifier>(0x30a4e5c9, 0x83502953)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0xa9cdc629, 0xb153eb33)</Value>
</Attribute>
<Attribute>
<Identifier>(0xc80ce8af, 0xf699f813)</Identifier>
<Value>2</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>1</Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x00001ddb, 0x0000547b)</Identifier>
<Name>DataViz</Name>
<AlgorithmClassIdentifier>(0x057b49ad, 0x040868cf)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x544a003e, 0x6dcba5f6)</TypeIdentifier>
<Name>input_StreamMatrix</Name>
</Input>
<Input>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>input_Stimulations</Name>
</Input>
</Inputs>
<Outputs>
<Output>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>stim_out</Name>
</Output>
</Outputs>
<Settings>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Clock frequency (Hz)</Name>
<DefaultValue>64</DefaultValue>
<Value>64</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x330306dd, 0x74a95f98)</TypeIdentifier>
<Name>Path to save the model</Name>
<DefaultValue></DefaultValue>
<Value></Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x330306dd, 0x74a95f98)</TypeIdentifier>
<Name>Path to load the model</Name>
<DefaultValue></DefaultValue>
<Value></Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x79a9edeb, 0x245d83fc)</TypeIdentifier>
<Name>Algorithm (PCA or LDA)</Name>
<DefaultValue>PCA</DefaultValue>
<Value>PCA</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Dimension reduction</Name>
<DefaultValue>2</DefaultValue>
<Value>2</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x79a9edeb, 0x245d83fc)</TypeIdentifier>
<Name>Labels</Name>
<DefaultValue></DefaultValue>
<Value></Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>96</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>496</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x434a6f9c, 0xa4ed45b3)</Value>
</Attribute>
<Attribute>
<Identifier>(0x527ad68d, 0x16d746a0)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x61d11811, 0x71e65362)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0xc80ce8af, 0xf699f813)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>6</Value>
</Attribute>
<Attribute>
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
<Value>2</Value>
</Attribute>
<Attribute>
<Identifier>(0xf191c1c8, 0xa0123976)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0xfba64161, 0x65304e21)</Identifier>
<Value></Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x00002a38, 0x00000aed)</Identifier>
<Name>Player Controller</Name>
<AlgorithmClassIdentifier>(0x5f426dce, 0x08456e13)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>Stimulations</Name>
</Input>
</Inputs>
<Settings>
<Setting>
<TypeIdentifier>(0x2c132d6e, 0x44ab0d97)</TypeIdentifier>
<Name>Stimulation name</Name>
<DefaultValue>OVTK_StimulationId_Label_00</DefaultValue>
<Value>OVTK_StimulationId_ExperimentStop</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0xcc14d8d6, 0xf27ecb73)</TypeIdentifier>
<Name>Action to perform</Name>
<DefaultValue>Pause</DefaultValue>
<Value>Stop</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>160</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>496</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x568d148e, 0x650792b3)</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>2</Value>
</Attribute>
<Attribute>
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
<Value>1</Value>
</Attribute>
</Attributes>
</Box>
</Boxes>
<Links>
<Link>
<Identifier>(0x00006692, 0x000071be)</Identifier>
<Source>
<BoxIdentifier>(0x00001ddb, 0x0000547b)</BoxIdentifier>
<BoxOutputIndex>0</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x00002a38, 0x00000aed)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
<Link>
<Identifier>(0x00007e63, 0x00007a47)</Identifier>
<Source>
<BoxIdentifier>(0x00001a44, 0x00001322)</BoxIdentifier>
<BoxOutputIndex>0</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x00001ddb, 0x0000547b)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
</Links>
<Comments>
<Comment>
<Identifier>(0x00007426, 0x0000717e)</Identifier>
<Text>You must wait the end of reading file to have display</Text>
<Attributes>
<Attribute>
<Identifier>(0x473d9a43, 0x97fc0a97)</Identifier>
<Value>720</Value>
</Attribute>
<Attribute>
<Identifier>(0x7234b86b, 0x2b8651a5)</Identifier>
<Value>32</Value>
</Attribute>
</Attributes>
</Comment>
</Comments>
<Metadata>
<Entry>
<Identifier>(0x000070c2, 0x000055b1)</Identifier>
<Type>(0x3bcce5d2, 0x43f2d968)</Type>
<Data>[{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":1,"height":1,"identifier":"(0x00005a2c, 0x00002f6b)","name":"Default window","parentIdentifier":"(0xffffffff, 0xffffffff)","type":1,"width":1},{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":1,"identifier":"(0x000042d2, 0x00004a25)","index":0,"name":"Default tab","parentIdentifier":"(0x00005a2c, 0x00002f6b)","type":2},{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":0,"identifier":"(0x00003ebf, 0x000013aa)","index":0,"name":"Empty","parentIdentifier":"(0x000042d2, 0x00004a25)","type":0}]</Data>
</Entry>
</Metadata>
</OpenViBE-Scenario>
@@ -0,0 +1,271 @@
<OpenViBE-Scenario>
<FormatVersion>2</FormatVersion>
<Creator>OpenViBE Designer</Creator>
<CreatorVersion>2.2.0</CreatorVersion>
<Settings></Settings>
<Inputs></Inputs>
<Outputs></Outputs>
<Boxes>
<Box>
<Identifier>(0x00002553, 0x00001145)</Identifier>
<Name>DatasetCreator</Name>
<AlgorithmClassIdentifier>(0x7b6738c4, 0x2c5156ed)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x544a003e, 0x6dcba5f6)</TypeIdentifier>
<Name>input_StreamMatrix</Name>
</Input>
</Inputs>
<Outputs>
<Output>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>stim_out</Name>
</Output>
</Outputs>
<Settings>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Clock frequency (Hz)</Name>
<DefaultValue>64</DefaultValue>
<Value>64</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x330306dd, 0x74a95f98)</TypeIdentifier>
<Name>Path directory</Name>
<DefaultValue>${Player_ScenarioDirectory}/datas/</DefaultValue>
<Value>${Player_ScenarioDirectory}/datas/</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x79a9edeb, 0x245d83fc)</TypeIdentifier>
<Name>Label_1</Name>
<DefaultValue>right</DefaultValue>
<Value>right</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x79a9edeb, 0x245d83fc)</TypeIdentifier>
<Name>Label_2</Name>
<DefaultValue>left</DefaultValue>
<Value>left</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x79a9edeb, 0x245d83fc)</TypeIdentifier>
<Name>Label_3</Name>
<DefaultValue>up</DefaultValue>
<Value>up</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x79a9edeb, 0x245d83fc)</TypeIdentifier>
<Name>Label_4</Name>
<DefaultValue>down</DefaultValue>
<Value>down</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
<Name>Several CSV</Name>
<DefaultValue>false</DefaultValue>
<Value>false</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Number of folds</Name>
<DefaultValue>1</DefaultValue>
<Value>1</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Number of actions</Name>
<DefaultValue>4</DefaultValue>
<Value>4</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>176</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>496</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0xd79a853e, 0x3396ae60)</Value>
</Attribute>
<Attribute>
<Identifier>(0x527ad68d, 0x16d746a0)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x61d11811, 0x71e65362)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0xc80ce8af, 0xf699f813)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>9</Value>
</Attribute>
<Attribute>
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0xf191c1c8, 0xa0123976)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0xfba64161, 0x65304e21)</Identifier>
<Value></Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x00002a38, 0x00000aed)</Identifier>
<Name>Player Controller</Name>
<AlgorithmClassIdentifier>(0x5f426dce, 0x08456e13)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>Stimulations</Name>
</Input>
</Inputs>
<Settings>
<Setting>
<TypeIdentifier>(0x2c132d6e, 0x44ab0d97)</TypeIdentifier>
<Name>Stimulation name</Name>
<DefaultValue>OVTK_StimulationId_Label_00</DefaultValue>
<Value>OVTK_StimulationId_ExperimentStop</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0xcc14d8d6, 0xf27ecb73)</TypeIdentifier>
<Name>Action to perform</Name>
<DefaultValue>Pause</DefaultValue>
<Value>Stop</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>240</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>496</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x568d148e, 0x650792b3)</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>2</Value>
</Attribute>
<Attribute>
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
<Value>1</Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x00003615, 0x000063b8)</Identifier>
<Name>Sinus oscillator</Name>
<AlgorithmClassIdentifier>(0x7e33bdb8, 0x68194a4a)</AlgorithmClassIdentifier>
<Outputs>
<Output>
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
<Name>Generated signal</Name>
</Output>
</Outputs>
<Settings>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Channel count</Name>
<DefaultValue>4</DefaultValue>
<Value>4</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Sampling frequency</Name>
<DefaultValue>512</DefaultValue>
<Value>512</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Generated epoch sample count</Name>
<DefaultValue>32</DefaultValue>
<Value>32</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>112</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>496</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x0b214ed8, 0x1f9ad83a)</Value>
</Attribute>
<Attribute>
<Identifier>(0xc80ce8af, 0xf699f813)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>3</Value>
</Attribute>
</Attributes>
</Box>
</Boxes>
<Links>
<Link>
<Identifier>(0x00004d38, 0x00007bd2)</Identifier>
<Source>
<BoxIdentifier>(0x00002553, 0x00001145)</BoxIdentifier>
<BoxOutputIndex>0</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x00002a38, 0x00000aed)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
<Link>
<Identifier>(0x00007380, 0x00007709)</Identifier>
<Source>
<BoxIdentifier>(0x00003615, 0x000063b8)</BoxIdentifier>
<BoxOutputIndex>0</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x00002553, 0x00001145)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
</Links>
<Comments></Comments>
<Metadata>
<Entry>
<Identifier>(0x0000716e, 0x00000d37)</Identifier>
<Type>(0x3bcce5d2, 0x43f2d968)</Type>
<Data>[{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":1,"height":320,"identifier":"(0x0000227a, 0x00001a2b)","name":"Default window","parentIdentifier":"(0xffffffff, 0xffffffff)","type":1,"width":480},{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":1,"identifier":"(0x00005d03, 0x00003bf1)","index":0,"name":"Default tab","parentIdentifier":"(0x0000227a, 0x00001a2b)","type":2},{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":0,"identifier":"(0x0000792d, 0x0000021b)","index":0,"name":"Empty","parentIdentifier":"(0x00005d03, 0x00003bf1)","type":0}]</Data>
</Entry>
</Metadata>
</OpenViBE-Scenario>
@@ -0,0 +1,223 @@
<OpenViBE-Scenario>
<FormatVersion>2</FormatVersion>
<Creator>OpenViBE Designer</Creator>
<CreatorVersion>2.2.0</CreatorVersion>
<Settings></Settings>
<Inputs></Inputs>
<Outputs></Outputs>
<Boxes>
<Box>
<Identifier>(0x00001a44, 0x00001322)</Identifier>
<Name>CSV File Reader</Name>
<AlgorithmClassIdentifier>(0x336a3d9a, 0x753f1ba4)</AlgorithmClassIdentifier>
<Outputs>
<Output>
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
<Name>Output stream</Name>
</Output>
<Output>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>Output stimulation</Name>
</Output>
</Outputs>
<Settings>
<Setting>
<TypeIdentifier>(0x330306dd, 0x74a95f98)</TypeIdentifier>
<Name>Filename</Name>
<DefaultValue></DefaultValue>
<Value>${Player_ScenarioDirectory}/datas/data-tutorial.csv</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>64</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>480</Value>
</Attribute>
<Attribute>
<Identifier>(0x30a4e5c9, 0x83502953)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0xa9cdc629, 0xb153eb33)</Value>
</Attribute>
<Attribute>
<Identifier>(0xc80ce8af, 0xf699f813)</Identifier>
<Value>2</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>1</Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x00002a38, 0x00000aed)</Identifier>
<Name>Player Controller</Name>
<AlgorithmClassIdentifier>(0x5f426dce, 0x08456e13)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>Stimulations</Name>
</Input>
</Inputs>
<Settings>
<Setting>
<TypeIdentifier>(0x2c132d6e, 0x44ab0d97)</TypeIdentifier>
<Name>Stimulation name</Name>
<DefaultValue>OVTK_StimulationId_Label_00</DefaultValue>
<Value>OVTK_StimulationId_ExperimentStop</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0xcc14d8d6, 0xf27ecb73)</TypeIdentifier>
<Name>Action to perform</Name>
<DefaultValue>Pause</DefaultValue>
<Value>Stop</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>208</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>480</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x568d148e, 0x650792b3)</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>2</Value>
</Attribute>
<Attribute>
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
<Value>1</Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x00004501, 0x000079b6)</Identifier>
<Name>Process Sklearn</Name>
<AlgorithmClassIdentifier>(0x681d3dfe, 0x060e2afb)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x544a003e, 0x6dcba5f6)</TypeIdentifier>
<Name>input_StreamMatrix</Name>
</Input>
</Inputs>
<Outputs>
<Output>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>stim_out</Name>
</Output>
</Outputs>
<Settings>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Clock frequency (Hz)</Name>
<DefaultValue>64</DefaultValue>
<Value>64</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x330306dd, 0x74a95f98)</TypeIdentifier>
<Name>Model filename</Name>
<DefaultValue>${Player_ScenarioDirectory}/model.clf</DefaultValue>
<Value>${Player_ScenarioDirectory}/model.clf</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x330306dd, 0x74a95f98)</TypeIdentifier>
<Name>Predictions filename</Name>
<DefaultValue></DefaultValue>
<Value></Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>144</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>480</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0xb942ac8a, 0x83cd7760)</Value>
</Attribute>
<Attribute>
<Identifier>(0x527ad68d, 0x16d746a0)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x61d11811, 0x71e65362)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0xc80ce8af, 0xf699f813)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>3</Value>
</Attribute>
<Attribute>
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0xf191c1c8, 0xa0123976)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0xfba64161, 0x65304e21)</Identifier>
<Value></Value>
</Attribute>
</Attributes>
</Box>
</Boxes>
<Links>
<Link>
<Identifier>(0x00002056, 0x00007dc0)</Identifier>
<Source>
<BoxIdentifier>(0x00004501, 0x000079b6)</BoxIdentifier>
<BoxOutputIndex>0</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x00002a38, 0x00000aed)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
<Link>
<Identifier>(0x00002361, 0x0000065a)</Identifier>
<Source>
<BoxIdentifier>(0x00001a44, 0x00001322)</BoxIdentifier>
<BoxOutputIndex>0</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x00004501, 0x000079b6)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
</Links>
<Comments></Comments>
<Metadata>
<Entry>
<Identifier>(0x00005f34, 0x0000794a)</Identifier>
<Type>(0x3bcce5d2, 0x43f2d968)</Type>
<Data>[{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":1,"height":320,"identifier":"(0x00005a92, 0x00001b40)","name":"Default window","parentIdentifier":"(0xffffffff, 0xffffffff)","type":1,"width":480},{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":1,"identifier":"(0x000023fa, 0x00007573)","index":0,"name":"Default tab","parentIdentifier":"(0x00005a92, 0x00001b40)","type":2},{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":0,"identifier":"(0x00001a40, 0x00000ba9)","index":0,"name":"Empty","parentIdentifier":"(0x000023fa, 0x00007573)","type":0}]</Data>
</Entry>
</Metadata>
</OpenViBE-Scenario>
@@ -0,0 +1,271 @@
<OpenViBE-Scenario>
<FormatVersion>2</FormatVersion>
<Creator>OpenViBE Designer</Creator>
<CreatorVersion>2.2.0</CreatorVersion>
<Settings></Settings>
<Inputs></Inputs>
<Outputs></Outputs>
<Boxes>
<Box>
<Identifier>(0x00000206, 0x00006ab1)</Identifier>
<Name>Trainer Sklearn</Name>
<AlgorithmClassIdentifier>(0x44b85a3a, 0xabfc390a)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x544a003e, 0x6dcba5f6)</TypeIdentifier>
<Name>input_StreamMatrix</Name>
</Input>
<Input>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>input_Stimulations</Name>
</Input>
</Inputs>
<Outputs>
<Output>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>stim_out</Name>
</Output>
</Outputs>
<Settings>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Clock frequency (Hz)</Name>
<DefaultValue>64</DefaultValue>
<Value>64</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x330306dd, 0x74a95f98)</TypeIdentifier>
<Name>Filename to save model to</Name>
<DefaultValue>${Player_ScenarioDirectory}/model.clf</DefaultValue>
<Value>${Player_ScenarioDirectory}/model.clf</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x330306dd, 0x74a95f98)</TypeIdentifier>
<Name>Filename to load model from</Name>
<DefaultValue></DefaultValue>
<Value></Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x73ae164d, 0xea21ab0a)</TypeIdentifier>
<Name>Classifier</Name>
<DefaultValue>None</DefaultValue>
<Value>AdaBoost</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>Test set share</Name>
<DefaultValue>0.2</DefaultValue>
<Value>0.2</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x79a9edeb, 0x245d83fc)</TypeIdentifier>
<Name>Labels</Name>
<DefaultValue></DefaultValue>
<Value></Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>n_estimators</Name>
<DefaultValue>50</DefaultValue>
<Value>50</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>learning_rate</Name>
<DefaultValue>1.0</DefaultValue>
<Value>1.0</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x27025481, 0x09bedc31)</TypeIdentifier>
<Name>algorithm</Name>
<DefaultValue>SAMME.R</DefaultValue>
<Value>SAMME.R</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x79a9edeb, 0x245d83fc)</TypeIdentifier>
<Name>random_state</Name>
<DefaultValue>None</DefaultValue>
<Value>None</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>128</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>480</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0xbe66982a, 0x597f92cf)</Value>
</Attribute>
<Attribute>
<Identifier>(0xc80ce8af, 0xf699f813)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>4</Value>
</Attribute>
<Attribute>
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
<Value>2</Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x00001a44, 0x00001322)</Identifier>
<Name>CSV File Reader</Name>
<AlgorithmClassIdentifier>(0x336a3d9a, 0x753f1ba4)</AlgorithmClassIdentifier>
<Outputs>
<Output>
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
<Name>Output stream</Name>
</Output>
<Output>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>Output stimulation</Name>
</Output>
</Outputs>
<Settings>
<Setting>
<TypeIdentifier>(0x330306dd, 0x74a95f98)</TypeIdentifier>
<Name>Filename</Name>
<DefaultValue></DefaultValue>
<Value>${Player_ScenarioDirectory}/datas/data-tutorial.csv</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>48</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>480</Value>
</Attribute>
<Attribute>
<Identifier>(0x30a4e5c9, 0x83502953)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0xa9cdc629, 0xb153eb33)</Value>
</Attribute>
<Attribute>
<Identifier>(0xc80ce8af, 0xf699f813)</Identifier>
<Value>2</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>1</Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x00002a38, 0x00000aed)</Identifier>
<Name>Player Controller</Name>
<AlgorithmClassIdentifier>(0x5f426dce, 0x08456e13)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>Stimulations</Name>
</Input>
</Inputs>
<Settings>
<Setting>
<TypeIdentifier>(0x2c132d6e, 0x44ab0d97)</TypeIdentifier>
<Name>Stimulation name</Name>
<DefaultValue>OVTK_StimulationId_Label_00</DefaultValue>
<Value>OVTK_StimulationId_ExperimentStop</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0xcc14d8d6, 0xf27ecb73)</TypeIdentifier>
<Name>Action to perform</Name>
<DefaultValue>Pause</DefaultValue>
<Value>Stop</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>192</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>480</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x568d148e, 0x650792b3)</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>2</Value>
</Attribute>
<Attribute>
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
<Value>1</Value>
</Attribute>
</Attributes>
</Box>
</Boxes>
<Links>
<Link>
<Identifier>(0x00003f8b, 0x00002044)</Identifier>
<Source>
<BoxIdentifier>(0x00001a44, 0x00001322)</BoxIdentifier>
<BoxOutputIndex>1</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x00000206, 0x00006ab1)</BoxIdentifier>
<BoxInputIndex>1</BoxInputIndex>
</Target>
</Link>
<Link>
<Identifier>(0x00005663, 0x000061f4)</Identifier>
<Source>
<BoxIdentifier>(0x00000206, 0x00006ab1)</BoxIdentifier>
<BoxOutputIndex>0</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x00002a38, 0x00000aed)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
<Link>
<Identifier>(0x00006e58, 0x000045c5)</Identifier>
<Source>
<BoxIdentifier>(0x00001a44, 0x00001322)</BoxIdentifier>
<BoxOutputIndex>0</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x00000206, 0x00006ab1)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
</Links>
<Comments></Comments>
<Metadata>
<Entry>
<Identifier>(0x00004b79, 0x00004f0d)</Identifier>
<Type>(0x3bcce5d2, 0x43f2d968)</Type>
<Data>[{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":1,"height":1,"identifier":"(0x000033dc, 0x0000473a)","name":"Default window","parentIdentifier":"(0xffffffff, 0xffffffff)","type":1,"width":1},{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":1,"identifier":"(0x00006ab8, 0x00002aa8)","index":0,"name":"Default tab","parentIdentifier":"(0x000033dc, 0x0000473a)","type":2},{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":0,"identifier":"(0x00006208, 0x00003307)","index":0,"name":"Empty","parentIdentifier":"(0x00006ab8, 0x00002aa8)","type":0}]</Data>
</Entry>
</Metadata>
</OpenViBE-Scenario>
@@ -0,0 +1,61 @@
/**
* \page BoxAlgorithmDataViz DataViz
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithmDataViz_Description|
This box aims to transform the data with a LDA or a PCA and plot the data in 2D or 3D.
* |OVP_DocEnd_BoxAlgorithmDataViz_Description|
__________________________________________________________________
Settings description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithmDataViz_Settings|
* |OVP_DocEnd_BoxAlgorithmDataViz_Settings|
* |OVP_DocBegin_BoxAlgorithmDataViz_Setting1|
The box clock frequency. The Python process function is called at
each tick.
* |OVP_DocEnd_BoxAlgorithmDataViz_Setting1|
* |OVP_DocBegin_BoxAlgorithmDataViz_Setting2|
If specified, path to save the model which transform the data.
* |OVP_DocEnd_BoxAlgorithmDataViz_Setting2|
* |OVP_DocBegin_BoxAlgorithmDataViz_Setting3|
If specified, path to load the model which transform the data.
If empty, a new model will be created depending on the input's data.
* |OVP_DocEnd_BoxAlgorithmDataViz_Setting3|
* |OVP_DocBegin_BoxAlgorithmDataViz_Setting4|
Algorithm to use to reduce the data's dimensions. Two options are available :
PCA or LDA. By default, the box will use PCA.
* |OVP_DocEnd_BoxAlgorithmDataViz_Setting4|
* |OVP_DocBegin_BoxAlgorithmDataViz_Setting5|
Number of dimensions to plot the data. The user can only choose between 2 or 3.
* |OVP_DocEnd_BoxAlgorithmDataViz_Setting5|
* |OVP_DocBegin_BoxAlgorithmDataViz_Setting6|
Labels of the data, if not specified then labels will be "1, 2, 3, etc".
* |OVP_DocEnd_BoxAlgorithmDataViz_Setting6|
__________________________________________________________________
Examples description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithmDataViz_Examples|
See <a href="http://openvibe.inria.fr/tutorial-using-python-with-openvibe">this page</a> for commented examples.
* |OVP_DocEnd_BoxAlgorithmDataViz_Examples|
__________________________________________________________________
Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithmDataViz_Miscellaneous|
* |OVP_DocEnd_BoxAlgorithmDataViz_Miscellaneous|
*/
@@ -0,0 +1,84 @@
/**
* \page BoxAlgorithmDatasetCreator DatasetCreator
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithmDatasetCreator_Description|
This box aims to monitor the user to create a dataset.
The user first configure the box and chooses some labels, then
the box will randomly determine an order between them. It will
then record the user's brain activity while verbally indicating
the current action. In this way, we will be able to create a
labeled dataset that can be used for learning.
* |OVP_DocEnd_BoxAlgorithmDatasetCreator_Description|
__________________________________________________________________
Settings description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithmDatasetCreator_Settings|
* |OVP_DocEnd_BoxAlgorithmDatasetCreator_Settings|
* |OVP_DocBegin_BoxAlgorithmDatasetCreator_Setting1|
The box clock frequency. The Python process function is called at
each tick.
* |OVP_DocEnd_BoxAlgorithmDatasetCreator_Setting1|
* |OVP_DocBegin_BoxAlgorithmDatasetCreator_Setting2|
The path to the dir where the user want to save the data.
* |OVP_DocEnd_BoxAlgorithmDatasetCreator_Setting2|
* |OVP_DocBegin_BoxAlgorithmDatasetCreator_Setting3|
Name of the first label. That label has to be configured with the
manager to associate a sound.
* |OVP_DocEnd_BoxAlgorithmDatasetCreator_Setting3|
* |OVP_DocBegin_BoxAlgorithmDatasetCreator_Setting4|
Name of the second label. That label has to be configured with the
manager to associate a sound.
* |OVP_DocEnd_BoxAlgorithmDatasetCreator_Setting4|
* |OVP_DocBegin_BoxAlgorithmDatasetCreator_Setting5|
Name of the third label. That label has to be configured with the
manager to associate a sound.
* |OVP_DocEnd_BoxAlgorithmDatasetCreator_Setting5|
* |OVP_DocBegin_BoxAlgorithmDatasetCreator_Setting6|
Name of the fourth label. That label has to be configured with the
manager to associate a sound.
* |OVP_DocEnd_BoxAlgorithmDatasetCreator_Setting6|
* |OVP_DocBegin_BoxAlgorithmDatasetCreator_Setting7|
True if you want one csv per label, false if you want one csv
containing all the labels.
* |OVP_DocEnd_BoxAlgorithmDatasetCreator_Setting7|
* |OVP_DocBegin_BoxAlgorithmDatasetCreator_Setting3|
Number of folds you want to create.
* |OVP_DocEnd_BoxAlgorithmDatasetCreator_Setting3|
* |OVP_DocBegin_BoxAlgorithmDatasetCreator_Setting3|
Number of action to record. For exemple, if you want two record for
4 labels, then number of actions if 8.
* |OVP_DocEnd_BoxAlgorithmDatasetCreator_Setting3|
__________________________________________________________________
Examples description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithmDatasetCreator_Examples|
See <a href="http://openvibe.inria.fr/tutorial-using-python-with-openvibe">this page</a> for commented examples.
* |OVP_DocEnd_BoxAlgorithmDatasetCreator_Examples|
__________________________________________________________________
Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithmDatasetCreator_Miscellaneous|
* |OVP_DocEnd_BoxAlgorithmDatasetCreator_Miscellaneous|
*/
@@ -0,0 +1,49 @@
/**
* \page BoxAlgorithmProcessML Process Sklearn
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithmProcessML_Description|
This box aims to load a machine learning model from scikit-learn
or pyriemann already trained and to use it to predict its input's data.
* |OVP_DocEnd_BoxAlgorithmProcessML_Description|
__________________________________________________________________
Settings description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithmProcessML_Settings|
* |OVP_DocEnd_BoxAlgorithmProcessML_Settings|
* |OVP_DocBegin_BoxAlgorithmProcessML_Setting1|
The box clock frequency. The Python process function is called at
each tick.
* |OVP_DocEnd_BoxAlgorithmProcessML_Setting1|
* |OVP_DocBegin_BoxAlgorithmProcessML_Setting2|
The path to the model the user want to load.
* |OVP_DocEnd_BoxAlgorithmProcessML_Setting2|
* |OVP_DocBegin_BoxAlgorithmProcessML_Setting3|
If not empty, the path where the user want to save the predictions
results.
* |OVP_DocEnd_BoxAlgorithmProcessML_Setting3|
__________________________________________________________________
Examples description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithmProcessML_Examples|
See <a href="http://openvibe.inria.fr/tutorial-using-python-with-openvibe">this page</a> for commented examples.
* |OVP_DocEnd_BoxAlgorithmProcessML_Examples|
__________________________________________________________________
Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithmProcessML_Miscellaneous|
* |OVP_DocEnd_BoxAlgorithmProcessML_Miscellaneous|
*/
@@ -0,0 +1,85 @@
/**
* \page BoxAlgorithmTrainerML Trainer Sklearn
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithmTrainerML_Description|
This box aims to create a machine learning model based on algorithm implemented in scikit-learn.
The user can modify nearly all the settings from scikit-learn.
It is possible for the user to test his model with a test set.
He can also save the model in a file which he can use with the ProcessML box.
An AdaBoost classifier is a meta-estimator that begins by fitting
a classifier on the original dataset and then fits additional
copies of the classifier on the same dataset but where the weights
of incorrectly classified instances are adjusted such that
subsequent classifiers focus more on difficult cases.
* |OVP_DocEnd_BoxAlgorithmTrainerML_Description|
__________________________________________________________________
Settings description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithmTrainerML_Settings|
* |OVP_DocEnd_BoxAlgorithmTrainerML_Settings|
* |OVP_DocBegin_BoxAlgorithmTrainerML_Setting1|
The box clock frequency. The Python process function is called at
each tick.
* |OVP_DocEnd_BoxAlgorithmTrainerML_Setting1|
* |OVP_DocBegin_BoxAlgorithmTrainerML_Setting2|
The path to the file where the user want to save the model.
If empty, the model is not saved.
* |OVP_DocEnd_BoxAlgorithmTrainerML_Setting2|
* |OVP_DocBegin_BoxAlgorithmTrainerML_Setting3|
The path to the file where the user want to load a model.
If empty, a new model will be created.
* |OVP_DocEnd_BoxAlgorithmTrainerML_Setting3|
* |OVP_DocBegin_BoxAlgorithmTrainerML_Setting4|
The name of the scikit-learn algorithm. The user should not try
to modify this setting.
* |OVP_DocEnd_BoxAlgorithmTrainerML_Setting4|
* |OVP_DocBegin_BoxAlgorithmTrainerML_Setting5|
Float between 0 and 1 (1 not included) which represents the proportion
of the input dataset transformed into a test set, allowing us to
evaluate our model once the training on the set train is done. If 0
is filled in, then all the data will constitute the train set and no
metrics will be displayed.
* |OVP_DocEnd_BoxAlgorithmTrainerML_Setting5|
* |OVP_DocBegin_BoxAlgorithmTrainerML_Setting6|
Names of the labels of input's data. This parameter is only usefull
when the PolyMode is used (several streamed matrix in input, one for
each label). Then the labels as to be described as follows :
"my_label_1, mylabel_2, my_label_3, etc"
Otherwise the labels will be "1, 2, 3, etc".
If the input mode is ov-mode (one input streamed matrix and one
stimulation), the labels will be read by the stimulations.
* |OVP_DocEnd_BoxAlgorithmTrainerML_Setting6|
The rest of the settings are from scikit-learn. You can find
their purpose here :
https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.AdaBoostClassifier.html
__________________________________________________________________
Examples description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithmTrainerML_Examples|
See <a href="http://openvibe.inria.fr/tutorial-using-python-with-openvibe">this page</a> for commented examples.
* |OVP_DocEnd_BoxAlgorithmTrainerML_Examples|
__________________________________________________________________
Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithmTrainerML_Miscellaneous|
* |OVP_DocEnd_BoxAlgorithmTrainerML_Miscellaneous|
*/
@@ -0,0 +1,725 @@
# -*- coding: utf-8 -*-
import re
import os
import sys
import shutil
import platform
import subprocess
import time
import inspect
import ast
import copy
from pathlib import Path
from collections import namedtuple
from shutil import copyfile
from filecmp import cmp as compare_file
from PyQt5.QtWidgets import QMessageBox
system = platform.system()
if system != 'Linux' and system != 'Windows':
raise Exception("OS {} is not handled with that script.".format(system))
def find_folders() :
"""Find openvibe and the manager folders."""
current_file = inspect.getfile(lambda : None)
current_file = Path(current_file)
manager_folder = str(current_file.parent) + '/'
openvibe_folder = str(current_file.parent.parent.parent.parent.parent.parent) + '/'
return manager_folder, openvibe_folder
def find_all_stims():
"""Retrieve all stimulation from Poly_Stimulation"""
prefixe_stim = 'OVPoly_'
stims = [k for k in Poly_stimulation.keys() if prefixe_stim in k]
stims = [k[len(prefixe_stim):] for k in stims]
return stims
def find_all_custom_settings(manager_folder) :
""" Generate a list containing all custom settings and their values"""
def get_custom_settings(path_header, prefixe) :
with open(path_header, 'r') as f :
text = f.read()
matchs = re.findall(r"{}[a-zA-Z0-9_]+".format(prefixe), text)
return matchs
def get_custom_settings_confirmed(path_cpp, custom_settings) :
with open(path_cpp, 'r') as f :
text = f.read()
custom_settings_confirmed = []
for ct in custom_settings :
match = re.search(r"rPluginModuleContext\.getTypeManager\(\)\.registerEnumerationType\({}, \"[a-zA-Z0-9_ \.]+\"\);".format(ct), text)
if match is not None :
custom_settings_confirmed += [ct]
return custom_settings_confirmed
def get_custom_values(path_cpp, custom_settings, prefixe) :
custom_values = {}
with open(path_cpp, 'r') as f :
text = f.read()
for ct in custom_settings :
matchs = re.findall(r'rPluginModuleContext\.getTypeManager\(\)\.registerEnumerationEntry\({}, "[0-9a-zA-Z_ \.]+", [0-9]+\);'.format(ct), text)
values = []
for match in matchs :
m_text = match.split('"')[1]
m_id = int(match.split(',')[-1][1:-2])
values += [Value(text=m_text, id=m_id)]
cs_name = ct[len(prefixe):]
custom_values[cs_name] = Custom_Setting(ct, values)
return custom_values
prefixe = 'OVPoly_ClassId_'
path_header = "{}/src/defines.hpp".format(manager_folder)
path_cpp = "{}/src/main.cpp".format(manager_folder)
custom_settings = get_custom_settings(path_header, prefixe)
custom_settings = get_custom_settings_confirmed(path_cpp, custom_settings)
custom_values = get_custom_values(path_cpp, custom_settings, prefixe)
return custom_values
def find_all_boxes(manager_folder, io_dic_type, settings_dic_type):
"""Generate a list containing all the existing box inside"""
path_all_boxes = "{}/src/box-algorithms/".format(manager_folder)
list_all_files = os.listdir(path_all_boxes)
directories = {}
for elem in list_all_files:
if elem[:3] == 'ovp' :
with open(path_all_boxes + elem, 'r') as f:
file_h = f.read()
# Path Script
script = re.findall(
r"m_script = \"(.*.py)\";", file_h)[0]
# Description
desc = re.findall(r"virtual CString getShortDescription\(void\) const "
r"{ return CString\(\"(.*?)\"\); }", file_h, flags=re.DOTALL)[0]
desc = desc.replace("\"", "\\\"")
author = re.search(r'virtual CString getAuthorName\(void\) const { return CString\(\"[a-zA-Z 0-9\-&]*\"\); }', file_h).group()
author = author[89:-5]
category = re.search(r'virtual CString getCategory\(void\) const { return CString\(\"[/a-zA-Z ]+\"\); }', file_h).group()
category = category[89:-5]
category = category[16:]
# Creation of the object
# workaround to allow creation of box with spaces
boxname = elem[3:-2]
name = boxname.replace('_',' ')
box = BoxPython(name=name, filename= boxname, desc=desc, path_script=script)
box.author = author
box.category = category
# Settings
setts = re.findall(r"(?<!//)prototype.addSetting\(\"(.*)\", "
r"((OV_TypeId|OVPoly)_[a-z_A-Z]*), \"(.*)\"\);", file_h)
# r"(OV_TypeId_[a-z_A-Z]*), \"(.*)\"\);", file_h)
# Same value as the one in counters in the display part
settings_compt = 2
for sett in setts:
if sett[0] != 'Clock frequency (Hz)':
keys = list(settings_dic_type.keys())
values = list(settings_dic_type.values())
box.settings[settings_compt] = [
sett[0], keys[values.index(sett[1])], sett[3]]
settings_compt += 1
# inputs
inputs = re.findall(
r"(?<!\/\/)prototype\.addInput\(\"(.*)\", (OV_TypeId_[a-zA-Z]*)\);", file_h)
# Same value as the one in counters in the display part
inputs_compt = 2
keys = list(io_dic_type.keys())
values = list(io_dic_type.values())
for inp in inputs:
box.inputs[inputs_compt] = [
inp[0], keys[values.index(inp[1])]]
inputs_compt += 1
# outputs
outputs = re.findall(
r"(?<!\/\/)prototype\.addOutput\(\"(.*)\", (OV_TypeId_[a-zA-Z]*)\);", file_h)
# Same value as the one in counters in the display part
outputs_compt = 2
for out in outputs:
box.outputs[outputs_compt] = [
out[0], keys[values.index(out[1])]]
outputs_compt += 1
# modify permission
permission_input = re.search(r"(\/\/)?prototype\.addFlag\(Kernel::BoxFlag_CanModifyInput\);", file_h)
permission_output = re.search(r"(\/\/)?prototype\.addFlag\(Kernel::BoxFlag_CanModifyOutput\);", file_h)
permission_setting = re.search(r"(\/\/)?prototype\.addFlag\(Kernel::BoxFlag_CanModifySetting\);", file_h)
if permission_input.group()[:2] == '//' :
box.modify_inputs = False
if permission_output.group()[:2] == '//' :
box.modify_outputs = False
if permission_setting.group()[:2] == '//' :
box.modify_settings = False
directories[box.name] = box
return directories
def warning_msg(msg) :
"""Show a warning message : msg in the manager to the user."""
box = QMessageBox()
box.setIcon(QMessageBox.Warning)
box.setText(msg)
box.setWindowTitle('Warning')
box.exec_()
def info_msg(msg) :
"""Show an information msg in the manager to the user."""
box = QMessageBox()
box.setIcon(QMessageBox.Information)
box.setText(msg)
box.setWindowTitle('Information')
box.exec_()
def insert_line_in_file(filename, string, tag):
"""Insert the line string in the file filename just after the first
line containing tag."""
with open(filename, 'r+') as f:
text = f.read()
i = text.index(tag) + len(tag)
f.seek(0)
f.write(text[:i] + '\n' + string + text[i:])
def replace_in_file(filename, old, new):
"""Replace in filename old by new."""
with open(filename, 'r') as f:
text = f.read()
text = text.replace(old, new)
with open(filename, 'w') as f:
f.write(text)
def remove_line_from_file(filename, tag):
"""Delete the first line in the file with the tag inside."""
flag = False
with open(filename, 'r') as f:
text = f.read()
tab = text.split('\n')
for i, line in enumerate(tab):
if tag in line:
flag = True
break
if flag:
tab = tab[:i] + tab[i + 1:]
text = '\n'.join(tab)
if flag:
with open(filename, 'w') as f:
f.write(text)
def generate_new_id(openvibe_folder):
"""Generate a set of 4 different random ids for openvibe"""
def get_folder_sdk_releae(openvibe_folder) :
path = '{}/build/'.format(openvibe_folder)
dirs = os.listdir(path)
path_to_return = path + [f for f in dirs if 'sdk-Release' in f][0]
return path_to_return
def get_path_ov_id_generator(openvibe_folder) :
path_sdk = get_folder_sdk_releae(openvibe_folder)
path_to_return = '{}/applications/developer-tools/id-generator/openvibe-id-generator'.format(path_sdk)
if system == 'Windows' :
path_to_return += '.exe'
return path_to_return
path_generator = get_path_ov_id_generator(openvibe_folder)
filename_tmp = 'tmp_result'
os.system('{} > {}'.format(path_generator, filename_tmp))
with open(filename_tmp, 'r') as f:
text = f.read()
text = text.split('\n')
line = text[0]
tab = line.split(',')
val1 = tab[0][-10:]
val2 = tab[1][1:11]
line = text[1]
tab = line.split(',')
val3 = tab[0][-10:]
val4 = tab[1][1:11]
if system == 'Linux' :
os.system('rm {}'.format(filename_tmp))
elif system == 'Windows' :
os.system('del {}'.format(filename_tmp))
return val1, val2, val3, val4
def create_box(openvibe_folder, manager_folder, setting_type, io_type, box_name, desc, path_script, category, author, settings, inputs, outputs, modify_settings, modify_inputs, modify_outputs):
"""Create and modify the files to create/modify a new box in openvibe."""
global system
# preventing the use of spaces which would cause problems for c++ filenames
box_name = box_name.replace(' ','_')
# force category to be in scripting
category = 'Scripting/PyBox/' + category
# 1/ We place ourselves at the root of the python boxe
old_location = os.getcwd()
os.chdir("{}/src/".format(manager_folder))
# 2/ We create the corresponding directory
path_dir_box = 'box-algorithms/'
full_path_dir_box = os.getcwd() + '/' + path_dir_box
# We duplicate files from the original box
path_file_header = path_dir_box + 'ovp{}.h'.format(box_name)
path_pattern_header = '{}Assets/BoxManager/NewBoxPattern-skeletton.h'.format(
manager_folder)
copyfile(path_pattern_header, path_file_header)
# 3/ We insert in defines.hpp the declaration of CIdentifiers
filename = 'defines.hpp'
tag = '// <tag> Tag Box Declaration'
time.sleep(1)
new_id1, new_id2, new_id3, new_id4 = generate_new_id(openvibe_folder)
new_line = "#define OVP_ClassId_BoxAlgorithm_{} OpenViBE::CIdentifier({}, {})"\
.format(box_name, new_id1, new_id2)
insert_line_in_file(filename, new_line, tag)
new_line = "#define OVP_ClassId_BoxAlgorithm_{}Desc OpenViBE::CIdentifier({}, {})"\
.format(box_name, new_id3, new_id4)
insert_line_in_file(filename, new_line, tag)
# 4/ We add our lines in main.cpp
filename = 'main.cpp'
tag = '#include "box-algorithms/CPolyBox.hpp"'
new_line = '#include "{}"'.format(path_file_header)
insert_line_in_file(filename, new_line, tag)
tag = '// <tag> OVP_Declare_New'
new_line = '\t\tOVP_Declare_New(Python::CBoxAlgorithm{}Desc);'.format(
box_name)
insert_line_in_file(filename, new_line, tag)
# 5/ We replace in ovpmyBox.h
replace_in_file(path_file_header, 'CBoxAlgorithmNewBoxPattern',
'CBoxAlgorithm{}'.format(box_name))
replace_in_file(path_file_header, 'OVP_ClassId_BoxAlgorithm_NewBoxPattern',
'OVP_ClassId_BoxAlgorithm_{}'.format(box_name))
replace_in_file(path_file_header, 'OVP_ClassId_BoxAlgorithm_NewBoxPatternDesc',
'OVP_ClassId_BoxAlgorithm_{}Desc'.format(box_name))
# 6/ We change the name, the description, the author name and the category
desc = desc.replace("\"", "\\\"")
# we reset the spaces for the name of the box
box_name = box_name.replace('_',' ')
replace_in_file(path_file_header,
'CString getName() const override { return CString("NewBoxPattern"); }',
'CString getName() const override { return CString("' + box_name + '"); }')
replace_in_file(path_file_header,
'CString getShortDescription() const override { return CString("Default Python Description"); }',
'CString getShortDescription() const override { return CString("' + desc + '"); }')
replace_in_file(path_file_header,
'CString getAuthorName() const override { return CString("NewAuthor"); }',
'CString getAuthorName() const override { return CString("' + author + '"); }')
replace_in_file(path_file_header,
'CString getCategory() const override { return CString("Scripting/Pybox/"); }',
'CString getCategory() const override { return CString("' + category + '"); }')
# 7/ We set the python script to execute
# On set le script python a executer
replace_in_file(path_file_header,
'm_script = "NewScript.py";',
'm_script = "{}";'.format(path_script.replace('\\', '/')))
# 8/ We can then add our params
tag = '// <tag> settings'
for number, (key, kind, value) in reversed(list(settings.items())):
if key:
new_line = ' prototype.addSetting("{}", {}, "{}");'.format(
key, all_settings_type[kind], value)
insert_line_in_file(path_file_header, new_line, tag)
# 9/ We can then add our inputs and our outputs
tag_inoutset = '// <tag> input & output'
for number, (name, kind) in reversed(list(inputs.items())):
if name:
new_line = ' prototype.addInput("{}", {});'.format(
name, io_type[kind])
insert_line_in_file(path_file_header, new_line, tag_inoutset)
for number, (name, kind) in reversed(list(outputs.items())):
new_line = ' prototype.addOutput("{}", {});'.format(
name, io_type[kind])
insert_line_in_file(path_file_header, new_line, tag_inoutset)
# 10/ Permissions to modify boxes in OV
if not modify_settings :
replace_in_file(path_file_header,
"prototype.addFlag(Kernel::BoxFlag_CanModifySetting);",
"//prototype.addFlag(Kernel::BoxFlag_CanModifySetting);")
if not modify_inputs :
replace_in_file(path_file_header,
"prototype.addFlag(Kernel::BoxFlag_CanModifyInput);",
"//prototype.addFlag(Kernel::BoxFlag_CanModifyInput);")
if not modify_outputs :
replace_in_file(path_file_header,
"prototype.addFlag(Kernel::BoxFlag_CanModifyOutput);",
"//prototype.addFlag(Kernel::BoxFlag_CanModifyOutput);")
os.chdir(old_location)
def delete_box(manager_folder, box_name):
"""Delete and modify files to delete an existing box."""
global system
box_name = box_name.replace(' ', '_')
# 1/ Change directory to src/
old_location = os.getcwd()
os.chdir("{}src/".format(manager_folder))
# 2/ Remove directory with the box algorithms
path_box = 'box-algorithms/ovp{}.h'.format(box_name)
os.remove(path_box)
# 3/ Remove lines from defines.hpp
path = 'defines.hpp'
tag = 'OVP_ClassId_BoxAlgorithm_{}Desc'.format(box_name)
remove_line_from_file(path, tag)
tag = 'OVP_ClassId_BoxAlgorithm_{}'.format(box_name)
remove_line_from_file(path, tag)
# 4/ Remove lines from main.cpp
path = 'main.cpp'
tag = 'OVP_Declare_New(Python::CBoxAlgorithm{}Desc);'.format(box_name)
remove_line_from_file(path, tag)
tag = '#include "box-algorithms/ovp{}.h"'.format(box_name)
remove_line_from_file(path, tag)
os.chdir(old_location)
# ------------------------------------------------------------
def create_custom_setting(manager_folder, openvibe_folder, cs) :
""" Make all modification to openvibe to create a custom setting."""
prefixe = 'OVPoly_ClassId_'
path_cpp = '{}/src/main.cpp'.format(manager_folder)
path_header = '{}/src/defines.hpp'.format(manager_folder)
tag = '// <tag> Custom Type Settings'
cs_define = "{}{}".format(prefixe, cs.name)
# header
id_1, id_2, _, _ = generate_new_id(openvibe_folder)
line_header = '#define {} OpenViBE::CIdentifier({}, {})'.format(cs_define, id_1, id_2)
insert_line_in_file(path_header, line_header, tag)
# cpp
for value in reversed(cs.values) :
line_entry = '\trKernelContext.getTypeManager().registerEnumerationEntry({}, "{}", {});'.format(cs_define, value.text, value.id)
insert_line_in_file(path_cpp, line_entry, tag)
line_type = '\trKernelContext.getTypeManager().registerEnumerationType({}, "{}");'.format(cs_define, cs.name)
insert_line_in_file(path_cpp, line_type, tag)
def delete_custom_setting(manager_folder, cs) :
"""Make all the modification to openvibe to delete a custom setting."""
def remove_lines_with_tag_in_file(file, tag) :
with open(file, 'r') as f :
text = f.read()
new_text = []
for line in text.split('\n') :
if not (tag in line) :
new_text += [line]
new_text = '\n'.join(new_text)
with open(file, 'w') as f :
f.write(new_text)
prefixe = 'OVPoly_ClassId_'
path_cpp = '{}/src/main.cpp'.format(manager_folder)
path_header = '{}/src/defines.hpp'.format(manager_folder)
tag = '{}{}'.format(prefixe, cs.name)
remove_lines_with_tag_in_file(path_header, tag)
remove_lines_with_tag_in_file(path_cpp, tag)
def compile(manager_folder, openvibe_folder):
"""Compile OpenVibe."""
# Go to openvibe folder directory
old_location = os.getcwd()
os.chdir(openvibe_folder)
path_log = manager_folder + 'compilation.log'
if system == 'Linux':
os.system('./build.sh | tee {}'.format(path_log))
elif system == 'Windows':
os.system('build.cmd > {}'.format(path_log))
# Set back to normal
os.chdir(old_location)
def add_stimulation(manager_folder, label, file_sound) :
"""Make all the modifications to add a stimulation to openvibe."""
def find_next_id(path_file_stim) :
# We open the PolyStimulations.py to find the next id
with open(path_file_stim, 'r') as f :
text = f.read()
# On charge de dictionnaire de stimulation
dico_string = ''
flag = False
for c in text :
if c == '{' :
flag = True
elif c == '}' :
dico_string += c
break
if flag :
dico_string += c
dico = ast.literal_eval(dico_string)
ids = [int(value) for key, value in Poly_stimulation.items() if 'OVPoly' in key]
ids.sort()
try :
current_id = ids[0]
except IndexError :
return 0x10001
while True :
if not current_id in ids :
return hex(current_id)
current_id += 1
def get_line_to_add(label, path_file_stim) :
key = '\'OVPoly_' + label[0].upper() + label[1:].lower() + '\''+ ' '*(29-len(label))
value = find_next_id(path_file_stim)
line = '{}: {},'.format(key, value)
return line
path_file_stim = '{}/share/PolyStimulations.py'.format(manager_folder)
tag = '# <Flag> New Stims'
line = get_line_to_add(label, path_file_stim)
insert_line_in_file(path_file_stim, line, tag)
copyfile(file_sound, '{}/Assets/Sounds/{}.mp3'.format(manager_folder, label))
def get_name_duplicate(dict, name) :
"""Find the name for a duplicated box."""
def regex_handling(regex, name):
res = re.search(regex, name)
try:
prefix_1 = res.group(1)
idx_1 = res.group(2) # useless ?
max_idx = 1
# Does we already have a duplicate ?
# we do so we find the max index and increment it
for bn in dict.keys() :
regex = '(' + prefix_1 + ')(\d+)'
res_2 = re.search(regex,bn)
try:
idx = int(res_2.group(2))
if idx >= max_idx:
max_idx = idx + 1
except:
pass
current_name = prefix_1 + str(max_idx)
except AttributeError:
# current_name = 'rr'
for bn in dict.keys() :
if bn == name :
current_name = '{}_{}'.format(name, 1)
finally:
return current_name
current_name = name
regex = '(.+?_)(\d+)'
res = re.search(regex, current_name) # useless ?
# We already have doublon
for bn in dict.keys():
# if we have the "original" name
if current_name in bn:
current_name = regex_handling(regex, bn)
break
else:
current_name = regex_handling(regex, current_name)
return current_name
def delete_stimulation(manager_folder, label) :
"""Make the modification to remove a stimulation from openvibe."""
path_file_stim = '{}/share/PolyStimulations.py'.format(manager_folder)
remove_line_from_file(path_file_stim, label)
path_sound = '{}/Assets/Sounds/{}.mp3'.format(manager_folder, label.lower())
os.remove(path_sound)
def retrieve_settings_type(custom_settings, all_settings=False) :
"""If 'mode=developer' in args : load all default and custom settings,
else load only default settings"""
settings = {
'Integer': 'OV_TypeId_Integer',
'Float': 'OV_TypeId_Float',
'String': 'OV_TypeId_String',
'Boolean': 'OV_TypeId_Boolean',
'Filename': 'OV_TypeId_Filename',
'Stimulation': 'OV_TypeId_Stimulation',
}
flag = False
for arg in sys.argv[1:] :
if 'mode=developer' in arg :
flag = True
if flag or all_settings :
for k,v in custom_settings.items() :
settings[k] = v.name
return settings
Setting = namedtuple('Setting', ['name', 'type', 'value', 'button_delete'])
Input = namedtuple('Input', ['name', 'type', 'button_delete'])
Output = namedtuple('Output', ['name', 'type', 'button_delete'])
Custom_Setting_Line = namedtuple('CSV', ['name', 'value', 'button_delete'])
Custom_Setting = namedtuple("Custom_Setting", ['name', 'values'])
Value = namedtuple("Value", ["text", "id"])
class BoxPython:
"""This class aim to contain all the data needed to create a box in openvibe."""
def __init__(self, name='Default Box', filename='Default filename', desc='Default Python Description', path_script='/home/', old_name=None,):
self.name = name
self.filename = filename
self.description = desc
self.py_script = path_script
self.category = ''
self.author = ''
self.settings = {}
self.inputs = {}
self.outputs = {}
self.modify_settings = True
self.modify_inputs = True
self.modify_outputs = True
self.to_be_updated = False
if old_name is None :
self.old_name = name
else :
self.old_name = old_name
def __eq__(self, obj):
equals = False
if isinstance(obj, BoxPython):
equals = self.name == obj.name and \
self.description == obj.description and \
self.py_script == obj.py_script and \
cmp(self.settings, obj.settings) == 0 and \
cmp(self.inputs, obj.inputs) == 0 and \
cmp(self.outputs, obj.outputs) == 0
return equals
def __ne__(self, obj):
return not self == obj
# Load all the data necessary to make the manager work.
manager_folder, openvibe_folder = find_folders()
io_type = {
'Signal': 'OV_TypeId_Signal',
'Stimulations': 'OV_TypeId_Stimulations',
'Streamed Matrix': 'OV_TypeId_StreamedMatrix'
}
modes = ['ov-mode', 'poly-mode']
category = ['Acquisition and network IO',
'Advanced Visualization',
'Classification',
'Data generation',
'Evaluation',
'Examples',
'Feature extraction',
'File reading and writing',
'Signal processing',
'Stimulation',
'Streaming',
'Tests',
'Tools',
'Visualization'
]
sys.path.append('{}/share/'.format(manager_folder))
from PolyStimulations import Poly_stimulation
custom_settings = find_all_custom_settings(manager_folder)
all_settings_type = retrieve_settings_type(custom_settings, all_settings=True)
settings_type = retrieve_settings_type(custom_settings)
boxes = find_all_boxes(manager_folder, io_type, all_settings_type)
stims = find_all_stims()
@@ -0,0 +1,203 @@
# -*- coding: utf-8 -*-
# File Name : PolyBox.py
# Created By : Yannis Bendi-Ouis
from StimulationsCodes import *
from PolyStimulations import *
from openvibe import *
import sys, traceback, collections
from io import StringIO
import re
def get_label_from_stim(stim):
stims_1 = list(OpenViBE_stimulation.items())
stims_2 = list(Poly_stimulation.items())
inv_dictstim = {v: k for k, v in stims_1 + stims_2}
label = inv_dictstim[stim.identifier][7:].lower()
label = ' '.join(label.split('_'))
return label
class PolyBox(OVBox):
def __init__(self, record=True):
OVBox.__init__(self)
self.acquiring_channel = []
self.signalHeader = []
self.data = {}
self.record = record
self.mode = ''
self.current_stimulation = None
self.labels = []
def initialize(self):
def verify_entry(self):
# Verify that the inputs corresponds to ov-mode or poly-mode
# ov-mode : 1 stimulations and 1 streamed matrix
# poly-mode : several streamed-matrix
# others : others -> warning
list_entry_type = [entry.type() for entry in self.input]
nb_matrix = list_entry_type.count('StreamedMatrix')
nb_stim = list_entry_type.count('Stimulations')
nb_signal = list_entry_type.count('Signal')
if nb_stim == 1 and nb_matrix == 1 and nb_signal == 0:
# ov-mode
self.mode = 'ov-mode'
elif nb_stim == 0 and nb_matrix >= 1 and nb_signal == 0:
# poly-mode
self.mode = 'poly-mode'
else:
raise Exception("ERROR : Entry of the box does not corresponds to any mode. \
You can use 1 stimulations and 1 streamed matrix, or several streamed matrix. \
But you can not use {} StreamedMatrix, {} Stimulations and {} Signal entry.".format(nb_matrix, nb_stim, nb_signal))
def verify_stim_output(self):
# Verify that an output stim exist, otherwise prevent the user that the program won't stop
flag = False
for out in self.output:
if out.type() == 'Stimulations':
flag = True
break
if not flag:
print('WARNING : The DatasetCreator does not have any output Stimulation. The program may never stop.')
def get_labels(self):
# retrieve labels in form : label1, label2, label3, mon label4
# Useless if you are in OV-MODE
if 'Labels' in self.setting.keys():
string = self.setting['Labels']
if len(string) > 0:
labels_cut = string.lower().split(',')
for label in labels_cut:
self.labels += ['_'.join([w for w in label.split(' ') if w != ''])]
def init_acquiring_channel(self):
# We get data for every input channel
for _ in range(len(self.input)):
self.acquiring_channel += [False]
self.signalHeader += [None]
verify_entry(self)
verify_stim_output(self)
get_labels(self)
init_acquiring_channel(self)
self.on_initialize()
def process(self):
# we go through every input
for inputIndex in range(len(self.input)):
for chunkIndex in range(len(self.input[inputIndex])):
# Signal init
if type(self.input[inputIndex][chunkIndex]) == OVStreamedMatrixHeader:
self.header_received(inputIndex, chunkIndex)
# Process every chunk received
elif type(self.input[inputIndex][chunkIndex]) == OVStreamedMatrixBuffer:
self.chunk_received(inputIndex, chunkIndex)
# End of signal
elif type(self.input[inputIndex][chunkIndex]) == OVStreamedMatrixEnd:
self.end_received(inputIndex, chunkIndex)
# Stimulations init
elif type(self.input[inputIndex][chunkIndex]) == OVStimulationHeader:
self.header_received(inputIndex, chunkIndex)
# Process every stimulation
elif type(self.input[inputIndex][chunkIndex]) == OVStimulationSet:
self.stimulation_received(inputIndex, chunkIndex)
# End of stim
elif type(self.input[inputIndex][chunkIndex]) == OVStimulationEnd:
self.end_received(inputIndex, chunkIndex)
def uninitialize(self):
pass
# ------- * -------- * ---------
def header_received(self, inputIndex, chunkIndex):
header = self.input[inputIndex].pop()
self.signalHeader[inputIndex] = header
self.acquiring_channel[inputIndex] = True
self.on_header_received(header)
def chunk_received(self, inputIndex, chunkIndex):
chunk = list(self.input[inputIndex].pop())
if self.acquiring_channel[inputIndex]:
# We look for the best key to use in function of mode and settings labels.
key = None
if self.mode == 'poly-mode':
if len(self.labels) > 0:
key = self.labels[inputIndex]
else:
key = inputIndex
elif self.mode == 'ov-mode':
key = get_label_from_stim(self.current_stimulation)
if self.record:
try:
self.data[key].append(chunk)
except KeyError:
self.data[key] = [chunk]
shape = tuple(self.signalHeader[inputIndex].dimensionSizes)
self.on_chunk_received(chunk, key, shape)
def stimulation_received(self, inputIndex, chunkIndex):
stim_list = self.input[inputIndex].pop()
if len(stim_list) > 0:
self.current_stimulation = stim_list[0]
def end_received(self, inputIndex, chunkIndex):
self.acquiring_channel[inputIndex] = False
self.input[inputIndex].pop()
if not self.is_acquiring():
print("Fin de l'acquisition des données...")
self.on_end_box()
self.send_end_stim()
def is_acquiring(self):
# Return false when all inputs of type StreamedMatrix received End flag.
nb_inputs = len(self.input)
for i in range(nb_inputs):
acquiring = self.acquiring_channel[i]
kind = self.input[i].type()
if kind == 'StreamedMatrix' and acquiring:
return True
return False
def send_end_stim(self):
indice = -1
for i, out in enumerate(self.output):
if out.type() == 'Stimulations':
indice = i
if indice != -1:
stimLabel = 'OVTK_StimulationId_ExperimentStop'
stimCode = OpenViBE_stimulation[stimLabel]
stimSet = OVStimulationSet(0, self.getCurrentTime())
stimSet.append(OVStimulation(stimCode, self.getCurrentTime(), 0.))
self.output[indice].append(stimSet)
# ---------- * -------------- * ---------------
def on_initialize(self):
pass
def on_header_received(self, header):
pass
def on_chunk_received(self, chunk, label, shape):
pass
def on_end_box(self):
pass
@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
#File Name : StimulationsCodes.py
#Created By :
## Stimulation codes
# Avoid the declaration of new stimulation (added in new tab here). Only Stimulation contains gdf in original list is really standard...
Poly_stimulation = {
'OVPoly_Down' : 0x10001, #Useless use 'OVTK_GDF_Down'
'OVPoly_Up' : 0x10002, #Useless use 'OVTK_GDF_Up'
'OVPoly_Right' : 0x10003, #Useless use 'OVTK_GDF_Right'
'OVPoly_Left' : 0x10004, #Useless use 'OVTK_GDF_Left'
'OVPoly_Neutral' : 0x10005,
'OVPoly_Push' : 0x10006,
'OVPoly_Pull' : 0x10007,
'OVPoly_Left_Wink' : 0x10008,
'OVPoly_Right_Wink' : 0x10009,
# <Flag> New Stims
}
@@ -0,0 +1,173 @@
# -*- coding: utf-8 -*-
from matplotlib import pyplot as plt
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
from sklearn.decomposition import PCA
import numpy as np
import pandas as pd
import pickle
from PolyBox import PolyBox
import warnings
warnings.filterwarnings("ignore")
flag_3D = True
try:
from mpl_toolkits.mplot3d import Axes3D
except:
print('Unable to import Axes3D from mpl_toolkits.mplot3d, 3D visualization disabled.')
flag_3D = False
class DataViz(PolyBox):
def __init__(self):
PolyBox.__init__(self)
self.x_data = []
self.y_data = []
self.model = None
self.path_load_model = ''
self.path_save_model = ''
self.algo = ''
self.dimension_reduction = -1
def on_initialize(self):
# We retrieve the setting from OpenViBE
def retrieve_path_save_model(self):
try:
self.path_save_model = self.setting["Path to save the model"]
except KeyError:
pass
if self.path_save_model == '':
print('No path has been given to save the model, thus it won\'t be saved.')
def retrieve_path_load_model(self):
try:
self.path_load_model = self.setting["Path to load the model"]
except KeyError:
pass
if self.path_load_model == '':
print('No path has been given to load the model, thus a new model will be created.')
def retrieve_algo(self):
try:
self.algo = self.setting['Algorithm (PCA or LDA)'].upper()
except KeyError:
pass
if self.algo == '':
print('No algo has been given to the model, default algo is PCA.')
self.algo = 'PCA'
def retrieve_dimension_reduction(self):
try:
self.dimension_reduction = int(self.setting['Dimension reduction'])
except KeyError:
pass
except ValueError:
print('{} is not a number. Please use 2 or 3 dimensions only.'.format(self.setting['Dimension reduction']))
if self.dimension_reduction == -1:
print('No dimension reduction has been given, default value is 2.')
self.dimension_reduction = 2
elif self.dimension_reduction == 3 and not flag_3D:
print('3D disabled, cannot show data in 3 dimensions. Default dimension is 2.')
self.dimension_reduction = 3
retrieve_path_save_model(self)
retrieve_path_load_model(self)
retrieve_algo(self)
retrieve_dimension_reduction(self)
def on_end_box(self):
self.prepare_data()
self.make_model_and_transform()
self.make_plot()
# ---------
def prepare_data(self):
for label in self.data.keys():
self.x_data += self.data[label]
self.y_data += [label for _ in range(len(self.data[label]))]
def make_model_and_transform(self):
# load the model if it exists, else create a new one
# then transform the data
def load_model(self):
model = pickle.load(open(self.path_load_model, 'rb'))
print('Model load from {}.'.format(self.path_load_model))
return model
def save_model(self):
pickle.dump(self.model, open(self.path_save_model, 'wb'))
print('Dataviz model saved in {}'.format(self.path_save_model))
def map_algo(self):
switcher = { 'LDA': LDA, 'PCA': PCA }
clf = switcher.get(self.algo)
return clf, switcher
def create_fit_model(self):
clf, _ = map_algo(self)
clf = clf(n_components=self.dimension_reduction)
if self.algo == 'PCA':
clf.fit(self.x_data)
elif self.algo == 'LDA':
clf.fit(self.x_data, self.y_data)
else:
raise Exception('{} is not known as an Algorithm. Please use PCA or LDA.'.format(self.algo))
return clf
# Load or create the model
if len(self.path_load_model) > 0:
self.model = load_model(self)
else:
self.model = create_fit_model(self)
# Save the model
if self.path_save_model != '':
save_model(self)
# Transform data
self.x_data = self.model.transform(self.x_data)
self.y_data = np.array(self.y_data)
def make_plot(self):
fig = plt.figure(figsize=(12, 12))
all_labels = list(self.data.keys())
colors = np.array([all_labels.index(label) for label in self.y_data])
if self.dimension_reduction == 2:
ax = plt.axes()
for label in all_labels:
ax.text(self.x_data[self.y_data == label, 0].mean(), self.x_data[self.y_data == label, 1].mean(),
label, horizontalalignment='center', bbox=dict(alpha=0.5, edgecolor='w', facecolor='w'))
ax.scatter(self.x_data[:, 0], self.x_data[:, 1], alpha=0.5, c=colors, cmap='Spectral', edgecolor='g')
plt.show()
elif self.dimension_reduction == 3:
ax = Axes3D(fig)
for label in all_labels:
ax.text3D(self.x_data[self.y_data == label, 0].mean(),
self.x_data[self.y_data == label, 1].mean(),
self.x_data[self.y_data == label, 2].mean(),
label, horizontalalignment='center', bbox=dict(alpha=0.5, edgecolor='w', facecolor='w'))
ax.scatter(self.x_data[:, 0], self.x_data[:, 1], self.x_data[:, 2],
alpha=0.5, c=colors, cmap='Spectral', edgecolor='g')
plt.show()
box = DataViz()
@@ -0,0 +1,421 @@
# -*- coding: utf-8 -*-
'''
 * Software License Agreement (AGPL-3 License)
 *
 * OpenViBE
 * Copyright (C) Inria, 2006-2019
 *
 * Authors
 *
* 2019, Yannis Bendi-Ouis <yannis.bendiouis@gmail.com>
* 2019, Jimmy Leblanc <jimmy.leblanc01@gmail.com>
*
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.
 * If not, see <http://www.gnu.org/licenses/>.
 *
'''
from pandas import Series, DataFrame, read_csv
import numpy as np
import pandas as pd
from natsort import natsorted
from PolyStimulations import Poly_stimulation
import os
import pickle
import random
import inspect
# Dans les settings :
# - "Path directory" : path qui mène au directory de sauvegarde des données. Donc finir par un '/'.
# - "Label_X" : Nom des labels, où X est un nombre. Il en faut autant qu'il y a de labels.
# - "Several CSV" : Boolean ou string qui return "true" ou "false".
# - "Number of folds" : Integer qui indique en combien de différents folds les data doivent être séparées.
# - "Number of actions" : Integer qui indique le nombre d'actions à enregistrer lors d'une session.
#------------------------------------------------------------
ACTION_DURATION = 12 # in seconds
TAMPON_DURATION = 3 # in seconds
BEGIN_RECORD = 2 # in seconds
END_RECORD = ACTION_DURATION
FREQ = 128
NB_POINTS_ONE_RECORD = ACTION_DURATION * FREQ
NB_POINTS_ONE_TAMPON = TAMPON_DURATION * FREQ
CHANNELS_NAME = ['AF3', 'F7', 'F3', 'FC5', 'T7', 'P7', 'O1', 'O2', 'P8', 'T8', 'FC6', 'F4', 'F8', 'AF4'] # 14
CHANNELS_STIMULATION = ['Event Id', 'Event Date', 'Event Duration']
PREFIXE_STIM = 'OVPoly_'
#------------------------------------------------------------
def ovdf(df, rewrite_stim=True):
shape = df.shape
timer = Series([float(i)/FREQ for i in range(shape[0])])
epochs = Series([int(i/64) for i in range(shape[0])])
df.insert(0, 'Time:{}Hz'.format(FREQ), timer)
df.insert(1, 'Epoch', epochs)
if rewrite_stim:
df['Event Id'] = Series([None for i in range(shape[0])])
df['Event Date'] = Series([None for i in range(shape[0])])
df['Event Duration'] = Series([None for i in range(shape[0])])
else:
# re-compute the time informations for stimulations
stim = df['Event Id'].values.tolist()
date = []
duration = []
for i, event_id in enumerate(stim):
if not pd.isnull(event_id):
date += [timer[i]]
duration += [0]
else:
date += [None]
duration += [None]
df['Event Date'] = Series(date)
df['Event Duration'] = Series(duration)
#------------------------------------------------------------
def get_stim_code_from_label(label):
key = label.split('_')
key = "_".join([w[0].upper() + w[1:] for w in key])
key = PREFIXE_STIM + key
return Poly_stimulation[key]
#------------------------------------------------------------
class DatasetCreator(OVBox):
#----------------------------------------
def __init__(self):
OVBox.__init__(self)
self.dir_name = None
self.url = None
self.signalHeader = None
self.labels_queue = None
self.current_label = None
self.is_tampon = False
self.several_csv = False
self.nb_fold = 0
self.nb_action_per_session = 0
self.labels = []
self.data_recorded = []
self.labels_order = []
self.dic_fold = {}
self.dic_dicount = {}
#----------------------------------------
def initialize(self):
def verify_labels_correct(self):
for label in self.labels:
try:
get_stim_code_from_label(label)
except KeyError:
raise Exception('Label {} not defined in PolyStimulations. You may want to add it with the manager.'.format(label))
def get_labels(self):
param_names = self.setting.keys()
param_names = natsorted(param_names)
for n in param_names:
if 'Label_' in n:
label = self.setting[n]
label = label.replace(' ', '_')
if len(label) > 0:
self.labels += [label.lower()]
def init_dict(self):
self.dic_fold = {'fold_{}'.format(i): None for i in range(1, self.nb_fold+1)}
self.dic_dicount = {'fold_{}'.format(i): None for i in range(1, self.nb_fold+1)}
def retrieve_settings(self):
# On récupère la booleen indiquant si l'on souhaite plusieurs ou un seul csv par fold
self.several_csv = self.setting['Several CSV']
if self.several_csv == 'true':
self.several_csv = True
elif self.several_csv == 'false':
self.several_csv = False
# On récupère le path du directory où l'on créé les fold
self.dir_name = self.setting['Path directory']
if self.dir_name[-1] != '/':
self.dir_name += '/'
# On récupère le nombre de fold
self.nb_fold = int(self.setting['Number of folds'])
# On récupère le nombre d'action à record par session
self.nb_action_per_session = int(self.setting['Number of actions'])
# On récupère les labels
get_labels(self)
def verify_stim_output(self):
# Verify that an output stim exist, otherwise prevent the user that the program won't stop
flag = False
for out in self.output:
if out.type() == 'Stimulations':
flag = True
break
if not flag:
print('WARNING : The DatasetCreator does not have any output Stimulation. The program may never stop.')
retrieve_settings(self)
verify_stim_output(self)
verify_labels_correct(self)
init_dict(self)
self.verify_arborescence()
self.prepare_session()
self.new_record()
#----------------------------------------
def process(self):
# On parcours tous les inputs
for inputIndex in range(len(self.input)):
for chunkIndex in range(len(self.input[inputIndex])):
# Initialisation pour le signal
if type(self.input[inputIndex][chunkIndex]) == OVStreamedMatrixHeader:
self.header_received(inputIndex, chunkIndex)
# Traitement à effectuer pour chaque chunk reçu
elif type(self.input[inputIndex][chunkIndex]) == OVStreamedMatrixBuffer:
self.chunk_received(inputIndex, chunkIndex)
# Fin du signal
elif type(self.input[inputIndex][chunkIndex]) == OVStreamedMatrixEnd:
self.end_received(inputIndex, chunkIndex)
#----------------------------------------
def uninitialize(self):
pass
# -------------- * ------------- * -------------
#----------------------------------------
def header_received(self, inputIndex, chunkIndex):
self.signalHeader = self.input[inputIndex].pop()
#----------------------------------------
def chunk_received(self, inputIndex, chunkIndex):
chunk = self.input[inputIndex].pop()
# Plusieurs lignes sont envoyées en même temps, il faut les séparer
indices = [(len(CHANNELS_NAME)*i, len(CHANNELS_NAME)*(i+1)) for i in range(int(len(chunk)/len(CHANNELS_NAME)))]
for begin, end in indices:
self.data_recorded += [chunk[begin:end]]
# Fin d'un tampon
if self.is_tampon and len(self.data_recorded) >= NB_POINTS_ONE_TAMPON:
self.empty_tampon()
self.new_record()
# Fin d'un record
if not self.is_tampon and len(self.data_recorded) >= NB_POINTS_ONE_RECORD:
if not self.end_record():
self.end_of_session()
self.end_of_box()
#----------------------------------------
def end_received(self, inputIndex, chunkIndex):
self.input[inputIndex].pop()
print("Error : entry signal stoped.")
self.end_of_box()
#----------------------------------------
def end_of_box(self):
indice = -1
for i, out in enumerate(self.output):
if out.type() == 'Stimulations':
indice = i
if indice != -1:
stimLabel = 'OVTK_StimulationId_ExperimentStop'
stimCode = OpenViBE_stimulation[stimLabel]
stimSet = OVStimulationSet(0, self.getCurrentTime())
stimSet.append(OVStimulation(stimCode, self.getCurrentTime(), 0.))
self.output[0].append(stimSet)
# -------------- * ------------- * -------------
#----------------------------------------
def verify_arborescence(self):
# On vérifie que chaque dossier du path existe, sinon on les créé
# et on vérifie les dicount
def verify_dir(self):
# On vérifie que le dossier existe et ses sous-dossiers, sinon on les créé
if not os.path.exists(self.dir_name):
os.mkdir(self.dir_name, 0o775)
def verify_folds(self):
# On vérifie que les dossiers des différents folds existent, sinon on les créés
for i in range(1, self.nb_fold+1):
name = self.dir_name + 'fold_{}/'.format(i)
if not os.path.exists(name):
os.mkdir(name, 0o775)
def verify_and_load_dicount(self):
# On vérifie que les fichiers contenant les compteurs par label existent, sinon on les créé
for i in range(1, self.nb_fold+1):
fold = 'fold_{}'.format(i)
filename = self.dir_name + fold + '/dicount.pick'
if not os.path.isfile(filename):
dicount = {l: 0 for l in self.labels}
self.dic_dicount[fold] = dicount
pickle.dump(dicount, open(filename, 'wb'))
else:
self.dic_dicount[fold] = pickle.load(open(filename, 'rb'))
verify_dir(self)
verify_folds(self)
verify_and_load_dicount(self)
#----------------------------------------
def prepare_session(self):
def create_labels_queue(self):
nb_label = len(self.labels)
nb_total = int(self.nb_action_per_session / nb_label) # in Python 3 / create float automatically and range hate that
nb_reste = self.nb_action_per_session - nb_total*nb_label
choice_label = []
tmp = [l for l in self.labels]
for _ in range(nb_reste):
l = random.choice(tmp)
tmp.remove(l)
choice_label += [l]
tmp = [l for l in self.labels]
self.labels_queue = tmp*nb_total + choice_label
random.shuffle(self.labels_queue)
# Prepare la suite aléatoire d'action a exectuer
create_labels_queue(self)
# Initialise self.dic_fold
for key in self.dic_fold.keys():
self.dic_fold[key] = {l: [] for l in self.labels}
#----------------------------------------
def empty_tampon(self):
# Supprime les données tampons entre deux records
self.is_tampon = False
end = TAMPON_DURATION * FREQ
self.data_recorded = self.data_recorded[end:]
#----------------------------------------
def new_record(self):
# Prevent the user a new record begin
if len(self.labels_queue) > 0:
self.current_label = self.labels_queue.pop(0)
print('Current label : {}'.format(self.current_label))
#----------------------------------------
def end_record(self):
# Démarre un nouvel enregistrement de 10 sec. Si le dernier est fini, l'enregistre.
# Return True s'il y a encore d'autres label à étudié pour la session, False sinon.
def retrieve_record(self):
def add_to_fold(self, data, label):
# Ajoute les éléments dans la liste data au dic_fold label et mets à jour dic_dicount
print('hello', self.dic_dicount)
tmp = [(key, value[label]) for key, value in self.dic_dicount.items()]
fold = min(tmp, key=lambda x: x[1])[0]
self.dic_fold[fold][label] += [data]
self.dic_dicount[fold][label] += len(data)
self.labels_order += [label]
# Extract the 10s recorded
record = self.data_recorded[:NB_POINTS_ONE_RECORD]
self.data_recorded = self.data_recorded[NB_POINTS_ONE_RECORD:]
# Extract data between begin and end
begin = FREQ*BEGIN_RECORD
end = FREQ*END_RECORD
data = record[begin:end]
add_to_fold(self, data, self.current_label)
# End record
print('Stop.')
retrieve_record(self)
self.is_tampon = True
return len(self.labels_queue) > 0
#----------------------------------------
def end_of_session(self):
def maj_dicount(self, fold):
filename = self.dir_name + fold + '/dicount.pick'
dicount = self.dic_dicount[fold]
pickle.dump(dicount, open(filename, 'wb'))
def add_label_stimulation(self, data, label):
# Add the stimulations indicating the begginning of a label
length = len(data)
event_id = [None for _ in range(length)]
event_date = [None for _ in range(length)]
event_duration = [None for _ in range(length)]
event_id[0] = get_stim_code_from_label(label)
dict_event = {"Event Id": event_id, "Event Date": event_date, "Event Duration": event_duration}
dict_data = {c: np.array(data)[:, i].tolist() for i, c in enumerate(CHANNELS_NAME)}
dict_data.update(dict_event)
return dict_data
def append_data_to_csv(self, data, fold, label, several_csv=False):
# Create all csv, either you can us one CSV with stimulations, either one CSV per label
if several_csv:
filename = self.dir_name + fold + '/' + label + '.csv'
columns = CHANNELS_NAME
else:
filename = self.dir_name + fold + '/' + fold + '.csv'
columns = CHANNELS_NAME + CHANNELS_STIMULATION
data = add_label_stimulation(self, data, label)
old_df = DataFrame()
if os.path.isfile(filename):
old_df = read_csv(filename).filter(columns)
add_df = DataFrame(data, columns=columns)
new_df = old_df.append(add_df, ignore_index=True)
ovdf(new_df, rewrite_stim=several_csv)
new_df.to_csv(filename, index=False)
labels_count = {label: 0 for label in self.labels}
self.labels_order = {'fold_{}'.format(n): self.labels_order[self.nb_action_per_session*i: self.nb_action_per_session*(i+1)]
for i in range(self.nb_fold) for n in range(1, self.nb_fold+1)}
for i in range(1, self.nb_fold+1):
fold = 'fold_{}'.format(i)
for label in self.labels_order[fold]:
count = labels_count[label]
data = self.dic_fold[fold][label][count]
labels_count[label] += 1
append_data_to_csv(self, data, fold, label, several_csv=self.several_csv)
maj_dicount(self, fold)
box = DatasetCreator()
@@ -0,0 +1,107 @@
# -*- coding: utf-8 -*-
from pyriemann.classification import MDM
from pyriemann.tangentspace import TangentSpace
from pyriemann.estimation import Covariances
from sklearn.pipeline import make_pipeline
from sklearn.tree import DecisionTreeClassifier
from sklearn.linear_model import LogisticRegression, SGDClassifier
from sklearn.neural_network import MLPClassifier
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.naive_bayes import GaussianNB
from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier, ExtraTreesClassifier, BaggingClassifier
from sklearn.neighbors import KNeighborsClassifier, NearestCentroid
from sklearn.metrics import confusion_matrix, classification_report
from collections import defaultdict
import numpy as np
import pickle
import os
from PolyBox import PolyBox
import warnings
warnings.filterwarnings("ignore")
class ProcessML(PolyBox):
#----------------------------------------
def __init__(self):
PolyBox.__init__(self, record=False)
self.model_path = None
self.pred_saving_path = None
self.model = None
self.predictions = []
self.shape = None
#----------------------------------------
def on_initialize(self):
# we get the model file
self.model_path = self.setting['Model filename']
# we load the model
try:
self.model = pickle.load(open(self.model_path, 'rb'))
except IOError as err:
print(err)
print('Please indicate an existing model.')
self.send_end_stim()
try:
self.pred_saving_path = self.setting['Predictions filename']
if len(self.pred_saving_path.replace(' ', '')) > 0:
self.pred_saving_path = os.path.abspath(self.pred_saving_path)
except IOError:
print('No filename to save predictions has been given, they will not be saved')
#----------------------------------------
def on_end_box(self):
self.save_preds()
self.make_stats()
#----------------------------------------
def list_to_str(self, preds):
string = ''
for x in preds:
string += str(x) + ','
return string
#----------------------------------------
def save_preds(self):
if self.pred_saving_path is not None and self.pred_saving_path != "":
preds_str = self.list_to_str(self.predictions)
with open(self.pred_saving_path, 'wb') as file:
file.write(preds_str)
print('Predictions saved in {}\n'.format(self.pred_saving_path))
#----------------------------------------
def on_chunk_received(self, chunk, label, shape):
chunk = np.array(chunk)
if self.shape is None:
# Riemanian Geometry
if self.model.custom_classifier == 'Riemann Minimum Distance to Mean' or self.model.custom_classifier == 'Riemann Tangent Space':
self.shape = (1, shape[0], shape[1])
else:
self.shape = (1, chunk.shape[0])
chunk = chunk.reshape(self.shape)
pred = self.model.predict(chunk)
self.predictions += list(pred)
#----------------------------------------
def make_stats(self):
length = len(self.predictions)
dictpred = defaultdict(int)
for elem in self.predictions:
dictpred[elem] += 1
print("Metrics : \n")
print('\n'.join(['{} : {}'.format(l, float(v)/length) for l, v in dictpred.items()]))
box = ProcessML()
@@ -0,0 +1,247 @@
# -*- coding: utf-8 -*-
from pyriemann.classification import MDM
from pyriemann.tangentspace import TangentSpace
from pyriemann.estimation import Covariances
import time
from sklearn.pipeline import make_pipeline
from sklearn.tree import DecisionTreeClassifier
from sklearn.linear_model import LogisticRegression, SGDClassifier
from sklearn.neural_network import MLPClassifier
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.naive_bayes import GaussianNB
from sklearn.svm import LinearSVC
from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier, ExtraTreesClassifier, BaggingClassifier
from sklearn.neighbors import KNeighborsClassifier, NearestCentroid
from sklearn.metrics import confusion_matrix, classification_report
from natsort import natsorted
import random
import pickle
import numpy as np
from collections import defaultdict
from PolyBox import PolyBox
import warnings
warnings.filterwarnings("ignore")
#----------------------------------------
def train_test_split(data_dict, test_size=0.2):
def create_xy(data_dict):
# used to shuffle data in a way that prevent any training data to be in the test set
y = np.array([])
for label in data_dict:
y_tmp = np.array([label for _ in data_dict[label]])
y = np.concatenate((y, y_tmp))
array = []
for label in data_dict:
array.append(data_dict[label])
x = np.concatenate(array, axis=0)
# shuffling
permutation = np.random.permutation(x.shape[0])
permut = True
if permut:
x = x[permutation]
y = y[permutation]
return x, y
dict_train = {}
dict_test = {}
for key, data in data_dict.items():
icut = int(len(data)*test_size)
dict_test[key] = data[:icut]
dict_train[key] = data[icut:]
x_train, y_train = create_xy(dict_train)
x_test, y_test = create_xy(dict_test)
return np.array(x_train), np.array(y_train), np.array(x_test), np.array(y_test)
#----------------------------------------
class TrainerML(PolyBox):
#----------------------------------------
# is exec first
def __init__(self):
PolyBox.__init__(self)
self.model_path = None
self.config_path = None
self.save_path = None
self.model = None
self.clf = None
self.config = None
self.std_settings = []
self.clf_dependant_settings = None
#----------------------------------------
def on_initialize(self):
try:
self.model_path = self.setting['Filename to save model to']
except KeyError:
self.model_path = ''
if self.model_path == '':
print('No correct file location has been given for saving the model, thus it won\'t be saved')
try:
self.test_set_share = float(self.setting['Test set share'])
# wrong value
diff = (1 - self.test_set_share)
if diff <= 0 or diff > 1:
self.test_set_share = 0
print('The value of the test set share must be between 0 and 1 (1 not included), no prediction will be performed')
except KeyError:
self.test_set_share = 0
print('The value of the test set share must be between 0 and 1 (1 not included), no prediction will be performed')
try:
self.save_path = self.setting['Filename to load model from']
self.model = pickle.load(open(self.save_path, 'rb'))
except KeyError:
self.save_path = ''
print('No correct location has been given to load the model from, thus a new model will be created.')
# if model doesn't exist we will init a new one with params from the box
# FileNotFoundError doesn't exist in Python 2.7
except IOError:
print('No correct location has been given to load the model from, thus a new model will be created.')
# special case for Riemannian Geometry because it needs a pipeline
clf = self.setting['Classifier']
try:
discriminator, _ = self.map_clf(self.setting['Discriminator'])
except KeyError:
discriminator = None
if clf == 'Riemann Tangent Space':
if discriminator is not None:
self.clf = make_pipeline(Covariances(), TangentSpace(metric='riemann'), discriminator())
else:
self.clf = make_pipeline(Covariances(), TangentSpace(metric='riemann'), LinearDiscriminantAnalysis())
elif clf == 'Riemann Minimum Distance to Mean':
if discriminator is not None:
self.clf = make_pipeline(Covariances(), MDM(metric=dict(mean='riemann', distance='riemann')), discriminator())
else:
self.clf = make_pipeline(Covariances(), MDM(metric=dict(mean='riemann', distance='riemann')))
else:
self.clf, _ = self.map_clf(clf)
self.init_params()
try:
self.clf = self.clf(**self.clf_dependant_settings)
except TypeError:
self.clf = self.clf()
#----------------------------------------
def init_params(self):
# default settings
self.std_settings = ['Classifier', 'Discriminator', 'Filename to load model from', 'Test set share',
'Filename to save configuration to', 'Filename to save model to', 'Clock frequency (Hz)', 'Labels']
settings = [key for key in self.setting.keys()]
# we get only settings that are clf relevant
clf_dependant_settings = list(set(settings) - set(self.std_settings))
clf_dependant_settings = dict((k, v) for k, v in self.setting.items() if (k in clf_dependant_settings and len(v) > 0))
# we convert values that need to be
for k, v in clf_dependant_settings.items():
try:
expr = eval(v)
clf_dependant_settings[k] = expr
except:
if v.lower() == 'true':
clf_dependant_settings[k] = True
elif v.lower() == 'false':
clf_dependant_settings[k] = False
elif v.lower() == 'none':
clf_dependant_settings[k] = None
else:
pass
self.clf_dependant_settings = clf_dependant_settings
#----------------------------------------
def map_clf(self, classifier):
"""
Returns the correct algorithm according to the classifier string
"""
switcher = {
'': None,
'None': None,
'Nearest Centroid': NearestCentroid,
'Nearest Neighbors Classifier': KNeighborsClassifier,
'Gaussian Naive Bayes': GaussianNB,
'Stochastic Gradient Descent': SGDClassifier,
'Logistic Regression': LogisticRegression,
'Decision Tree Classifier': DecisionTreeClassifier,
'Extra Trees': ExtraTreesClassifier,
'Bagging': BaggingClassifier,
'Random Forest': RandomForestClassifier,
'Support Vector Machine': LinearSVC,
'Linear Discriminant Analysis': LinearDiscriminantAnalysis,
'AdaBoost': AdaBoostClassifier,
'Multi Layer Perceptron': MLPClassifier,
'Linear SVC': LinearSVC,
}
clf = switcher.get(classifier, lambda: 'unknown classifier')
return clf, switcher
#----------------------------------------
def on_chunk_received(self, chunk, label, shape):
# special case for riemannian geometry
if self.setting['Classifier'] == 'Riemann Minimum Distance to Mean' or self.setting['Classifier'] == 'Riemann Tangent Space':
numpyBuffer = np.array(chunk).reshape(shape)
self.data[label][-1] = numpyBuffer
#----------------------------------------
def on_end_box(self):
try:
self.train()
self.save()
except Exception as e:
print(e)
self.send_end_stim()
#----------------------------------------
def train(self):
x_train, y_train, x_test, y_test = train_test_split(self.data, self.test_set_share)
if self.model != None:
self.clf = self.model
self.clf.fit(x_train, y_train)
# to be used in ProcessML
self.clf.custom_classifier = self.setting['Classifier']
if x_test.shape[0] > 0:
predictions = self.clf.predict(x_test)
report = classification_report(y_test, predictions, labels=list(self.data.keys()))
matrix = confusion_matrix(y_test, predictions)
print("Report :\n{}\n".format(report))
print("Confusion Matrix : \n{}\n".format(matrix))
print("Fin de l'entrainement...")
#----------------------------------------
def save(self):
if self.model_path != "":
pickle.dump(self.clf, open(self.model_path, 'wb'))
print('Model saved in {}\n'.format(self.model_path))
box = TrainerML()
@@ -0,0 +1,120 @@
///-------------------------------------------------------------------------------------------------
///
/// \file ovpADA.h
/// \brief Class NewBoxPattern
/// \author Thibaut Monseigne (Inria) & Jimmy Leblanc (Polymont) & Yannis Bendi-Ouis (Polymont)
/// \version 1.0.
/// \date 12/03/2020.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/%22%3EGNU Affero General Public License v3.0</a>.
///
///-------------------------------------------------------------------------------------------------
#pragma once
// Windows debug build doesn't typically link as most people don't have the python debug library.
#if defined TARGET_HAS_ThirdPartyPython3 && !(defined(WIN32) && defined(TARGET_BUILDTYPE_Debug))
#include <Python.h>
#if defined(PY_MAJOR_VERSION) && (PY_MAJOR_VERSION == 3)
#include "defines.hpp"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <vector>
namespace OpenViBE {
namespace Plugins {
namespace PyBox {
/// <summary> Enumeration of Adaptation Methods for classifier. </summary>
enum class EClassifier
{
NearestCentroid, NearestNeighbors, GaussianNaiveBayes, StochasticGradientDescent, LogisticRegression, DecisionTree, ExtraTrees,
Bagging, RandomForest, SVM, LDA, AdaBoost, MultiLayerPerceptron, MDM, TangentSpace, None
};
/// <summary> Convert classifier to string.</summary>
/// <param name="type"> The type of classifier.</param>
/// <returns> std::string </returns>
inline std::string toString(const EClassifier type)
{
switch (type)
{
case EClassifier::None: return "None";
case EClassifier::NearestCentroid: return "Nearest Centroid";
case EClassifier::NearestNeighbors: return "Nearest Neighbors";
case EClassifier::GaussianNaiveBayes: return "Gaussian Naive Bayes";
case EClassifier::StochasticGradientDescent: return "Stochastic Gradient Descent";
case EClassifier::LogisticRegression: return "Logistic Regression";
case EClassifier::DecisionTree: return "Decision Tree";
case EClassifier::ExtraTrees: return "Extra Trees";
case EClassifier::Bagging: return "Bagging";
case EClassifier::RandomForest: return "Random Forest";
case EClassifier::SVM: return "Support Vector Machine";
case EClassifier::LDA: return "Linear Discriminant Analysis";
case EClassifier::AdaBoost: return "AdaBoost";
case EClassifier::MultiLayerPerceptron: return "Multi Layer Perceptron";
case EClassifier::MDM: return "Riemann Minimum Distance to Mean";
case EClassifier::TangentSpace: return "Riemann Tangent Space";
default: return "Invalid";
}
}
class CPolyBox : virtual public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
{
public:
void release() override { delete this; }
uint64_t getClockFrequency() override { return m_clockFrequency << 32; }
bool initialize() override;
bool uninitialize() override;
bool processClock(Kernel::CMessageClock& msg) override;
bool processInput(size_t index) override;
bool process() override;
protected:
uint64_t m_clockFrequency = 0;
CString m_script;
std::vector<Toolkit::TDecoder<CPolyBox>*> m_decoders;
std::vector<Toolkit::TEncoder<CPolyBox>*> m_encoders;
// These are all borrowed references in python v2.7. Do not free them.
static bool m_isInitialized;
static PyObject *m_mainModule, *m_mainDictionnary;
static PyObject *m_matrixHeader, *m_matrixBuffer, *m_matrixEnd;
static PyObject *m_signalHeader, *m_signalBuffer, *m_signalEnd;
static PyObject *m_stimulationHeader, *m_stimulation, *m_stimulationSet, *m_stimulationEnd;
static PyObject* m_buffer;
static PyObject* m_execFileFunction;
static PyObject *m_stdout, *m_stderr;
//std::map<char,PyObject *> m_PyObjectMap;
PyObject *m_box = nullptr, *m_boxInput = nullptr, *m_boxOutput = nullptr, *m_boxSetting = nullptr, *m_boxTime = nullptr;
PyObject *m_boxInitialize = nullptr, *m_boxProcess = nullptr, *m_boxUninitialize = nullptr;
bool m_initializeSucceeded = false;
bool logSysStd(const bool out);
bool logSysStdout() { return logSysStd(true); }
bool logSysStderr() { return logSysStd(false); }
void buildPythonSettings();
bool initializePythonSafely();
bool transferStreamedMatrixInputChunksToPython(const size_t index);
bool transferStreamedMatrixOutputChunksFromPython(const size_t index);
bool transferSignalInputChunksToPython(const size_t index);
bool transferSignalOutputChunksFromPython(const size_t index);
bool transferStimulationInputChunksToPython(const size_t index);
bool transferStimulationOutputChunksFromPython(const size_t index);
};
} // namespace PyBox
} // namespace Plugins
} // namespace OpenViBE
#endif // #if defined(PY_MAJOR_VERSION) && (PY_MAJOR_VERSION == 3)
#endif // TARGET_HAS_ThirdPartyPython3
@@ -0,0 +1,99 @@
///-------------------------------------------------------------------------------------------------
///
/// \file DataViz.hpp
/// \brief Class NewBoxPattern
/// \author Thibaut Monseigne (Inria) & Jimmy Leblanc (Polymont) & Yannis Bendi-Ouis (Polymont)
/// \version 1.0.
/// \date 12/03/2020.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/%22%3EGNU Affero General Public License v3.0</a>.
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include "CPolyBox.hpp"
#if defined TARGET_HAS_ThirdPartyPython3 && !(defined(WIN32) && defined(TARGET_BUILDTYPE_Debug))
#if defined(PY_MAJOR_VERSION) && (PY_MAJOR_VERSION == 3)
namespace OpenViBE {
namespace Plugins {
namespace PyBox {
class CBoxAlgorithmDataViz final : public CPolyBox
{
public:
CBoxAlgorithmDataViz() { m_script = Directories::getDataDir() + "/plugins/python3/pybox/DataViz.py"; }
_IsDerivedFromClass_Final_(OpenViBE::Toolkit::TBoxAlgorithm < OpenViBE::Plugins::IBoxAlgorithm >, OVP_ClassId_BoxAlgorithm_DataViz)
};
class CBoxAlgorithmDataVizListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
bool onInputAdded(Kernel::IBox& box, const size_t index) override
{
box.setInputType(index, OV_TypeId_StreamedMatrix);
return true;
}
_IsDerivedFromClass_Final_(OpenViBE::Toolkit::TBoxListener < OpenViBE::Plugins::IBoxListener >, CIdentifier::undefined())
};
class CBoxAlgorithmDataVizDesc final : virtual public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("DataViz"); }
CString getAuthorName() const override { return CString("Yannis Bendi-Ouis & Jimmy Leblanc"); }
CString getAuthorCompanyName() const override { return CString("Polymont IT Services"); }
CString getShortDescription() const override { return CString("Transform the data with a LDA or a PCA and plot the data in 2D or 3D."); }
CString getDetailedDescription() const override { return CString(""); }
CString getCategory() const override { return CString("Scripting/PyBox"); }
CString getVersion() const override { return CString("0.1"); }
CString getStockItemName() const override { return CString("gtk-convert"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_DataViz; }
IPluginObject* create() override { return new CBoxAlgorithmDataViz; }
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmDataVizListener; }
void releaseBoxListener(IBoxListener* pBoxListener) const override { delete pBoxListener; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addSetting("Clock frequency (Hz)", OV_TypeId_Integer, "64");
// <tag> settings
prototype.addSetting("Path to save the model", OV_TypeId_Filename, "");
prototype.addSetting("Path to load the model", OV_TypeId_Filename, "");
prototype.addSetting("Algorithm (PCA or LDA)", OV_TypeId_String, "PCA");
prototype.addSetting("Dimension reduction", OV_TypeId_Integer, "2");
prototype.addSetting("Labels", OV_TypeId_String, "");
prototype.addFlag(Kernel::BoxFlag_CanAddInput);
prototype.addFlag(Kernel::BoxFlag_CanModifyInput);
prototype.addFlag(Kernel::BoxFlag_CanAddSetting);
prototype.addFlag(Kernel::BoxFlag_CanModifySetting);
prototype.addInputSupport(OV_TypeId_Signal);
prototype.addInputSupport(OV_TypeId_Stimulations);
prototype.addInputSupport(OV_TypeId_StreamedMatrix);
prototype.addOutputSupport(OV_TypeId_Signal);
prototype.addOutputSupport(OV_TypeId_Stimulations);
prototype.addOutputSupport(OV_TypeId_StreamedMatrix);
// <tag> input & output
prototype.addOutput("stim_out", OV_TypeId_Stimulations);
prototype.addInput("input_StreamMatrix", OV_TypeId_StreamedMatrix);
prototype.addInput("input_Stimulations", OV_TypeId_Stimulations);
return true;
}
_IsDerivedFromClass_Final_(OpenViBE::Plugins::IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_DataVizDesc)
};
} // namespace PyBox
} // namespace Plugins
} // namespace OpenViBE
#endif // #if defined(PY_MAJOR_VERSION) && (PY_MAJOR_VERSION == 3)
#endif // TARGET_HAS_ThirdPartyPython3
@@ -0,0 +1,101 @@
///-------------------------------------------------------------------------------------------------
///
/// \file DatasetCreator.hpp
/// \brief Class NewBoxPattern
/// \author Thibaut Monseigne (Inria) & Jimmy Leblanc (Polymont) & Yannis Bendi-Ouis (Polymont)
/// \version 1.0.
/// \date 12/03/2020.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/%22%3EGNU Affero General Public License v3.0</a>.
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include "CPolyBox.hpp"
#if defined TARGET_HAS_ThirdPartyPython3 && !(defined(WIN32) && defined(TARGET_BUILDTYPE_Debug))
#if defined(PY_MAJOR_VERSION) && (PY_MAJOR_VERSION == 3)
namespace OpenViBE {
namespace Plugins {
namespace PyBox {
class CBoxAlgorithmDatasetCreator final : public CPolyBox
{
public:
CBoxAlgorithmDatasetCreator() { m_script = Directories::getDataDir() + "/plugins/python3/pybox/DatasetCreator.py"; }
_IsDerivedFromClass_Final_(OpenViBE::Toolkit::TBoxAlgorithm < OpenViBE::Plugins::IBoxAlgorithm >, OVP_ClassId_BoxAlgorithm_DatasetCreator)
};
class CBoxAlgorithmDatasetCreatorListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
bool onInputAdded(Kernel::IBox& box, const size_t index) override
{
box.setInputType(index, OV_TypeId_StreamedMatrix);
return true;
}
_IsDerivedFromClass_Final_(OpenViBE::Toolkit::TBoxListener < OpenViBE::Plugins::IBoxListener >, CIdentifier::undefined())
};
class CBoxAlgorithmDatasetCreatorDesc final : virtual public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("DatasetCreator"); }
CString getAuthorName() const override { return CString("Yannis Bendi-Ouis & Jimmy LeBlanc"); }
CString getAuthorCompanyName() const override { return CString("Polymont IT Services"); }
CString getShortDescription() const override { return CString("Monitor the user to create a dataset."); }
CString getDetailedDescription() const override { return CString(""); }
CString getCategory() const override { return CString("Scripting/PyBox"); }
CString getVersion() const override { return CString("0.1"); }
CString getStockItemName() const override { return CString("gtk-convert"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_DatasetCreator; }
IPluginObject* create() override { return new CBoxAlgorithmDatasetCreator; }
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmDatasetCreatorListener; }
void releaseBoxListener(IBoxListener* pBoxListener) const override { delete pBoxListener; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addSetting("Clock frequency (Hz)", OV_TypeId_Integer, "64");
// <tag> settings
prototype.addSetting("Path directory", OV_TypeId_Filename, "${Player_ScenarioDirectory}/datas/");
prototype.addSetting("Label_1", OV_TypeId_String, "right");
prototype.addSetting("Label_2", OV_TypeId_String, "left");
prototype.addSetting("Label_3", OV_TypeId_String, "up");
prototype.addSetting("Label_4", OV_TypeId_String, "down");
prototype.addSetting("Several CSV", OV_TypeId_Boolean, "false");
prototype.addSetting("Number of folds", OV_TypeId_Integer, "1");
prototype.addSetting("Number of actions", OV_TypeId_Integer, "4");
prototype.addFlag(Kernel::BoxFlag_CanAddInput);
prototype.addFlag(Kernel::BoxFlag_CanModifyInput);
prototype.addFlag(Kernel::BoxFlag_CanAddSetting);
prototype.addFlag(Kernel::BoxFlag_CanModifySetting);
prototype.addInputSupport(OV_TypeId_Signal);
prototype.addInputSupport(OV_TypeId_Stimulations);
prototype.addInputSupport(OV_TypeId_StreamedMatrix);
prototype.addOutputSupport(OV_TypeId_Signal);
prototype.addOutputSupport(OV_TypeId_Stimulations);
prototype.addOutputSupport(OV_TypeId_StreamedMatrix);
// <tag> input & output
prototype.addOutput("stim_out", OV_TypeId_Stimulations);
prototype.addInput("input_StreamMatrix", OV_TypeId_StreamedMatrix);
return true;
}
_IsDerivedFromClass_Final_(OpenViBE::Plugins::IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_DatasetCreatorDesc)
};
} // namespace PyBox
} // namespace Plugins
} // namespace OpenViBE
#endif // #if defined(PY_MAJOR_VERSION) && (PY_MAJOR_VERSION == 3)
#endif // TARGET_HAS_ThirdPartyPython3
@@ -0,0 +1,98 @@
///-------------------------------------------------------------------------------------------------
///
/// \file ProcessML.hpp
/// \brief Class NewBoxPattern
/// \author Thibaut Monseigne (Inria) & Jimmy Leblanc (Polymont) & Yannis Bendi-Ouis (Polymont)
/// \version 1.0.
/// \date 12/03/2020.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/%22%3EGNU Affero General Public License v3.0</a>.
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include "CPolyBox.hpp"
#if defined TARGET_HAS_ThirdPartyPython3 && !(defined(WIN32) && defined(TARGET_BUILDTYPE_Debug))
#if defined(PY_MAJOR_VERSION) && (PY_MAJOR_VERSION == 3)
namespace OpenViBE {
namespace Plugins {
namespace PyBox {
class CBoxAlgorithmProcessML final : public CPolyBox
{
public:
CBoxAlgorithmProcessML() { m_script = Directories::getDataDir() + "/plugins/python3/pybox/ProcessML.py"; }
_IsDerivedFromClass_Final_(OpenViBE::Toolkit::TBoxAlgorithm < OpenViBE::Plugins::IBoxAlgorithm >, OVP_ClassId_BoxAlgorithm_ProcessML)
};
class CBoxAlgorithmProcessMLListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
bool onInputAdded(Kernel::IBox& box, const size_t index) override
{
box.setInputType(index, OV_TypeId_StreamedMatrix);
return true;
}
_IsDerivedFromClass_Final_(OpenViBE::Toolkit::TBoxListener < OpenViBE::Plugins::IBoxListener >, CIdentifier::undefined())
};
class CBoxAlgorithmProcessMLDesc final : virtual public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Process Sklearn"); }
CString getAuthorName() const override { return CString("Jimmy Leblanc & Yannis Bendi-Ouis"); }
CString getAuthorCompanyName() const override { return CString("Polymont IT Services"); }
CString getShortDescription() const override
{
return CString("This box aim to use a machine learning model previously trained to predict labels of input's data.");
}
CString getDetailedDescription() const override { return CString(""); }
CString getCategory() const override { return CString("Scripting/PyBox"); }
CString getVersion() const override { return CString("0.1"); }
CString getStockItemName() const override { return CString("gtk-convert"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_ProcessML; }
IPluginObject* create() override { return new CBoxAlgorithmProcessML; }
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmProcessMLListener; }
void releaseBoxListener(IBoxListener* pBoxListener) const override { delete pBoxListener; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addSetting("Clock frequency (Hz)", OV_TypeId_Integer, "64");
// <tag> settings
prototype.addSetting("Model filename", OV_TypeId_Filename, "${Player_ScenarioDirectory}/model.clf");
prototype.addSetting("Predictions filename", OV_TypeId_Filename, "");
prototype.addFlag(Kernel::BoxFlag_CanAddInput);
prototype.addFlag(Kernel::BoxFlag_CanModifyInput);
prototype.addFlag(Kernel::BoxFlag_CanAddSetting);
prototype.addFlag(Kernel::BoxFlag_CanModifySetting);
prototype.addInputSupport(OV_TypeId_Signal);
prototype.addInputSupport(OV_TypeId_Stimulations);
prototype.addInputSupport(OV_TypeId_StreamedMatrix);
prototype.addOutputSupport(OV_TypeId_Signal);
prototype.addOutputSupport(OV_TypeId_Stimulations);
prototype.addOutputSupport(OV_TypeId_StreamedMatrix);
// <tag> input & output
prototype.addOutput("stim_out", OV_TypeId_Stimulations);
prototype.addInput("input_StreamMatrix", OV_TypeId_StreamedMatrix);
return true;
}
_IsDerivedFromClass_Final_(OpenViBE::Plugins::IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_ProcessMLDesc)
};
} // namespace PyBox
} // namespace Plugins
} // namespace OpenViBE
#endif // #if defined(PY_MAJOR_VERSION) && (PY_MAJOR_VERSION == 3)
#endif // TARGET_HAS_ThirdPartyPython3
@@ -0,0 +1,257 @@
#include "TrainerML.hpp"
#include <tuple>
#include <vector>
#if defined TARGET_HAS_ThirdPartyPython3 && !(defined(WIN32) && defined(TARGET_BUILDTYPE_Debug))
#if defined(PY_MAJOR_VERSION) && (PY_MAJOR_VERSION == 3)
namespace OpenViBE {
namespace Plugins {
namespace PyBox {
static const std::vector<std::tuple<std::string, CIdentifier, std::string>> ADA_BOOST_SETTING = {
std::make_tuple("Test set share", OV_TypeId_Float, "0.2"),
std::make_tuple("Labels", OV_TypeId_String, ""),
std::make_tuple("n_estimators", OV_TypeId_Integer, "50"),
std::make_tuple("learning_rate", OV_TypeId_Float, "1.0"),
std::make_tuple("algorithm", OVPoly_ClassId_ADA_algorithm, "SAMME.R"),
std::make_tuple("random_state", OV_TypeId_String, "None")
};
static const std::vector<std::tuple<std::string, CIdentifier, std::string>> BAGGING_SETTING = {
std::make_tuple("Test set share", OV_TypeId_Float, "0.2"),
std::make_tuple("Labels", OV_TypeId_String, ""),
std::make_tuple("max_features", OV_TypeId_Float, "1.0"),
std::make_tuple("random_state", OV_TypeId_String, "None"),
std::make_tuple("n_estimators", OV_TypeId_Integer, "10"),
std::make_tuple("bootstrap", OV_TypeId_Boolean, "true"),
std::make_tuple("bootstrap_features", OV_TypeId_Boolean, "false"),
std::make_tuple("oob_score", OV_TypeId_Boolean, "false"),
std::make_tuple("warm_start", OV_TypeId_Boolean, "false"),
std::make_tuple("n_jobs", OV_TypeId_String, "None"),
std::make_tuple("verbose", OV_TypeId_Integer, "0")
};
static const std::vector<std::tuple<std::string, CIdentifier, std::string>> TREE_SETTING = {
std::make_tuple("Test set share", OV_TypeId_Float, "0.2"),
std::make_tuple("Labels", OV_TypeId_String, ""),
std::make_tuple("criterion", OVPoly_ClassId_Criterion, "gini"),
std::make_tuple("splitter", OVPoly_ClassId_DecisionTree_splitter, "best"),
std::make_tuple("max_depth", OV_TypeId_String, "None"),
std::make_tuple("min_samples_split", OV_TypeId_Float, "2"),
std::make_tuple("min_samples_leaf", OV_TypeId_Float, "1"),
std::make_tuple("min_weight_fraction_leaf", OV_TypeId_Float, "0"),
std::make_tuple("max_features", OV_TypeId_String, "None"),
std::make_tuple("random_state", OV_TypeId_String, "None"),
std::make_tuple("max_leaf_nodes", OV_TypeId_String, "None"),
std::make_tuple("min_impurity_decrease", OV_TypeId_Float, "0"),
std::make_tuple("min_impurity_split", OV_TypeId_Float, "1e-7")
};
static const std::vector<std::tuple<std::string, CIdentifier, std::string>> XTREE_SETTING = {
std::make_tuple("Test set share", OV_TypeId_Float, "0.2"),
std::make_tuple("Labels", OV_TypeId_String, ""),
std::make_tuple("criterion", OVPoly_ClassId_Criterion, "gini"),
std::make_tuple("max_depth", OV_TypeId_String, "None"),
std::make_tuple("min_samples_split", OV_TypeId_Float, "2"),
std::make_tuple("min_samples_leaf", OV_TypeId_Float, "1"),
std::make_tuple("min_weight_fraction_leaf", OV_TypeId_Float, "0"),
std::make_tuple("max_features", OV_TypeId_String, "auto"),
std::make_tuple("random_state", OV_TypeId_String, "None"),
std::make_tuple("max_leaf_nodes", OV_TypeId_String, "None"),
std::make_tuple("min_impurity_decrease", OV_TypeId_Float, "0"),
std::make_tuple("min_impurity_split", OV_TypeId_Float, "1e-7"),
std::make_tuple("bootstrap", OV_TypeId_Boolean, "false"),
std::make_tuple("oob_score", OV_TypeId_Boolean, "false"),
std::make_tuple("verbose", OV_TypeId_Integer, "0"),
std::make_tuple("warm_start", OV_TypeId_Boolean, "false"),
std::make_tuple("max_samples", OV_TypeId_String, "None"),
};
static const std::vector<std::tuple<std::string, CIdentifier, std::string>> GAUSSIAN_SETTING = {
std::make_tuple("Discriminator", OV_TypeId_String, ""),
std::make_tuple("Test set share", OV_TypeId_Float, "0.2"),
std::make_tuple("Labels", OV_TypeId_String, ""),
std::make_tuple("var_smoothing", OV_TypeId_Float, "0.000000001"),
};
static const std::vector<std::tuple<std::string, CIdentifier, std::string>> KNN_SETTING = {
std::make_tuple("Discriminator", OV_TypeId_String, ""),
std::make_tuple("Test set share", OV_TypeId_Float, "0.2"),
std::make_tuple("Labels", OV_TypeId_String, ""),
std::make_tuple("n_neighbors", OV_TypeId_Integer, "5"),
std::make_tuple("weights", OVPoly_ClassId_Knn_Weights, "uniform"),
std::make_tuple("algorithm", OVPoly_ClassId_Knn_Algorithm, "auto"),
std::make_tuple("leaf_size", OV_TypeId_Integer, "30"),
std::make_tuple("p", OV_TypeId_Integer, "2"),
std::make_tuple("metric", OVPoly_ClassId_Metric, "minkowski"),
};
static const std::vector<std::tuple<std::string, CIdentifier, std::string>> LDA_SETTING = {
std::make_tuple("Test set share", OV_TypeId_Float, "0.2"),
std::make_tuple("Labels", OV_TypeId_String, ""),
std::make_tuple("solver", OVPoly_ClassId_LDA_solver, "svd"),
std::make_tuple("n_components", OV_TypeId_String, ""),
};
static const std::vector<std::tuple<std::string, CIdentifier, std::string>> LR_SETTING = {
std::make_tuple("Test set share", OV_TypeId_Float, "0.2"),
std::make_tuple("Labels", OV_TypeId_String, ""),
std::make_tuple("penalty", OVPoly_ClassId_Penalty, "l2"),
std::make_tuple("dual", OV_TypeId_Boolean, "false"),
std::make_tuple("tol", OV_TypeId_Float, "1e-4"),
std::make_tuple("fit_intercept", OV_TypeId_Boolean, "true"),
std::make_tuple("intercept_scaling", OV_TypeId_Float, "1"),
std::make_tuple("random_state", OV_TypeId_String, "None"),
std::make_tuple("solver", OVPoly_ClassId_Log_reg_solver, "lbfgs"),
std::make_tuple("max_iter", OV_TypeId_Integer, "100"),
std::make_tuple("verbose", OV_TypeId_Integer, "0"),
std::make_tuple("warm_start", OV_TypeId_Boolean, "false"),
std::make_tuple("n_jobs", OV_TypeId_String, "None"),
std::make_tuple("multi_class", OVPoly_ClassId_Log_reg_multi_class, "auto"),
std::make_tuple("C", OV_TypeId_Float, "1.0")
};
static const std::vector<std::tuple<std::string, CIdentifier, std::string>> MLP_SETTING = {
std::make_tuple("Test set share", OV_TypeId_Float, "0.2"),
std::make_tuple("Labels", OV_TypeId_String, ""),
std::make_tuple("hidden_layer_sizes", OV_TypeId_String, "(100,)"),
std::make_tuple("activation", OVPoly_ClassId_MLP_activation, "relu"),
std::make_tuple("learning_rate", OVPoly_ClassId_MLP_learning_rate, "constant"),
std::make_tuple("solver", OVPoly_ClassId_MLP_solver, "adam"),
std::make_tuple("alpha", OV_TypeId_Float, "0.0001"),
std::make_tuple("batch_size", OV_TypeId_String, "auto"),
std::make_tuple("learning_rate_init", OV_TypeId_Float, "0.001"),
std::make_tuple("power_t", OV_TypeId_Float, "0.5"),
std::make_tuple("max_iter", OV_TypeId_Integer, "200"),
std::make_tuple("shuffle", OV_TypeId_Boolean, "true"),
std::make_tuple("random_state", OV_TypeId_String, "None"),
std::make_tuple("tol", OV_TypeId_Float, "1e-4"),
std::make_tuple("verbose", OV_TypeId_Boolean, "true"),
std::make_tuple("warm_start", OV_TypeId_Boolean, "false"),
std::make_tuple("momentum", OV_TypeId_Float, "0.9"),
std::make_tuple("nesterovs_momentum", OV_TypeId_Boolean, "true"),
std::make_tuple("early_stopping", OV_TypeId_Boolean, "false"),
std::make_tuple("validation_fraction", OV_TypeId_Float, "0.1"),
std::make_tuple("beta_1", OV_TypeId_Float, "0.9"),
std::make_tuple("beta_2", OV_TypeId_Float, "0.999"),
std::make_tuple("epsilon", OV_TypeId_Float, "1e-8"),
std::make_tuple("n_iter_no_change", OV_TypeId_Integer, "10")
};
static const std::vector<std::tuple<std::string, CIdentifier, std::string>> NC_SETTING = {
std::make_tuple("Test set share", OV_TypeId_Float, "0.2"),
std::make_tuple("Labels", OV_TypeId_String, ""),
std::make_tuple("metric", OVPoly_ClassId_Metric, "euclidean"),
std::make_tuple("shrink_threshold", OV_TypeId_String, "")
};
static const std::vector<std::tuple<std::string, CIdentifier, std::string>> RF_SETTING = {
std::make_tuple("Test set share", OV_TypeId_Float, "0.2"),
std::make_tuple("Labels", OV_TypeId_String, ""),
std::make_tuple("criterion", OVPoly_ClassId_Criterion, "gini"),
std::make_tuple("max_depth", OV_TypeId_String, "None"),
std::make_tuple("min_samples_split", OV_TypeId_Float, "2"),
std::make_tuple("min_samples_leaf", OV_TypeId_Float, "1"),
std::make_tuple("min_weight_fraction_leaf", OV_TypeId_Float, "0"),
std::make_tuple("max_features", OV_TypeId_String, "auto"),
std::make_tuple("random_state", OV_TypeId_String, "None"),
std::make_tuple("max_leaf_nodes", OV_TypeId_String, "None"),
std::make_tuple("min_impurity_decrease", OV_TypeId_Float, "0"),
std::make_tuple("min_impurity_split", OV_TypeId_Float, "1e-7"),
std::make_tuple("bootstrap", OV_TypeId_Boolean, "false"),
std::make_tuple("oob_score", OV_TypeId_Boolean, "false"),
std::make_tuple("verbose", OV_TypeId_Integer, "0"),
std::make_tuple("warm_start", OV_TypeId_Boolean, "false"),
std::make_tuple("max_samples", OV_TypeId_String, "None"),
std::make_tuple("n_estimators", OV_TypeId_Integer, "100")
};
static const std::vector<std::tuple<std::string, CIdentifier, std::string>> RTS_SETTING = {
std::make_tuple("Test set share", OV_TypeId_Float, "0.2"),
std::make_tuple("Labels", OV_TypeId_String, ""),
std::make_tuple("Discriminator", OVPoly_ClassId_Classifier_Algorithm, "Linear Discriminant Analysis")
};
static const std::vector<std::tuple<std::string, CIdentifier, std::string>> RMDM_SETTING = {
std::make_tuple("Test set share", OV_TypeId_Float, "0.2"),
std::make_tuple("Labels", OV_TypeId_String, ""),
std::make_tuple("Discriminator", OVPoly_ClassId_Classifier_Algorithm, "None")
};
static const std::vector<std::tuple<std::string, CIdentifier, std::string>> SGD_SETTING = {
std::make_tuple("Test set share", OV_TypeId_Float, "0.2"),
std::make_tuple("Labels", OV_TypeId_String, ""),
std::make_tuple("loss", OVPoly_ClassId_SGD_loss, "hinge"),
std::make_tuple("penalty", OVPoly_ClassId_Penalty, "l2"),
std::make_tuple("alpha", OV_TypeId_Float, "0.0001"),
std::make_tuple("l1_ratio", OV_TypeId_Float, "0.15"),
std::make_tuple("fit_intercept", OV_TypeId_Boolean, "true"),
std::make_tuple("max_iter", OV_TypeId_Integer, "1000"),
std::make_tuple("tol", OV_TypeId_Float, "0.001"),
std::make_tuple("shuffle", OV_TypeId_Boolean, "true"),
std::make_tuple("verbose", OV_TypeId_Integer, "0"),
std::make_tuple("epsilon", OV_TypeId_Float, "0.1"),
std::make_tuple("random_state", OV_TypeId_String, ""),
std::make_tuple("learning_rate", OVPoly_ClassId_SGD_learning_rate, "optimal"),
std::make_tuple("early_stopping", OV_TypeId_Boolean, "false"),
std::make_tuple("n_iter_no_change", OV_TypeId_Integer, "5")
};
static const std::vector<std::tuple<std::string, CIdentifier, std::string>> SVM_SETTING = {
std::make_tuple("Test set share", OV_TypeId_Float, "0.2"),
std::make_tuple("Labels", OV_TypeId_String, ""),
std::make_tuple("penalty", OVPoly_ClassId_Penalty, "l2"),
std::make_tuple("loss", OVPoly_ClassId_SVM_Loss, "squared_hinge"),
std::make_tuple("dual", OV_TypeId_Boolean, "true"),
std::make_tuple("tol", OV_TypeId_Float, "0.0001"),
std::make_tuple("C", OV_TypeId_Float, "1.0"),
std::make_tuple("multi_class", OVPoly_ClassId_SVM_MultiClass, "ovr"),
std::make_tuple("fit_intercept", OV_TypeId_Boolean, "true"),
std::make_tuple("intercept_scaling", OV_TypeId_Float, "1"),
std::make_tuple("verbose", OV_TypeId_Integer, "0"),
std::make_tuple("max_iter", OV_TypeId_Integer, "1000")
};
static void ClearSetting(Kernel::IBox& box) { while (box.getSettingCount() > 4) { box.removeSetting(4); } }
static bool SetSetting(Kernel::IBox& box, const std::vector<std::tuple<std::string, CIdentifier, std::string>>& settings)
{
for (const auto& t : settings) { box.addSetting(std::get<0>(t).c_str(), std::get<1>(t), std::get<2>(t).c_str()); }
return true;
}
bool CBoxAlgorithmTrainerMLListener::onSettingValueChanged(Kernel::IBox& box, const size_t index)
{
if (index == 3)
{
CString value;
box.getSettingValue(index, value);
ClearSetting(box);
if (std::string(value.toASCIIString()) == toString(EClassifier::NearestCentroid)) { return SetSetting(box, NC_SETTING); }
if (std::string(value.toASCIIString()) == toString(EClassifier::NearestNeighbors)) { return SetSetting(box, KNN_SETTING); }
if (std::string(value.toASCIIString()) == toString(EClassifier::GaussianNaiveBayes)) { return SetSetting(box, GAUSSIAN_SETTING); }
if (std::string(value.toASCIIString()) == toString(EClassifier::StochasticGradientDescent)) { return SetSetting(box, SGD_SETTING); }
if (std::string(value.toASCIIString()) == toString(EClassifier::LogisticRegression)) { return SetSetting(box, LR_SETTING); }
if (std::string(value.toASCIIString()) == toString(EClassifier::DecisionTree)) { return SetSetting(box, TREE_SETTING); }
if (std::string(value.toASCIIString()) == toString(EClassifier::ExtraTrees)) { return SetSetting(box, XTREE_SETTING); }
if (std::string(value.toASCIIString()) == toString(EClassifier::Bagging)) { return SetSetting(box, BAGGING_SETTING); }
if (std::string(value.toASCIIString()) == toString(EClassifier::RandomForest)) { return SetSetting(box, RF_SETTING); }
if (std::string(value.toASCIIString()) == toString(EClassifier::SVM)) { return SetSetting(box, SVM_SETTING); }
if (std::string(value.toASCIIString()) == toString(EClassifier::LDA)) { return SetSetting(box, LDA_SETTING); }
if (std::string(value.toASCIIString()) == toString(EClassifier::AdaBoost)) { return SetSetting(box, ADA_BOOST_SETTING); }
if (std::string(value.toASCIIString()) == toString(EClassifier::MultiLayerPerceptron)) { return SetSetting(box, MLP_SETTING); }
if (std::string(value.toASCIIString()) == toString(EClassifier::MDM)) { return SetSetting(box, RMDM_SETTING); }
if (std::string(value.toASCIIString()) == toString(EClassifier::TangentSpace)) { return SetSetting(box, RTS_SETTING); }
return true;
}
}
} // namespace PyBox
} // namespace Plugins
} // namespace OpenViBE
#endif // #if defined(PY_MAJOR_VERSION) && (PY_MAJOR_VERSION == 3)
#endif // TARGET_HAS_ThirdPartyPython3
@@ -0,0 +1,94 @@
///-------------------------------------------------------------------------------------------------
///
/// \file TrainerML.hpp
/// \brief Class TrainerML
/// \author Thibaut Monseigne (Inria) & Jimmy Leblanc (Polymont) & Yannis Bendi-Ouis (Polymont)
/// \version 1.0.
/// \date 12/03/2020.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/%22%3EGNU Affero General Public License v3.0</a>.
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include "CPolyBox.hpp"
#if defined TARGET_HAS_ThirdPartyPython3 && !(defined(WIN32) && defined(TARGET_BUILDTYPE_Debug))
#if defined(PY_MAJOR_VERSION) && (PY_MAJOR_VERSION == 3)
namespace OpenViBE {
namespace Plugins {
namespace PyBox {
class CBoxAlgorithmTrainerML final : public CPolyBox
{
public:
CBoxAlgorithmTrainerML() { m_script = Directories::getDataDir() + "/plugins/python3/pybox/TrainerML.py"; }
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_TrainerML)
};
class CBoxAlgorithmTrainerMLListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
bool onInputAdded(Kernel::IBox& box, const size_t index) override
{
box.setInputType(index, OV_TypeId_StreamedMatrix);
return true;
}
bool onSettingValueChanged(Kernel::IBox& box, const size_t index) override;
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
};
class CBoxAlgorithmTrainerMLDesc final : virtual public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Trainer Sklearn"); }
CString getAuthorName() const override { return CString("Jimmy Leblanc & Yannis Bendi-Ouis"); }
CString getAuthorCompanyName() const override { return CString("Polymont IT Services"); }
CString getShortDescription() const override { return CString("Train an TrainerML Classifier from Sklearn."); }
CString getDetailedDescription() const override { return CString(""); }
CString getCategory() const override { return CString("Scripting/PyBox"); }
CString getVersion() const override { return CString("0.1"); }
CString getStockItemName() const override { return CString("gtk-convert"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_TrainerML; }
IPluginObject* create() override { return new CBoxAlgorithmTrainerML; }
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmTrainerMLListener; }
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addSetting("Clock frequency (Hz)", OV_TypeId_Integer, "64");
// <tag> settings
prototype.addSetting("Filename to save model to", OV_TypeId_Filename, "${Player_ScenarioDirectory}/model.clf");
prototype.addSetting("Filename to load model from", OV_TypeId_Filename, "");
prototype.addSetting("Classifier", OVPoly_ClassId_Classifier_Algorithm, "None");
prototype.addInputSupport(OV_TypeId_Signal);
prototype.addInputSupport(OV_TypeId_Stimulations);
prototype.addInputSupport(OV_TypeId_StreamedMatrix);
prototype.addOutputSupport(OV_TypeId_Signal);
prototype.addOutputSupport(OV_TypeId_Stimulations);
prototype.addOutputSupport(OV_TypeId_StreamedMatrix);
// <tag> input & output
prototype.addOutput("stim_out", OV_TypeId_Stimulations);
prototype.addInput("input_StreamMatrix", OV_TypeId_StreamedMatrix);
prototype.addInput("input_Stimulations", OV_TypeId_Stimulations);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_TrainerMLDesc)
};
} // namespace PyBox
} // namespace Plugins
} // namespace OpenViBE
#endif // #if defined(PY_MAJOR_VERSION) && (PY_MAJOR_VERSION == 3)
#endif // TARGET_HAS_ThirdPartyPython3
@@ -0,0 +1,58 @@
#pragma once
//__________________________________________________________________//
// //
// Box //
//__________________________________________________________________//
// //
// <tag> Tag Box Declaration
#define OVP_ClassId_BoxAlgorithm_DatasetCreatorDesc OpenViBE::CIdentifier(0x6EBF2A07, 0x52EA5F3E)
#define OVP_ClassId_BoxAlgorithm_DatasetCreator OpenViBE::CIdentifier(0x7B6738C4, 0x2C5156ED)
#define OVP_ClassId_BoxAlgorithm_DataVizDesc OpenViBE::CIdentifier(0x44E07FAC, 0x57BE15DB)
#define OVP_ClassId_BoxAlgorithm_DataViz OpenViBE::CIdentifier(0x057B49AD, 0x040868CF)
#define OVP_ClassId_BoxAlgorithm_ProcessMLDesc OpenViBE::CIdentifier(0x764A11C8, 0x04006876)
#define OVP_ClassId_BoxAlgorithm_ProcessML OpenViBE::CIdentifier(0x681D3DFE, 0x060E2AFB)
#define OVP_ClassId_BoxAlgorithm_TrainerMLDesc OpenViBE::CIdentifier(0x6b14815c, 0x41da26d4)
#define OVP_ClassId_BoxAlgorithm_TrainerML OpenViBE::CIdentifier(0x44b85a3a, 0xabfc390a)
//__________________________________________________________________//
// //
// Custom Settings //
//__________________________________________________________________//
// //
// <tag> Custom Type Settings
#define OVPoly_ClassId_Classifier_Algorithm OpenViBE::CIdentifier(0x73AE164D, 0xEA21AB0A)
#define OVPoly_ClassId_Knn_Algorithm OpenViBE::CIdentifier(0xBF763F3B, 0x303EC694)
#define OVPoly_ClassId_Knn_Weights OpenViBE::CIdentifier(0x178AF88A, 0xEC44DFF3)
#define OVPoly_ClassId_Metric OpenViBE::CIdentifier(0xA04E99E9, 0x020A6874)
#define OVPoly_ClassId_SVM_Loss OpenViBE::CIdentifier(0xF05612EC, 0x139AFC45)
#define OVPoly_ClassId_Penalty OpenViBE::CIdentifier(0xBA4193F2, 0xCD152A47)
#define OVPoly_ClassId_SVM_MultiClass OpenViBE::CIdentifier(0x743F2BC9, 0xA84BC9DF)
#define OVPoly_ClassId_Criterion OpenViBE::CIdentifier(0x7455C643, 0x5D1E74E7)
// Logistic regression
#define OVPoly_ClassId_Log_reg_solver OpenViBE::CIdentifier(0x8CF680DF, 0x9EA77031)
#define OVPoly_ClassId_Log_reg_multi_class OpenViBE::CIdentifier(0xC0B45F28, 0x13AEBFD2)
// Decision Tree
#define OVPoly_ClassId_DecisionTree_splitter OpenViBE::CIdentifier(0x4E3C6F6F, 0x9AC56CEA)
// MLP
#define OVPoly_ClassId_MLP_activation OpenViBE::CIdentifier(0x324DA925, 0x58A1BCD9)
#define OVPoly_ClassId_MLP_solver OpenViBE::CIdentifier(0xAD038F27, 0x9B0AB84F)
#define OVPoly_ClassId_MLP_learning_rate OpenViBE::CIdentifier(0x5B10D3BC, 0x57658821)
// SGD
#define OVPoly_ClassId_SGD_loss OpenViBE::CIdentifier(0x57CA4145, 0xB25841A5)
#define OVPoly_ClassId_SGD_learning_rate OpenViBE::CIdentifier(0xB6075C69, 0x93BE696B)
// LDA
#define OVPoly_ClassId_LDA_solver OpenViBE::CIdentifier(0x2B78F491, 0xB86A6DE7)
// ADA
#define OVPoly_ClassId_ADA_algorithm OpenViBE::CIdentifier(0x27025481, 0x09BEDC31)
@@ -0,0 +1,167 @@
#if defined TARGET_HAS_ThirdPartyPython3 && !(defined(WIN32) && defined(TARGET_BUILDTYPE_Debug))
#include "box-algorithms/CPolyBox.hpp"
#include "box-algorithms/DataViz.hpp"
#include "box-algorithms/DatasetCreator.hpp"
#include "box-algorithms/ProcessML.hpp"
#include "box-algorithms/TrainerML.hpp"
#if defined(PY_MAJOR_VERSION) && (PY_MAJOR_VERSION == 3)
#ifdef TARGET_OS_Windows
#include "windows.h"
#endif
#include <string>
#include <iostream>
namespace OpenViBE {
namespace Plugins {
namespace PyBox {
OVP_Declare_Begin()
// <tag> OVP_Declare_New
OVP_Declare_New(CBoxAlgorithmDatasetCreatorDesc);
OVP_Declare_New(CBoxAlgorithmDataVizDesc);
OVP_Declare_New(CBoxAlgorithmProcessMLDesc);
OVP_Declare_New(CBoxAlgorithmTrainerMLDesc);
// <tag> Custom Type Settings
context.getTypeManager().registerEnumerationType(OVPoly_ClassId_Classifier_Algorithm, "Classifier_Algorithm");
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Classifier_Algorithm, "Nearest Centroid", 0);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Classifier_Algorithm, "Nearest Neighbors", 1);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Classifier_Algorithm, "Gaussian Naive Bayes", 2);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Classifier_Algorithm, "Stochastic Gradient Descent", 3);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Classifier_Algorithm, "Logistic Regression", 4);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Classifier_Algorithm, "Decision Tree", 5);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Classifier_Algorithm, "Extra Trees", 6);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Classifier_Algorithm, "Bagging", 7);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Classifier_Algorithm, "Random Forest", 8);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Classifier_Algorithm, "Support Vector Machine", 9);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Classifier_Algorithm, "Linear Discriminant Analysis", 10);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Classifier_Algorithm, "AdaBoost", 11);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Classifier_Algorithm, "Multi Layer Perceptron", 12);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Classifier_Algorithm, "Riemann Minimum Distance to Mean", 13);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Classifier_Algorithm, "Riemann Tangent Space", 14);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Classifier_Algorithm, "None", 15);
context.getTypeManager().registerEnumerationType(OVPoly_ClassId_Knn_Algorithm, "Knn_Algorithm");
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Knn_Algorithm, "auto", 0);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Knn_Algorithm, "ball_tree", 1);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Knn_Algorithm, "kd_tree", 2);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Knn_Algorithm, "brute", 3);
context.getTypeManager().registerEnumerationType(OVPoly_ClassId_Knn_Weights, "Knn_Weights");
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Knn_Weights, "uniform", 0);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Knn_Weights, "distance", 1);
context.getTypeManager().registerEnumerationType(OVPoly_ClassId_Metric, "Metric");
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Metric, "cityblock", 0);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Metric, "cosine", 1);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Metric, "euclidean", 2);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Metric, "l1", 3);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Metric, "l2", 4);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Metric, "manhattan", 5);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Metric, "braycurtis", 6);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Metric, "canberra", 7);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Metric, "chebyshev", 8);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Metric, "correlation", 9);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Metric, "dice", 10);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Metric, "hamming", 11);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Metric, "jaccard", 12);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Metric, "kulsinski", 13);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Metric, "minkowski", 14);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Metric, "mahalanobis", 15);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Metric, "rogerstanimoto", 16);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Metric, "russellrao", 17);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Metric, "seuclidean", 18);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Metric, "sokalmichener", 19);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Metric, "sokalsneath", 20);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Metric, "sqeuclidean", 21);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Metric, "yule", 22);
context.getTypeManager().registerEnumerationType(OVPoly_ClassId_Penalty, "Penalty");
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Penalty, "l1", 0);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Penalty, "l2", 1);
context.getTypeManager().registerEnumerationType(OVPoly_ClassId_SVM_Loss, "SVM_Loss");
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_SVM_Loss, "hinge", 0);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_SVM_Loss, "squared_hinge", 1);
context.getTypeManager().registerEnumerationType(OVPoly_ClassId_SVM_MultiClass, "SVM_MultiClass");
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_SVM_MultiClass, "ovr", 0);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_SVM_MultiClass, "crammer_singer", 1);
context.getTypeManager().registerEnumerationType(OVPoly_ClassId_Criterion, "Criterion");
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Criterion, "gini", 0);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Criterion, "entropy", 1);
// Logistic regression
context.getTypeManager().registerEnumerationType(OVPoly_ClassId_Log_reg_solver, "Solver");
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Log_reg_solver, "newton-cg", 0);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Log_reg_solver, "lbfgs", 1);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Log_reg_solver, "liblinear", 2);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Log_reg_solver, "sag", 3);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Log_reg_solver, "saga", 4);
context.getTypeManager().registerEnumerationType(OVPoly_ClassId_Log_reg_multi_class, "Multi_class");
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Log_reg_multi_class, "auto", 0);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Log_reg_multi_class, "ovr", 1);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_Log_reg_multi_class, "multinominal", 2);
// Decision Tree Classifier
context.getTypeManager().registerEnumerationType(OVPoly_ClassId_DecisionTree_splitter, "Splitter");
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_DecisionTree_splitter, "best", 0);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_DecisionTree_splitter, "random", 1);
// MLP
context.getTypeManager().registerEnumerationType(OVPoly_ClassId_MLP_activation, "Activation");
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_MLP_activation, "identity", 0);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_MLP_activation, "logistic", 1);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_MLP_activation, "tanh", 2);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_MLP_activation, "relu", 3);
context.getTypeManager().registerEnumerationType(OVPoly_ClassId_MLP_solver, "Solver");
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_MLP_solver, "lbfgs", 0);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_MLP_solver, "sgd", 1);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_MLP_solver, "adam", 2);
context.getTypeManager().registerEnumerationType(OVPoly_ClassId_MLP_learning_rate, "Learning rate");
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_MLP_learning_rate, "constant", 0);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_MLP_learning_rate, "invscaling", 1);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_MLP_learning_rate, "adaptive", 2);
// SGD
context.getTypeManager().registerEnumerationType(OVPoly_ClassId_SGD_loss, "Loss");
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_SGD_loss, "hinge", 0);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_SGD_loss, "log", 1);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_SGD_loss, "modified_huber", 2);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_SGD_loss, "squared_hinge", 3);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_SGD_loss, "perceptron", 4);
context.getTypeManager().registerEnumerationType(OVPoly_ClassId_SGD_learning_rate, "Learning rate");
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_SGD_learning_rate, "optimal", 0);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_SGD_learning_rate, "constant", 1);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_SGD_learning_rate, "invscaling", 2);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_SGD_learning_rate, "adaptive", 3);
// LDA
context.getTypeManager().registerEnumerationType(OVPoly_ClassId_LDA_solver, "Loss");
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_LDA_solver, "svd", 0);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_LDA_solver, "lsqr", 1);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_LDA_solver, "eigen", 2);
// ADA
context.getTypeManager().registerEnumerationType(OVPoly_ClassId_ADA_algorithm, "Algorithm");
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_ADA_algorithm, "SAMME", 0);
context.getTypeManager().registerEnumerationEntry(OVPoly_ClassId_ADA_algorithm, "SAMME.R", 1);
OVP_Declare_End()
} // namespace PyBox
} // namespace Plugins
} // namespace OpenViBE
#else
#pragma message ("WARNING: Python 3.7 headers are required to build the Python plugin, different includes found, skipped")
#endif // #if defined(PY_MAJOR_VERSION) && (PY_MAJOR_VERSION == 3)
#endif // TARGET_HAS_ThirdPartyPython3
@@ -0,0 +1,5 @@
# Add all the subdirs as projects of the named branch
# n.b. the folder is currently empty, don't bother adding
#OV_ADD_PROJECTS("CONTRIB_APPLICATIONS_PLATFORM")
@@ -0,0 +1,77 @@
# ---------------------------------
# Finds GNEEDaccessAPI
# Adds library to target
# Adds include path
# ---------------------------------
IF(WIN32)
IF("${PLATFORM_TARGET}" STREQUAL "x64")
SET(PATHS_GNEEDaccessAPI "C:/Program Files/gtec/gNEEDaccess Client API/C")
SET(PATHS_GNEEDaccessLIB "C:/Program Files/gtec/gNEEDaccess Client API/C/x64")
SET(PATHS_GNEEDaccessServer "C:/Program Files/gtec/gNEEDaccess/")
ELSE()
SET(PATHS_GNEEDaccessAPI "C:/Program Files/gtec/gNEEDaccess Client API/C" "C:/Program Files (x86)/gtec/gNEEDaccess Client API/C")
SET(PATHS_GNEEDaccessLIB "C:/Program Files/gtec/gNEEDaccess Client API/C/win32" "C:/Program Files (x86)/gtec/gNEEDaccess Client API/C/win32")
SET(PATHS_GNEEDaccessServer "C:/Program Files/gtec/gNEEDaccess/" "C:/Program Files (x86)/gtec/gNEEDaccess/")
ENDIF()
FIND_PATH(PATH_GNEEDaccessAPI GDSClientAPI.h PATHS
${PATHS_GNEEDaccessAPI}
NO_DEFAULT_PATH)
IF(PATH_GNEEDaccessAPI)
MESSAGE(STATUS " Found gtec gNEEDaccessAPI...")
# Find GDSClientAPI lib and dll
FIND_PATH(PATH_ClientLIB GDSClientAPI.dll PATHS
${PATHS_GNEEDaccessLIB}
NO_DEFAULT_PATH)
FIND_LIBRARY(LIB_GDSClientAPI GDSClientAPI PATHS ${PATHS_GNEEDaccessLIB} NO_DEFAULT_PATH)
IF(LIB_GDSClientAPI)
MESSAGE(STATUS " [ OK ] lib ${LIB_GDSClientAPI}")
ELSE(LIB_GDSClientAPI)
MESSAGE(STATUS " [FAILED] lib GDSClientAPI")
ENDIF(LIB_GDSClientAPI)
# Find GDSServer dll
FIND_PATH(PATH_ServerDLL GDSServer.dll PATHS
${PATHS_GNEEDaccessServer}
NO_DEFAULT_PATH)
IF(PATH_ServerDLL)
MESSAGE(STATUS " [ OK ] dll ${PATH_ServerDLL}")
ELSE(PATH_ServerDLL)
MESSAGE(STATUS " [FAILED] dll GDSServer")
ENDIF(PATH_ServerDLL)
# Find GDSServer lib
FIND_LIBRARY(LIB_GDSServer GDSServer PATHS ${PATHS_GNEEDaccessLIB} NO_DEFAULT_PATH)
IF(LIB_GDSServer)
MESSAGE(STATUS " [ OK ] lib ${LIB_GDSServer}")
ELSE(LIB_GDSServer)
MESSAGE(STATUS " [FAILED] lib GDSServer")
ENDIF(LIB_GDSServer)
# MESSAGE(STATUS "1, ${PATH_ClientLIB} 2, ${LIB_GDSClientAPI} 3, ${PATH_ServerDLL} 4, ${LIB_GDSServer}")
# Only add the compile/install directive if all necessary components were found
IF(PATH_ClientLIB AND LIB_GDSClientAPI AND PATH_ServerDLL AND LIB_GDSServer)
# Copy the DLL file at install
INSTALL(PROGRAMS "${PATH_ClientLIB}/GDSClientAPI.dll" DESTINATION ${DIST_BINDIR})
INSTALL(PROGRAMS "${PATH_ClientLIB}/gAPI.dll" DESTINATION ${DIST_BINDIR})
INSTALL(PROGRAMS "${PATH_ClientLIB}/Networking.dll" DESTINATION ${DIST_BINDIR})
INSTALL(PROGRAMS "${PATH_ServerDLL}/GDSServer.dll" DESTINATION ${DIST_BINDIR})
INCLUDE_DIRECTORIES(${PATH_GNEEDaccessAPI})
TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${LIB_GDSClientAPI} )
TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${LIB_GDSServer} )
ADD_DEFINITIONS(-DTARGET_HAS_ThirdPartyGNEEDaccessAPI)
SET(OV_ThirdPartyGNEEDaccess "YES")
ENDIF(PATH_ClientLIB AND LIB_GDSClientAPI AND PATH_ServerDLL AND LIB_GDSServer)
ELSE(PATH_GNEEDaccessAPI)
MESSAGE(STATUS " FAILED to find gtec gNEEDaccessAPI (optional driver)")
ENDIF(PATH_GNEEDaccessAPI)
ENDIF(WIN32)
@@ -0,0 +1,26 @@
GET_PROPERTY(OV_PRINTED GLOBAL PROPERTY OV_TRIED_ThirdPartyGtecUnicornCAPI)
IF(WIN32)
FIND_PATH(PATH_UNICORN Unicorn.dll PATHS ${LIST_DEPENDENCIES_PATH} PATH_SUFFIXES sdk_gtec_unicorn NO_DEFAULT_PATH)
IF(PATH_UNICORN)
OV_PRINT(OV_PRINTED " Found Gtec Unicorn device API...")
INCLUDE_DIRECTORIES(${PATH_UNICORN}/)
TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${PATH_UNICORN}/unicorn.lib)
INSTALL(PROGRAMS "${PATH_UNICORN}/Unicorn.dll" DESTINATION ${DIST_BINDIR})
ADD_DEFINITIONS(-DTARGET_HAS_ThirdPartyGtecUnicron)
ELSE(PATH_UNICORN)
OV_PRINT(OV_PRINTED " FAILED to find Gtec Unicorn device API (optional driver)")
ENDIF(PATH_UNICORN)
ENDIF(WIN32)
IF (UNIX)
OV_PRINT(OV_PRINTED " Gtec Unicorn device API (optional driver): No Linux support")
ENDIF(UNIX)
SET_PROPERTY(GLOBAL PROPERTY OV_TRIED_ThirdPartyGtecUnicornCAPI "Yes")
@@ -0,0 +1,80 @@
INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/contrib/common")
SET(ADDITIONAL_PATH "${CMAKE_SOURCE_DIR}/contrib/plugins/server-extensions/external-stimulations/")
INCLUDE_DIRECTORIES(${ADDITIONAL_PATH})
FILE(GLOB_RECURSE ADDITIONAL_SRC_FILES ${ADDITIONAL_PATH}/*.cpp ${ADDITIONAL_PATH}/*.h)
SET(SRC_FILES "${SRC_FILES};${ADDITIONAL_SRC_FILES}")
SET(ADDITIONAL_PATH "${CMAKE_SOURCE_DIR}/contrib/plugins/server-extensions/tcp-tagging/")
INCLUDE_DIRECTORIES(${ADDITIONAL_PATH})
FILE(GLOB ADDITIONAL_SRC_FILES ${ADDITIONAL_PATH}/*.cpp ${ADDITIONAL_PATH}/*.h)
SET(SRC_FILES "${SRC_FILES};${ADDITIONAL_SRC_FILES}")
FUNCTION(OV_ADD_CONTRIB_DRIVER DRIVER_PATH)
SET(ADDITIONAL_PATH ${DRIVER_PATH})
INCLUDE_DIRECTORIES(${ADDITIONAL_PATH}/src)
FILE(GLOB_RECURSE ADDITIONAL_SRC_FILES ${ADDITIONAL_PATH}/src/*.cpp ${ADDITIONAL_PATH}/src/*.h)
SET(SRC_FILES "${SRC_FILES};${ADDITIONAL_SRC_FILES}" PARENT_SCOPE)
#MESSAGE(STATUS "DO I EXIST: ${ADDITIONAL_PATH}/share/")
IF(EXISTS "${ADDITIONAL_PATH}/share/")
#MESSAGE(STATUS "I EXIST: ${ADDITIONAL_PATH}/share/")
INSTALL(DIRECTORY "${ADDITIONAL_PATH}/share/" DESTINATION "${DIST_DATADIR}/openvibe/applications/acquisition-server/")
ENDIF(EXISTS "${ADDITIONAL_PATH}/share/")
#MESSAGE(STATUS "DO I EXIST: ${ADDITIONAL_PATH}/bin/")
IF(EXISTS "${ADDITIONAL_PATH}/bin/")
#MESSAGE(STATUS "I EXIST: ${ADDITIONAL_PATH}/bin/")
INSTALL(DIRECTORY "${ADDITIONAL_PATH}/bin/" DESTINATION "${DIST_BINDIR}")
ENDIF(EXISTS "${ADDITIONAL_PATH}/bin/")
# Add the dir to be parsed for documentation later.
GET_PROPERTY(OV_TMP GLOBAL PROPERTY OV_PROP_CURRENT_PROJECTS)
SET(OV_TMP "${OV_TMP};${ADDITIONAL_PATH}")
SET_PROPERTY(GLOBAL PROPERTY OV_PROP_CURRENT_PROJECTS ${OV_TMP})
ENDFUNCTION(OV_ADD_CONTRIB_DRIVER)
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/contrib/plugins/server-drivers/brainmaster-discovery")
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/contrib/plugins/server-drivers/brainproducts-brainvisionrecorder")
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/contrib/plugins/server-drivers/cognionics")
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/contrib/plugins/server-drivers/ctfvsm-meg")
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/contrib/plugins/server-drivers/encephalan")
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/contrib/plugins/server-drivers/gtec-gipsa/common")
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/contrib/plugins/server-drivers/gtec-gipsa/gusbamp")
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/contrib/plugins/server-drivers/gtec-gipsa/unicorn")
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/contrib/plugins/server-drivers/gtec-bcilab")
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/contrib/plugins/server-drivers/gtec-gmobilabplus")
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/contrib/plugins/server-drivers/gtec-gusbamp")
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/contrib/plugins/server-drivers/gtec-gnautilus")
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/contrib/plugins/server-drivers/mbt-smarting")
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/contrib/plugins/server-drivers/mitsarEEG202A")
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/contrib/plugins/server-drivers/openal-mono16bit-audiocapture")
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/contrib/plugins/server-drivers/openeeg-modulareeg")
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/contrib/plugins/server-drivers/openbci")
IF(WIN32 AND "${PLATFORM_TARGET}" STREQUAL "x64")
MESSAGE(STATUS " SKIPPED fieldtrip on x64")
ELSE()
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/contrib/plugins/server-drivers/field-trip-protocol")
ENDIF()
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/contrib/plugins/server-drivers/eemagine-eego")
# The block is used to compile wrapper.cc into Acquisition Server which is not in OV git.
# nb. we need to add the wrapper.cc file before the cmake add_executable() directive, and at that
# point FindThirdPartyEemagineEEGO has not yet been run on some builds (e.g. win command line build),
# nor can we do the adding at that point; it'd be too late. On the other hand, the find script
# cannot be called before the executable has been added.
if (WIN32)
FIND_PATH(PATH_EEGOAPI amplifier.h PATHS ${LIST_DEPENDENCIES_PATH} PATH_SUFFIXES sdk_eemagine_eego/eemagine/sdk/)
else()
FIND_PATH(PATH_EEGOAPI amplifier.h PATHS /usr/include PATH_SUFFIXES eemagine/sdk/)
endif(WIN32)
IF(PATH_EEGOAPI)
SET(SRC_FILES "${SRC_FILES};${PATH_EEGOAPI}/wrapper.cc")
ENDIF(PATH_EEGOAPI)
IF(OV_COMPILE_TESTS)
ADD_SUBDIRECTORY("../../../contrib/plugins/server-extensions/tcp-tagging/test" "./test")
ENDIF(OV_COMPILE_TESTS)
@@ -0,0 +1,90 @@
#pragma once
/*
#include "openeeg-modulareeg/src/ovasCDriverOpenEEGModularEEG.h"
#include "field-trip-protocol/src/ovasCDriverFieldtrip.h"
#include "brainproducts-brainvisionrecorder/src/ovasCDriverBrainProductsBrainVisionRecorder.h"
*/
#include "ovasCPluginExternalStimulations.h"
#include "ovasCPluginTCPTagging.h"
#include "ovasCDriverBrainmasterDiscovery.h"
#include "ovasCDriverBrainProductsBrainVisionRecorder.h"
#include "ovasCDriverCognionics.h"
#include "ovasCDriverCtfVsmMeg.h"
#include "ovasCDriverEncephalan.h"
#include "CDriverGTecGUSBamp.hpp"
#include "ovasCDriverGTecGUSBampLegacy.h"
#include "ovasCDriverGTecGUSBampLinux.h"
#include "CDriverGTecUnicorn.hpp"
#include "ovasCDriverGTecGMobiLabPlus.h"
#include "ovasCDrivergNautilusInterface.h"
#include "ovasCDriverMBTSmarting.h"
#include "ovasCDriverMitsarEEG202A.h"
#include "ovasCDriverOpenALAudioCapture.h"
#include "ovasCDriverOpenEEGModularEEG.h"
#include "ovasCDriverOpenBCI.h"
#include "ovasCDriverEEGO.h"
#if !defined(WIN32) || defined(TARGET_PLATFORM_i386)
#include "ovasCDriverFieldtrip.h"
#endif
namespace OpenViBE {
namespace Contributions {
inline void InitiateContributions(AcquisitionServer::CAcquisitionServerGUI* pGUI, AcquisitionServer::CAcquisitionServer* pAS,
const Kernel::IKernelContext& context, std::vector<AcquisitionServer::IDriver*>* drivers)
{
//No Limitations
drivers->push_back(new AcquisitionServer::CDriverBrainProductsBrainVisionRecorder(pAS->getDriverContext()));
drivers->push_back(new AcquisitionServer::CDriverCtfVsmMeg(pAS->getDriverContext()));
drivers->push_back(new AcquisitionServer::CDriverMBTSmarting(pAS->getDriverContext()));
drivers->push_back(new AcquisitionServer::CDriverOpenEEGModularEEG(pAS->getDriverContext()));
drivers->push_back(new AcquisitionServer::CDriverOpenBCI(pAS->getDriverContext()));
//OS Limitations
#if defined WIN32
drivers->push_back(new AcquisitionServer::CDriverCognionics(pAS->getDriverContext()));
#endif
#if defined TARGET_OS_Windows
drivers->push_back(new AcquisitionServer::CDriverEncephalan(pAS->getDriverContext()));
#endif
#if defined TARGET_HAS_PThread
#if !defined(WIN32) || defined(TARGET_PLATFORM_i386)
drivers->push_back(new AcquisitionServer::CDriverFieldtrip(pAS->getDriverContext()));
#endif
#endif
//Commercial Limitations
#if defined TARGET_HAS_ThirdPartyGUSBampCAPI
drivers->push_back(new AcquisitionServer::CDriverGTecGUSBamp(pAS->getDriverContext()));
drivers->push_back(new AcquisitionServer::CDriverGTecGUSBampLegacy(pAS->getDriverContext()));
#endif
#if defined TARGET_HAS_ThirdPartyGUSBampCAPI_Linux
drivers->push_back(new AcquisitionServer::CDriverGTecGUSBampLinux(pAS->getDriverContext()));
#endif
#if defined TARGET_HAS_ThirdPartyGtecUnicron
drivers->push_back(new AcquisitionServer::CDriverGTecUnicorn(pAS->getDriverContext()));
#endif
#if defined TARGET_HAS_ThirdPartyGMobiLabPlusAPI
drivers->push_back(new AcquisitionServer::CDriverGTecGMobiLabPlus(pAS->getDriverContext()));
#endif
#if defined TARGET_HAS_ThirdPartyGNEEDaccessAPI
drivers->push_back(new AcquisitionServer::CDrivergNautilusInterface(pAS->getDriverContext()));
#endif
#if defined TARGET_HAS_ThirdPartyBrainmasterCodeMakerAPI
drivers->push_back(new AcquisitionServer::CDriverBrainmasterDiscovery(pAS->getDriverContext()));
#endif
#if defined(TARGET_HAS_ThirdPartyMitsar)
drivers->push_back(new AcquisitionServer::CDriverMitsarEEG202A(pAS->getDriverContext()));
#endif
#if defined TARGET_HAS_ThirdPartyOpenAL
drivers->push_back(new AcquisitionServer::CDriverOpenALAudioCapture(pAS->getDriverContext()));
#endif
#if defined(TARGET_HAS_ThirdPartyEEGOAPI)
drivers->push_back(new AcquisitionServer::CDriverEEGO(pAS->getDriverContext()));
#endif
pGUI->registerPlugin(new AcquisitionServer::Plugins::CPluginExternalStimulations(context));
pGUI->registerPlugin(new AcquisitionServer::Plugins::CPluginTCPTagging(context));
}
} // namespace Contributions
} // namespace OpenViBE
@@ -0,0 +1,9 @@
INCLUDE("FindThirdPartyBrainmasterCodeMakerAPI")
INCLUDE("FindThirdPartyEemagineEEGO")
INCLUDE("FindThirdPartyGMobiLabPlusAPI")
INCLUDE("FindThirdPartyGUSBampCAPI")
INCLUDE("FindThirdPartyMitsar")
INCLUDE("FindThirdPartyGNEEDaccessAPI")
INCLUDE("FindThirdPartyGtecUnicornCAPI")
@@ -0,0 +1,2 @@
# These files can be included by non-contrib projects.
@@ -0,0 +1,31 @@
Copyright (c) 2000-2013 Chih-Chung Chang and Chih-Jen Lin
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither name of copyright holders nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,112 @@
#pragma once
#define LIBSVM_VERSION 324
extern "C" {
extern int libsvm_version;
struct svm_node
{
int index;
double value;
};
struct svm_problem
{
int l;
double* y;
struct svm_node** x;
};
enum { C_SVC, NU_SVC, ONE_CLASS, EPSILON_SVR, NU_SVR }; // svm_type
enum { LINEAR, POLY, RBF, SIGMOID, PRECOMPUTED }; // kernel_type
/* Table help you to transforms Enums to strings */
//extern static const char *svm_type_table[5];
//extern static const char *kernel_type_table[5];
inline const char* get_svm_type(const unsigned int code)
{
static const char* svm_type_table[] = { "c_svc", "nu_svc", "one_class", "epsilon_svr", "nu_svr", nullptr };
return svm_type_table[code];
}
inline const char* get_kernel_type(const unsigned int code)
{
static const char* types[] = { "linear", "polynomial", "rbf", "sigmoid", "precomputed", nullptr };
return types[code];
}
struct svm_parameter
{
int svm_type;
int kernel_type;
int degree; // for poly
double gamma; // for poly/rbf/sigmoid
double coef0; // for poly/sigmoid
// these are for training only
double cache_size; // in MB
double eps; // stopping criteria
double C; // for C_SVC, EPSILON_SVR and NU_SVR
int nr_weight; // for C_SVC
int* weight_label; // for C_SVC
double* weight; // for C_SVC
double nu; // for NU_SVC, ONE_CLASS, and NU_SVR
double p; // for EPSILON_SVR
int shrinking; // use the shrinking heuristics
int probability; // do probability estimates
};
//
// svm_model
//
struct svm_model
{
struct svm_parameter param; // parameter
int nr_class; // number of classes, = 2 in regression/one class svm
int l; // total #SV
struct svm_node** SV; // SVs (SV[l])
double** sv_coef; // coefficients for SVs in decision functions (sv_coef[k-1][l])
double* rho; // constants in decision functions (rho[k*(k-1)/2])
double* probA; // pariwise probability information
double* probB;
int* sv_indices; // sv_indices[0,...,nSV-1] are values in [1,...,num_traning_data] to indicate SVs in the training set
// for classification only
int* label; // label of each class (label[k])
int* nSV; // number of SVs for each class (nSV[k])
// nSV[0] + nSV[1] + ... + nSV[k-1] = l
// XXX
int free_sv; // 1 if svm_model is created by svm_load_model
// 0 if svm_model is created by svm_train
};
struct svm_model* svm_train(const struct svm_problem* prob, const struct svm_parameter* param);
void svm_cross_validation(const struct svm_problem* prob, const struct svm_parameter* param, int nr_fold, double* target);
int svm_save_model(const char* model_file_name, const struct svm_model* model);
struct svm_model* svm_load_model(const char* model_file_name);
int svm_get_svm_type(const struct svm_model* model);
int svm_get_nr_class(const struct svm_model* model);
void svm_get_labels(const struct svm_model* model, int* label);
void svm_get_sv_indices(const struct svm_model* model, int* indices);
int svm_get_nr_sv(const struct svm_model* model);
double svm_get_svr_probability(const struct svm_model* model);
double svm_predict_values(const struct svm_model* model, const struct svm_node* x, double* dec_values);
double svm_predict(const struct svm_model* model, const struct svm_node* x);
double svm_predict_probability(const struct svm_model* model, const struct svm_node* x, double* prob_estimates);
void svm_free_model_content(struct svm_model* model_ptr);
void svm_free_and_destroy_model(struct svm_model** model_ptr_ptr);
void svm_destroy_param(struct svm_parameter* param);
const char* svm_check_parameter(const struct svm_problem* prob, const struct svm_parameter* param);
int svm_check_probability_model(const struct svm_model* model);
void svm_set_print_string_function(void (*print_func)(const char*));
}
@@ -0,0 +1,340 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.
@@ -0,0 +1,18 @@
/*Copyright (C) 2011 Rafat Hussain
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*You should have received a copy of the GNU General Public License
*along with this program; if not, write to the Free Software
*Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
@@ -0,0 +1,22 @@
1. This package contains two wavelet libraries- libwavelet2d.so.1.0 (shared) and libwavelet2s.a (static) compiled essentially from the same source code. Source code is available in the 'src' folder.
2. You may need to link to header files that are included with their resepctive libraries. They are also available in the 'src' folder.
3. You may want to install shared library in one of your existing paths to make compilation easier. You can create sym links once inside the folder( say /usr/local/lib) by using following commands
ln -sf libwavelet2d.so.1.0 libwavelet2d.so
ln -sf libwavelet2d.so.1.0 libwavelet2d.so.1
ldconfig
You will probably need superuser privileges to perform previous steps. If you don't have su privilege then you can move libwavelet2d.so.1.0 to your work folder or where your source code is, create sym links as before and then put your folder in the path during runtime.
ln -sf libwavelet2d.so.1.0 libwavelet2d.so
ln -sf libwavelet2d.so.1.0 libwavelet2d.so.1
export LD_LIBRARY_PATH=.
libwavelet2s.a : Working with static library is pretty straightforward. You will only need to include wavelet2s.h in your program and specify the library (-lwavelet2s flag) , include path (-I<path to wavelet2s.h> flag) and library path (-L<path to libwavelet2s.a> flag).
4. These libraries are licensed under GNU-GPL v2.0 (or any later version). See COPYRIGHT and COPYING files for more information.
5. These libraries statically link to FFTW-3.2.2 static library. More information, fftw libraries and associated files for this version are available at www.fftw.org. I have not modified fftw source codes in any way shape or form so you may want to download copies of source code and other files from the FFTW website itself if you are so inclined. However, you will find FFTW3 licensing and copyright information in the fftw3 folder.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,87 @@
#pragma once
#if defined(TARGET_HAS_ThirdPartyFFTW3)
#include <vector>
#include <complex>
// 1D Functions
// void* dwt(std::vector<double> &, int ,std::string , std::vector<double> &, std::vector<double> &);
void* dwt1(const std::string& wname, std::vector<double>& signal, std::vector<double>& cA, std::vector<double>& cD);
void* dyadic_zpad_1d(std::vector<double>& signal);
double convol(std::vector<double>& a1, std::vector<double>& b1, std::vector<double>& c);
int filtcoef(const std::string& name, std::vector<double>& lp1, std::vector<double>& hp1, std::vector<double>& lp2, std::vector<double>& hp2);
void downsamp(std::vector<double>& sig, int m, std::vector<double>& sigD);
void upsamp(std::vector<double>& sig, int m, std::vector<double>& sigU);
void circshift(std::vector<double>& sigCir, int l);
int sign(int x);
void* idwt1(const std::string& wname, std::vector<double>& X, std::vector<double>& cA, std::vector<double>& cD);
int vecsum(std::vector<double>& a, std::vector<double>& b, std::vector<double>& c);
// 1D Symmetric Extension DWT Functions
void* dwt_sym(std::vector<double>& signal, int J, const std::string& nm, std::vector<double>& dwtOutput, std::vector<double>& flag,
std::vector<size_t>& length);
void* dwt1_sym(const std::string& wname, std::vector<double>& signal, std::vector<double>& cA, std::vector<double>& cD);
void* idwt_sym(std::vector<double>& dwtop, std::vector<double>& flag, const std::string& nm, std::vector<double>& idwtOutput, std::vector<size_t>& length);
void* symm_ext(std::vector<double>& sig, int a);
void* idwt1_sym(const std::string& wname, std::vector<double>& x, std::vector<double>& app, std::vector<double>& detail); // Not Tested
// 1D Stationary Wavelet Transform
void* swt(std::vector<double>& signal1, int J, const std::string& nm, std::vector<double>& swtOutput, int& length);
void* iswt(std::vector<double>& swtop, int J, const std::string& nm, std::vector<double>& iswtOutput);
void* per_ext(std::vector<double>& sig, int a);
// 2D Functions
void* branch_lp_dn(const std::string& wname, std::vector<double>& signal, std::vector<double>& sigop);
void* branch_hp_dn(const std::string& wname, std::vector<double>& signal, std::vector<double>& sigop);
void* branch_lp_hp_up(const std::string& wname, std::vector<double>& cA, std::vector<double>& cD, std::vector<double>& x);
// void* dwt_2d(std::vector<std::vector<double> > &, int , std::string , std::vector<std::vector<double> > &, std::vector<double> &) ;
// void* idwt_2d(std::vector<std::vector<double> > &,std::vector<double> &, std::string ,std::vector<std::vector<double> > &);
void* dyadic_zpad_2d(std::vector<std::vector<double>>& signal, std::vector<std::vector<double>>& mod);
void* dwt_output_dim(std::vector<std::vector<double>>& signal, int& r, int& c);
void* zero_remove(std::vector<std::vector<double>>& input, std::vector<std::vector<double>>& output);
void* getcoeff2d(std::vector<std::vector<double>>& dwtoutput, std::vector<std::vector<double>>& cH, std::vector<std::vector<double>>& cV,
std::vector<std::vector<double>>& cD, std::vector<double>& flag, int& n);
void* idwt2(const std::string& name, std::vector<std::vector<double>>& signal, std::vector<std::vector<double>>& cLL, std::vector<std::vector<double>>& cLH,
std::vector<std::vector<double>>& cHL, std::vector<std::vector<double>>& cHH);
void* dwt2(const std::string& name, std::vector<std::vector<double>>& signal, std::vector<std::vector<double>>& cLL, std::vector<std::vector<double>>& cLH,
std::vector<std::vector<double>>& cHL, std::vector<std::vector<double>>& cHH);
void* downsamp2(std::vector<std::vector<double>>& vec1, std::vector<std::vector<double>>& vec2, int rowsDn, int colsDn);
void* upsamp2(std::vector<std::vector<double>>& vec1, std::vector<std::vector<double>>& vec2, int rowsUp, int colsUp);
// 2D DWT (Symmetric Extension) Functions
void* dwt_2d_sym(std::vector<std::vector<double>>& origsig, int J, const std::string& nm, std::vector<double>& dwtOutput, std::vector<double>& flag,
std::vector<size_t>& length);
void* dwt2_sym(const std::string& name, std::vector<std::vector<double>>& signal, std::vector<std::vector<double>>& cLL, std::vector<std::vector<double>>& cLH,
std::vector<std::vector<double>>& cHL, std::vector<std::vector<double>>& cHH);
void* idwt_2d_sym(std::vector<double>& dwtop, std::vector<double>& flag, const std::string& nm, std::vector<std::vector<double>>& idwtOutput,
std::vector<size_t>& length);
void* circshift2d(std::vector<std::vector<double>>& signal, int x, int y);
void symm_ext2d(std::vector<std::vector<double>>& signal, std::vector<std::vector<double>>& temp2, int a);
void* dispDWT(std::vector<double>& output, std::vector<std::vector<double>>& dwtdisp, std::vector<size_t>& length, std::vector<size_t>& length2, int J);
//2D Stationary Wavelet Transform
void* swt_2d(std::vector<std::vector<double>>& sig, int J, const std::string& nm, std::vector<double>& swtOutput);
void* per_ext2d(std::vector<std::vector<double>>& signal, std::vector<std::vector<double>>& temp2, int a);
// FFT functions
double convfft(std::vector<double>& a, std::vector<double>& b, std::vector<double>& c);
double convfftm(std::vector<double>& a, std::vector<double>& b, std::vector<double>& c);
void* fft(std::vector<std::complex<double>>& data, int sign, size_t n);
void* bitreverse(std::vector<std::complex<double>>& sig);
void* freq(std::vector<double>& sig, std::vector<double>& freqResp);
//New
void* dwt1_sym_m(const std::string& wname, std::vector<double>& signal, std::vector<double>& cA, std::vector<double>& cD);//FFTW3 for 2D
void* idwt1_sym_m(const std::string& wname, std::vector<double>& x, std::vector<double>& app, std::vector<double>& detail);
void* dwt(std::vector<double>& sig, int j, const std::string& nm, std::vector<double>& dwtOutput, std::vector<double>& flag, std::vector<size_t>& length);
void* idwt(std::vector<double>& dwtop, std::vector<double>& flag, const std::string& nm, std::vector<double>& idwtOutput, std::vector<size_t>& length);
void* dwt_2d(std::vector<std::vector<double>>& origsig, int J, const std::string& nm, std::vector<double>& dwtOutput, std::vector<double>& flag,
std::vector<size_t>& length);
void* dwt1_m(const std::string& wname, std::vector<double>& signal, std::vector<double>& cA, std::vector<double>& cD);
void* idwt_2d(std::vector<double>& dwtop, std::vector<double>& flag, const std::string& nm, std::vector<std::vector<double>>& idwtOutput,
std::vector<size_t>& length);
void* idwt1_m(const std::string& wname, std::vector<double>& x, std::vector<double>& cA, std::vector<double>& cD);
void* dwt_output_dim2(std::vector<size_t>& length, std::vector<size_t>& length2, int j);
#endif
@@ -0,0 +1,4 @@
# Add all the subdirs as projects of the named branch
OV_ADD_PROJECTS("CONTRIB_PLUGINS")
@@ -0,0 +1,4 @@
# Add all the subdirs as projects of the named branch
OV_ADD_PROJECTS("CONTRIB_PLUGINS_PROCESSING")
@@ -0,0 +1,3 @@
# fill this once there is a contribution to this category...
@@ -0,0 +1,15 @@
#pragma once
//___________________________________________________________________//
// //
// Global defines //
//___________________________________________________________________//
// //
#ifdef TARGET_HAS_ThirdPartyOpenViBEPluginsGlobalDefines
#include "ovp_global_defines.h"
#endif // TARGET_HAS_ThirdPartyOpenViBEPluginsGlobalDefines
@@ -0,0 +1,10 @@
#include <vector>
#include <openvibe/ov_all.h>
#include "ovp_defines.h"
OVP_Declare_Begin();
// For the moment this plugin is empty
OVP_Declare_End()
@@ -0,0 +1,27 @@
PROJECT(openvibe-plugins-contrib-file-io)
SET(PROJECT_VERSION_MAJOR ${OV_GLOBAL_VERSION_MAJOR})
SET(PROJECT_VERSION ${OV_GLOBAL_VERSION_STRING})
FILE(GLOB_RECURSE SRC_FILES src/*.cpp src/*.h src/*.inl src/*.c)
ADD_LIBRARY(${PROJECT_NAME} SHARED ${SRC_FILES})
SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
FOLDER ${PLUGINS_FOLDER}
COMPILE_FLAGS "-DOVP_Exports -DOVP_Shared -D_LARGEFILE64_SOURCE -D_LARGEFILE_SOURCE")
# ---------------------------------
INCLUDE("FindOpenViBE")
INCLUDE("FindOpenViBECommon")
INCLUDE("FindOpenViBEToolkit")
INCLUDE("FindOpenViBEModuleEBML")
INCLUDE("FindThirdPartyBoost")
# -----------------------------
# Install files
# -----------------------------
INSTALL(TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION ${DIST_BINDIR}
LIBRARY DESTINATION ${DIST_LIBDIR}
ARCHIVE DESTINATION ${DIST_LIBDIR})
@@ -0,0 +1,59 @@
/**
* \page BoxAlgorithm_EDFFileWriter EDF File Writer
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_EDFFileWriter_Description|
* Writes EEG data in European Data Format (EDF).
* |OVP_DocEnd_BoxAlgorithm_EDFFileWriter_Description|
__________________________________________________________________
Inputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_EDFFileWriter_Inputs|
* |OVP_DocEnd_BoxAlgorithm_EDFFileWriter_Inputs|
* |OVP_DocBegin_BoxAlgorithm_EDFFileWriter_Input1|
* Experiment Information stream, coming from an acquisition client/server.
* |OVP_DocEnd_BoxAlgorithm_EDFFileWriter_Input1|
* |OVP_DocBegin_BoxAlgorithm_EDFFileWriter_Input2|
* EEG signal.
* |OVP_DocEnd_BoxAlgorithm_EDFFileWriter_Input2|
* |OVP_DocBegin_BoxAlgorithm_EDFFileWriter_Input3|
* Stimulation stream.
* |OVP_DocEnd_BoxAlgorithm_EDFFileWriter_Input3|
Settings description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_EDFFileWriter_Settings|
* |OVP_DocEnd_BoxAlgorithm_EDFFileWriter_Settings|
* |OVP_DocBegin_BoxAlgorithm_EDFFileWriter_Setting1|
* The file name to be written.
* |OVP_DocEnd_BoxAlgorithm_EDFFileWriter_Setting1|
__________________________________________________________________
Examples description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_EDFFileWriter_Examples|
* |OVP_DocEnd_BoxAlgorithm_EDFFileWriter_Examples|
__________________________________________________________________
Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_EDFFileWriter_Miscellaneous|
EDF format can be read by several programs, (e.g EDF browser).
This box is based on the free software EDFlib by Teunis van Beelen.
* |OVP_DocEnd_BoxAlgorithm_EDFFileWriter_Miscellaneous|
*/
@@ -0,0 +1,535 @@
/*
*****************************************************************************
*
* Copyright (c) 2009, 2010, 2011 Teunis van Beelen
* All rights reserved.
*
* email: teuniz@gmail.com
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY Teunis van Beelen ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL Teunis van Beelen BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*****************************************************************************
*/
/* compile with options "-D_LARGEFILE64_SOURCE -D_LARGEFILE_SOURCE" */
#ifndef EDFLIB_INCLUDED
#define EDFLIB_INCLUDED
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define EDFLIB_TIME_DIMENSION (10000000LL)
#define EDFLIB_MAXSIGNALS 256
#define EDFLIB_MAX_ANNOTATION_LEN 512
#define EDFSEEK_SET 0
#define EDFSEEK_CUR 1
#define EDFSEEK_END 2
/* the following defines are used in the member "filetype" of the edf_hdr_struct */
/* and as return value for the function edfopen_file_readonly() */
#define EDFLIB_FILETYPE_EDF 0
#define EDFLIB_FILETYPE_EDFPLUS 1
#define EDFLIB_FILETYPE_BDF 2
#define EDFLIB_FILETYPE_BDFPLUS 3
#define EDFLIB_MALLOC_ERROR -1
#define EDFLIB_NO_SUCH_FILE_OR_DIRECTORY -2
#define EDFLIB_FILE_CONTAINS_FORMAT_ERRORS -3
#define EDFLIB_MAXFILES_REACHED -4
#define EDFLIB_FILE_READ_ERROR -5
#define EDFLIB_FILE_ALREADY_OPENED -6
#define EDFLIB_FILETYPE_ERROR -7
#define EDFLIB_FILE_WRITE_ERROR -8
#define EDFLIB_NUMBER_OF_SIGNALS_INVALID -9
#define EDFLIB_FILE_IS_DISCONTINUOUS -10
#define EDFLIB_INVALID_READ_ANNOTS_VALUE -11
/* values for annotations */
#define EDFLIB_DO_NOT_READ_ANNOTATIONS 0
#define EDFLIB_READ_ANNOTATIONS 1
#define EDFLIB_READ_ALL_ANNOTATIONS 2
/* the following defines are possible errors returned by edfopen_file_writeonly() */
#define EDFLIB_NO_SIGNALS -20
#define EDFLIB_TOO_MANY_SIGNALS -21
#define EDFLIB_NO_SAMPLES_IN_RECORD -22
#define EDFLIB_DIGMIN_IS_DIGMAX -23
#define EDFLIB_DIGMAX_LOWER_THAN_DIGMIN -24
#define EDFLIB_PHYSMIN_IS_PHYSMAX -25
#ifdef __cplusplus
extern "C" {
#endif
struct edf_param_struct // this structure contains all the relevant EDF-signal parameters of one signal
{
char label[17]; // label (name) of the signal, null-terminated string
long long smp_in_file; // number of samples of this signal in the file
double phys_max; // physical maximum
double phys_min; // physical minimum
int dig_max; // digital maximum
int dig_min; // digital minimum
int smp_in_datarecord; // number of samples of this signal in a datarecord
char physdimension[9]; // physical dimension (uV, bpm, mA, etc.), null-terminated string
char prefilter[81]; // null-terminated string
char transducer[81]; // null-terminated string
};
struct edf_annotation_struct // this structure is used for annotations
{
long long onset; // onset time of the event, expressed in units of 100 nanoSeconds
char duration[16]; // duration time, this is a null-terminated ASCII text-string
char annotation[EDFLIB_MAX_ANNOTATION_LEN + 1]; // description of the event in UTF-8, this is a null terminated string
};
struct edf_hdr_struct // this structure contains all the relevant EDF header info and will be filled when calling the function edf_open_file_readonly()
{
int handle; // a handle (identifier) used to distinguish the different files
int filetype; // 0: EDF, 1: EDFplus, 2: BDF, 3: BDFplus, a negative number means an error
int edfsignals; // number of EDF signals in the file, annotation channels are NOT included
long long file_duration; // duration of the file expressed in units of 100 nanoSeconds
int startdate_day;
int startdate_month;
int startdate_year;
long long
starttime_subsecond; // starttime offset expressed in units of 100 nanoSeconds. Is always less than 10000000 (one second). Only used by EDFplus and BDFplus
int starttime_second;
int starttime_minute;
int starttime_hour;
char patient[81]; // null-terminated string, contains patientfield of header, is always empty when filetype is EDFPLUS or BDFPLUS
char recording[81
]; // null-terminated string, contains recordingfield of header, is always empty when filetype is EDFPLUS or BDFPLUS
char patientcode[81]; // null-terminated string, is always empty when filetype is EDF or BDF
char gender[16]; // null-terminated string, is always empty when filetype is EDF or BDF
char birthdate[16]; // null-terminated string, is always empty when filetype is EDF or BDF
char patient_name[81]; // null-terminated string, is always empty when filetype is EDF or BDF
char patient_additional[81]; // null-terminated string, is always empty when filetype is EDF or BDF
char admincode[81]; // null-terminated string, is always empty when filetype is EDF or BDF
char technician[81]; // null-terminated string, is always empty when filetype is EDF or BDF
char equipment[81]; // null-terminated string, is always empty when filetype is EDF or BDF
char recording_additional[81]; // null-terminated string, is always empty when filetype is EDF or BDF
long long datarecord_duration; // duration of a datarecord expressed in units of 100 nanoSeconds
long long datarecords_in_file; // number of datarecords in the file
long long annotations_in_file; // number of annotations in the file
struct edf_param_struct signalparam[EDFLIB_MAXSIGNALS]; // array of structs which contain the relevant signal parameters
};
int edflib_version();
/* Returns the version number of this library, multiplied by hundred. if version is "1.00" than it will return 100 */
/***************** the following functions are used to read files **************************/
int EdfopenFileReadonly(const char* path, struct edf_hdr_struct* edfhdr, const int readAnnotations);
/* opens an existing file for reading */
/* path is a null-terminated string containing the path to the file */
/* hdr is a pointer to an edf_hdr_struct, all fields in this struct will be overwritten */
/* the edf_hdr_struct will be filled with all the relevant header- and signalinfo/parameters */
/* read_annotations must have one of the following values: */
/* EDFLIB_DO_NOT_READ_ANNOTATIONS annotations will not be read (this saves time when opening a very large EDFplus or BDFplus file */
/* EDFLIB_READ_ANNOTATIONS annotations will be read immediately, stops when an annotation has */
/* been found which contains the description "Recording ends" */
/* EDFLIB_READ_ALL_ANNOTATIONS all annotations will be read immediately */
/* returns 0 on success, in case of an error it returns -1 and an errorcode will be set in the member "filetype" of struct edf_hdr_struct */
/* This function is required if you want to read a file */
int EdfcloseFile(const int handle);
/* closes and finalizes the file */
/* returns -1 in case of an error, 0 on success */
/* this function MUST be called when you are finished reading or writing */
/* This function is required after reading or writing. Failing to do so will cause */
/* unnessecary memory usage and in case of writing it will cause a corrupted and incomplete file */
int EdfreadPhysicalSamples(const int handle, const int edfsignal, int n, double* buf);
/* reads n samples from edfsignal, starting from the current sample position indicator, into buf (edfsignal starts at 0) */
/* the values are converted to their physical values e.g. microVolts, beats per minute, etc. */
/* bufsize should be equal to or bigger than sizeof(double[n]) */
/* the sample position indicator will be increased with the amount of samples read */
/* returns the amount of samples read (this can be less than n or zero!) */
/* or -1 in case of an error */
int EdfreadDigitalSamples(const int handle, const int edfsignal, int n, int* buf);
/* reads n samples from edfsignal, starting from the current sample position indicator, into buf (edfsignal starts at 0) */
/* the values are the "raw" digital values */
/* bufsize should be equal to or bigger than sizeof(int[n]) */
/* the sample position indicator will be increased with the amount of samples read */
/* returns the amount of samples read (this can be less than n or zero!) */
/* or -1 in case of an error */
long long edfseek(const int handle, const int edfsignal, const long long offset, const int whence);
/* The edfseek() function sets the sample position indicator for the edfsignal pointed to by edfsignal. */
/* The new position, measured in samples, is obtained by adding offset samples to the position specified by whence. */
/* If whence is set to EDFSEEK_SET, EDFSEEK_CUR, or EDFSEEK_END, the offset is relative to the start of the file, */
/* the current position indicator, or end-of-file, respectively. */
/* Returns the current offset. Otherwise, -1 is returned. */
/* note that every signal has it's own independent sample position indicator and edfseek() affects only one of them */
long long edftell(const int handle, const int edfsignal);
/* The edftell() function obtains the current value of the sample position indicator for the edfsignal pointed to by edfsignal. */
/* Returns the current offset. Otherwise, -1 is returned */
/* note that every signal has it's own independent sample position indicator and edftell() affects only one of them */
void edfrewind(const int handle, const int edfsignal);
/* The edfrewind() function sets the sample position indicator for the edfsignal pointed to by edfsignal to the beginning of the file. */
/* It is equivalent to: () edfseek(int handle, int edfsignal, 0LL, EDFSEEK_SET) */
/* note that every signal has it's own independent sample position indicator and edfrewind() affects only one of them */
int EdfGetAnnotation(const int handle, const int n, struct edf_annotation_struct* annot);
/* Fills the edf_annotation_struct with the annotation n, returns 0 on success, otherwise -1 */
/* To obtain the number of annotations in a file, check edf_hdr_struct -> annotations_in_file */
/*
Notes:
Annotationsignals
EDFplus and BDFplus store the annotations in one or more signals (in order to be backwards compatibel with EDF and BDF).
The counting of the signals in the file starts at 0. Signals used for annotations are skipped by EDFlib.
This means that the annotationsignal(s) in the file are hided.
Use the function edf_get_annotation() to get the annotations.
So, when a file contains 5 signals and the third signal is used to store the annotations, the library will
report that there are only 4 signals in the file.
The library will "map" the signalnumbers as follows: 0->0, 1->1, 2->3, 3->4.
This way you don't need to worry about which signals are annotationsignals. The library will do it for you.
How the library stores time-values
To avoid rounding errors, the library stores some timevalues in variables of type long long int.
In order not to loose the subsecond precision, all timevalues have been multiplied by 10000000.
This will limit the timeresolution to 100 nanoSeconds. To calculate the amount of seconds, divide
the timevalue by 10000000 or use the macro EDFLIB_TIME_DIMENSION which is declared in edflib.h.
The following variables do use this when you open a file in read mode: "file_duration", "starttime_subsecond" and "onset".
*/
/***************** the following functions are used to write files **************************/
int EdfopenFileWriteonly(const char* path, const int filetype, const int nSignals);
/* opens an new file for writing. warning, an already existing file with the same name will be silently overwritten without advance warning!! */
/* path is a null-terminated string containing the path and name of the file */
/* filetype must be EDFLIB_FILETYPE_EDFPLUS or EDFLIB_FILETYPE_BDFPLUS */
/* returns a handle on success, you need this handle for the other functions */
/* in case of an error it returns a negative number corresponding to one of the following values: */
/* EDFLIB_MALLOC_ERROR */
/* EDFLIB_NO_SUCH_FILE_OR_DIRECTORY */
/* EDFLIB_MAXFILES_REACHED */
/* EDFLIB_FILE_ALREADY_OPENED */
/* EDFLIB_NUMBER_OF_SIGNALS_INVALID */
/* This function is required if you want to write a file */
int EdfSetSampling(const int handle, const int edfsignal, const int sampling);
/* Sets the sampling of signal edfsignal. */
/* Returns 0 on success, otherwise -1 */
/* This function is required for every signal and can be called only after opening a */
/* file in writemode and before the first sample write action */
int EdfSetPhysicalMaximum(const int handle, const int edfsignal, const double max);
/* Sets the maximum physical value of signal edfsignal. */
/* Returns 0 on success, otherwise -1 */
/* This function is required for every signal and can be called only after opening a */
/* file in writemode and before the first sample write action */
int EdfSetPhysicalMinimum(const int handle, const int edfsignal, const double min);
/* Sets the minimum physical value of signal edfsignal. */
/* Usually this will be (-(phys_max)) */
/* Returns 0 on success, otherwise -1 */
/* This function is required for every signal and can be called only after opening a */
/* file in writemode and before the first sample write action */
int EdfSetDigitalMaximum(const int handle, const int edfsignal, const int max);
/* Sets the maximum digital value of signal edfsignal. Usually, the value 32767 is used for EDF+ and 8388607 for BDF+ */
/* Returns 0 on success, otherwise -1 */
/* This function is required for every signal and can be called only after opening a file in writemode */
/* and before the first sample write action */
int EdfSetDigitalMinimum(const int handle, const int edfsignal, const int min);
/* Sets the minimum digital value of signal edfsignal. Usually, the value -32768 is used for EDF+ and -8388608 for BDF+ */
/* Usually this will be (-(dig_max + 1)) */
/* Returns 0 on success, otherwise -1 */
/* This function is required for every signal and can be called only after opening a file in writemode */
/* and before the first sample write action */
int EdfSetLabel(const int handle, const int edfsignal, const char* label);
/* Sets the label (name) of signal edfsignal. ("FP1", "SaO2", etc.) */
/* label is a pointer to a NULL-terminated ASCII-string containing the label (name) of the signal edfsignal */
/* Returns 0 on success, otherwise -1 */
/* This function is recommended for every signal when you want to write a file */
/* and can be called only after opening a file in writemode and before the first sample write action */
int EdfSetPrefilter(const int handle, const int edfsignal, const char* prefilter);
/* Sets the prefilter of signal edfsignal ("HP:0.1Hz", "LP:75Hz N:50Hz", etc.). */
/* prefilter is a pointer to a NULL-terminated ASCII-string containing the prefilter text of the signal edfsignal */
/* Returns 0 on success, otherwise -1 */
/* This function is optional and can be called only after opening a file in writemode and before */
/* the first sample write action */
int EdfSetTransducer(const int handle, const int edfsignal, const char* transducer);
/* Sets the transducer of signal edfsignal ("AgAgCl cup electrodes", etc.). */
/* transducer is a pointer to a NULL-terminated ASCII-string containing the transducer text of the signal edfsignal */
/* Returns 0 on success, otherwise -1 */
/* This function is optional and can be called only after opening a file in writemode and before */
/* the first sample write action */
int EdfSetPhysicalDimension(const int handle, const int edfsignal, const char* dim);
/* Sets the physical dimension of signal edfsignal. ("uV", "BPM", "mA", "Degr.", etc.) */
/* phys_dim is a pointer to a NULL-terminated ASCII-string containing the physical dimension of the signal edfsignal */
/* Returns 0 on success, otherwise -1 */
/* This function is recommended for every signal when you want to write a file */
/* and can be called only after opening a file in writemode and before the first sample write action */
int EdfSetStartdatetime(const int handle, const int year, const int month, const int day, const int hour, const int minute, const int second);
/* Sets the startdate and starttime. */
/* year: 1970 - 3000, month: 1 - 12, day: 1 - 31 */
/* hour: 0 - 23, minute: 0 - 59, second: 0 - 59 */
/* If not called, the library will use the system date and time at runtime */
/* Returns 0 on success, otherwise -1 */
/* This function is optional and can be called only after opening a file in writemode */
/* and before the first sample write action */
int EdfSetPatientname(const int handle, const char* patientname);
/* Sets the patientname. patientname is a pointer to a null-terminated ASCII-string. */
/* Returns 0 on success, otherwise -1 */
/* This function is optional and can be called only after opening a file in writemode */
/* and before the first sample write action */
int EdfSetPatientcode(const int handle, const char* patientcode);
/* Sets the patientcode. patientcode is a pointer to a null-terminated ASCII-string. */
/* Returns 0 on success, otherwise -1 */
/* This function is optional and can be called only after opening a file in writemode */
/* and before the first sample write action */
int EdfSetGender(const int handle, const int gender);
/* Sets the gender. 1 is male, 0 is female. */
/* Returns 0 on success, otherwise -1 */
/* This function is optional and can be called only after opening a file in writemode */
/* and before the first sample write action */
int EdfSetBirthdate(const int handle, const int year, const int month, const int day);
/* Sets the birthdate. */
/* year: 1800 - 3000, month: 1 - 12, day: 1 - 31 */
/* This function is optional */
/* Returns 0 on success, otherwise -1 */
/* This function is optional and can be called only after opening a file in writemode */
/* and before the first sample write action */
int EdfSetPatientAdditional(const int handle, const char* additional);
/* Sets the additional patientinfo. patient_additional is a pointer to a null-terminated ASCII-string. */
/* Returns 0 on success, otherwise -1 */
/* This function is optional and can be called only after opening a file in writemode */
/* and before the first sample write action */
int EdfSetAdmincode(const int handle, const char* admincode);
/* Sets the admincode. admincode is a pointer to a null-terminated ASCII-string. */
/* Returns 0 on success, otherwise -1 */
/* This function is optional and can be called only after opening a file in writemode */
/* and before the first sample write action */
int EdfSetTechnician(const int handle, const char* technician);
/* Sets the technicians name. technician is a pointer to a null-terminated ASCII-string. */
/* Returns 0 on success, otherwise -1 */
/* This function is optional and can be called only after opening a file in writemode */
/* and before the first sample write action */
int EdfSetEquipment(const int handle, const char* equipment);
/* Sets the name of the equipment used during the aquisition. equipment is a pointer to a null-terminated ASCII-string. */
/* Returns 0 on success, otherwise -1 */
/* This function is optional and can be called only after opening a file in writemode */
/* and before the first sample write action */
int EdfSetRecordingAdditional(const int handle, const char* additional);
/* Sets the additional recordinginfo. recording_additional is a pointer to a null-terminated ASCII-string. */
/* Returns 0 on success, otherwise -1 */
/* This function is optional and can be called only after opening a file in writemode */
/* and before the first sample write action */
int EdfwritePhysicalSamples(const int handle, const double* buf);
/* Writes n physical samples (uV, mA, Ohm) from *buf belonging to one signal */
/* where n is the sampling of the signal. */
/* The physical samples will be converted to digital samples using the */
/* values of physical maximum, physical minimum, digital maximum and digital minimum */
/* The number of samples written is equal to the sampling of the signal */
/* Size of buf should be equal to or bigger than sizeof(double[sampling]) */
/* Call this function for every signal in the file. The order is important! */
/* When there are 4 signals in the file, the order of calling this function */
/* must be: signal 0, signal 1, signal 2, signal 3, signal 0, signal 1, signal 2, etc. */
/* Returns 0 on success, otherwise -1 */
int EdfBlockwritePhysicalSamples(const int handle, const double* buf);
/* Writes physical samples (uV, mA, Ohm) from *buf */
/* buf must be filled with samples from all signals, starting with n samples of signal 0, n samples of signal 1, n samples of signal 2, etc. */
/* where n is the sampling of the signal. */
/* buf must be filled with samples from all signals, starting with signal 0, 1, 2, etc. */
/* one block equals one second */
/* The physical samples will be converted to digital samples using the */
/* values of physical maximum, physical minimum, digital maximum and digital minimum */
/* The number of samples written is equal to the sum of the samplefrequencies of all signals */
/* Size of buf should be equal to or bigger than sizeof(double) multiplied by the sum of the samplefrequencies of all signals */
/* Returns 0 on success, otherwise -1 */
int EdfwriteDigitalSamples(const int handle, const int* buf);
/* Writes n "raw" digital samples from *buf belonging to one signal */
/* where n is the sampling of the signal. */
/* The 16 (or 24 in case of BDF) least significant bits of the sample will be written to the */
/* file without any conversion. */
/* The number of samples written is equal to the sampling of the signal */
/* Size of buf should be equal to or bigger than sizeof(int[sampling]) */
/* Call this function for every signal in the file. The order is important! */
/* When there are 4 signals in the file, the order of calling this function */
/* must be: signal 0, signal 1, signal 2, signal 3, signal 0, signal 1, signal 2, etc. */
/* Returns 0 on success, otherwise -1 */
int EdfBlockwriteDigitalSamples(const int handle, const int* buf);
/* Writes "raw" digital samples from *buf. */
/* buf must be filled with samples from all signals, starting with n samples of signal 0, n samples of signal 1, n samples of signal 2, etc. */
/* where n is the sampling of the signal. */
/* One block equals one second. */
/* The 16 (or 24 in case of BDF) least significant bits of the sample will be written to the */
/* file without any conversion. */
/* The number of samples written is equal to the sum of the samplefrequencies of all signals. */
/* Size of buf should be equal to or bigger than sizeof(int) multiplied by the sum of the samplefrequencies of all signals */
/* Returns 0 on success, otherwise -1 */
int EdfwriteAnnotationUTF8(const int handle, const long long onset, const long long duration, const char* description);
/* writes an annotation/event to the file */
/* onset is relative to the starttime and startdate of the file */
/* onset and duration are in units of 100 microSeconds! resolution is 0.0001 second! */
/* for example: 34.071 seconds must be written as 340710 */
/* if duration is unknown or not applicable: set a negative number (-1) */
/* description is a null-terminated UTF8-string containing the text that describes the event */
/* This function is optional and can be called only after opening a file in writemode */
/* and before closing the file */
int EdfwriteAnnotationLatin1(const int handle, const long long onset, const long long duration, const char* description);
/* writes an annotation/event to the file */
/* onset is relative to the starttime and startdate of the file */
/* onset and duration are in units of 100 microSeconds! resolution is 0.0001 second! */
/* for example: 34.071 seconds must be written as 340710 */
/* if duration is unknown or not applicable: set a negative number (-1) */
/* description is a null-terminated Latin1-string containing the text that describes the event */
/* This function is optional and can be called only after opening a file in writemode */
/* and before closing the file */
int EdfSetDatarecordDuration(const int handle, const int duration);
/* Sets the datarecord duration. The default value is 1 second. */
/* ATTENTION: the argument "duration" is expressed in units of 10 microSeconds! */
/* So, if you want to set the datarecord duration to 0.1 second, you must give */
/* the argument "duration" a value of "10000". */
/* This function is optional, normally you don't need to change the default value. */
/* The datarecord duration must be in the range 0.025 to 20.0 seconds. */
/* Returns 0 on success, otherwise -1 */
/* This function is NOT REQUIRED but can be called after opening a */
/* file in writemode and before the first sample write action. */
/* This function can be used when you want to use a samplerate */
/* which is not an integer. For example, if you want to use a samplerate of 0.5 Hz, */
/* set the sampling to 5 Hz and the datarecord duration to 10 seconds, */
/* or set the sampling to 1 Hz and the datarecord duration to 2 seconds. */
/* Do not use this function, except when absolutely necessary! */
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif
@@ -0,0 +1,315 @@
/* Project: Gipsa-lab plugins for OpenVibe
* AUTHORS AND CONTRIBUTORS: Andreev A., Barachant A., Congedo M., Ionescu,Gelu,
* This file is part of "Gipsa-lab plugins for OpenVibe".
* You can redistribute it and/or modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* This file is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along with Brain Invaders. If not, see http://www.gnu.org/licenses/.
*/
#include "ovpCBoxAlgorithmBrainampFileWriterGipsa.h"
#include <string>
#include <iostream>
#include <sstream>
namespace OpenViBE {
namespace Plugins {
namespace FileIO {
//documentation Appendix B EEG file format: http://tsgdoc.socsci.ru.nl/images/d/d1/BrainVision_Recorder_UM.pdf
bool CBoxAlgorithmBrainampFileWriterGipsa::initialize()
{
m_isVmrkHeaderFileWritten = false;
//init input signal 1
m_streamDecoder = new Toolkit::TSignalDecoder<CBoxAlgorithmBrainampFileWriterGipsa>(*this, 0);
//init input stimulation 1
m_stimDecoder = new Toolkit::TStimulationDecoder<CBoxAlgorithmBrainampFileWriterGipsa>(*this, 1);
//Get parameters:
const CString filename = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
const CString tmp = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1);
const CIdentifier binaryFormatID = this->getTypeManager().getEnumerationEntryValueFromName(OVP_TypeId_BinaryFormat, tmp);
if (binaryFormatID == OVP_TypeId_BinaryFormat_int16_t) { m_binaryFormat = BinaryFormat_Integer16; }
else if (binaryFormatID == OVP_TypeId_BinaryFormat_uint16_t) { m_binaryFormat = BinaryFormat_UnsignedInteger16; }
else if (binaryFormatID == OVP_TypeId_BinaryFormat_float) { m_binaryFormat = BinaryFormat_Float32; }
else
{
this->getLogManager() << Kernel::LogLevel_Error << "Unknown binary format: " << binaryFormatID << "\n";
return false;
}
//Perform checks:
if (std::string(filename).empty())
{
this->getLogManager() << Kernel::LogLevel_Error << "Header file path is empty!\n";
return false;
}
if (std::string(filename).substr(filename.length() - 5, 5) != std::string(".vhdr"))
{
this->getLogManager() << Kernel::LogLevel_Warning << "The supplied output file does not end with .vhdr\n";
}
m_headerFilename = filename;
m_headerFile.open(m_headerFilename.c_str());
if (!m_headerFile.good())
{
this->getLogManager() << Kernel::LogLevel_Error << "Could not open header file [" << m_headerFilename << "]\n";
return false;
}
//this->getLogManager() << Kernel::LogLevel_ImportantWarning << m_sHeaderFilename << "\n";
m_dataFilename = m_headerFilename.substr(0, m_headerFilename.length() - 5) + std::string(".eeg");
m_dataFile.open(m_dataFilename.c_str(), std::ios::binary);
if (!m_dataFile.good())
{
this->getLogManager() << Kernel::LogLevel_Error << "Could not open data file [" << m_dataFilename << "]\n";
return false;
}
m_markerFilename = m_headerFilename.substr(0, m_headerFilename.length() - 5) + std::string(".vmrk");
m_markerFile.open(m_markerFilename.c_str());
if (!m_markerFile.good())
{
this->getLogManager() << Kernel::LogLevel_Error << "Could not open marker file [" << m_markerFilename << "]\n";
return false;
}
return true;
}
bool CBoxAlgorithmBrainampFileWriterGipsa::uninitialize()
{
if (m_streamDecoder)
{
m_streamDecoder->uninitialize();
delete m_streamDecoder;
}
// uninit input stimulation
if (m_stimDecoder)
{
m_stimDecoder->uninitialize();
delete m_stimDecoder;
}
//close files
m_headerFile.flush();
m_headerFile.close();
m_dataFile.flush();
m_dataFile.close();
m_markerFile.flush();
m_markerFile.close();
return true;
}
bool CBoxAlgorithmBrainampFileWriterGipsa::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmBrainampFileWriterGipsa::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
//1. Process signal
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i) //first input channel data
{
m_streamDecoder->decode(i);
//HEADER
if (m_streamDecoder->isHeaderReceived())
{
m_matrix = m_streamDecoder->getOutputMatrix();
m_sampling = m_streamDecoder->getOutputSamplingRate();
writeHeaderFile();
}
//BUFFER
if (m_streamDecoder->isBufferReceived())
{
// size_t channelCount = m_Matrix->getDimensionSize(0);
switch (m_binaryFormat)
{
case BinaryFormat_Integer16:
saveBuffer(int16_t(0));
break;
case BinaryFormat_UnsignedInteger16:
saveBuffer(uint16_t(0));
break;
case BinaryFormat_Float32:
saveBuffer(float(0));
break;
default: break;
}
}
//END
if (m_streamDecoder->isEndReceived()) { }
boxContext.markInputAsDeprecated(0, i);
}
// Make sure the marker file header is written in any case
if (!m_isVmrkHeaderFileWritten)
{
const boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
const std::string formated(formatTime(now));
m_markerFile << "Brain Vision Data Exchange Marker File, Version 1.0" << std::endl << std::endl
<< "[Common Infos]" << std::endl
<< "Codepage=ANSI" << std::endl
<< "DataFile=" << getShortName(m_dataFilename) << std::endl << std::endl
<< "[Marker Infos]" << std::endl
<< "; Each entry: Mk<Marker number>=<Type>,<Description>,<Position in data points>," << std::endl
<< "; <Size in data points>, <Channel number (0 = marker is related to all channels)>" << std::endl
<< "; Fields are delimited by commas, some fields might be omitted (empty)." << std::endl
<< "; Commas in type or description text are coded as \"\\1\"." << std::endl
<< "Mk1=New Segment,,1,1,0," << formated << "000000" << std::endl;
m_isVmrkHeaderFileWritten = true;
}
//2. Process stimulations - input channel 1
for (size_t i = 0; i < boxContext.getInputChunkCount(1); ++i)
{
// uint64_t chunkStartTime =boxContext.getInputChunkStartTime(0, i);
m_stimDecoder->decode(i);
//buffer
if (m_stimDecoder->isBufferReceived())
{
IStimulationSet* stimSet = m_stimDecoder->getOutputStimulationSet();
// Loop on stimulations
for (size_t j = 0; j < stimSet->getStimulationCount(); ++j)
{
const uint64_t code = stimSet->getStimulationIdentifier(j);
const uint64_t position = CTime(stimSet->getStimulationDate(j)).toSampleCount(m_sampling) + 1;
m_markerFile << "Mk" << m_stimulationCounter << "=Stimulus," << "S" << std::right << std::setw(3) << code << "," << position << ",1,0" << std::
endl;
m_stimulationCounter++;
}
}
boxContext.markInputAsDeprecated(1, i);
}
return true;
}
bool CBoxAlgorithmBrainampFileWriterGipsa::writeHeaderFile()
{
const size_t nChannel = m_matrix->getDimensionSize(0);
// size_t samplesPerChunk = m_Matrix->getDimensionSize(1);
const double samplingInterval = 1000000.0 / double(m_sampling);
CString format("UNKNOWN");
switch (m_binaryFormat)
{
case BinaryFormat_Integer16:
format = "INT_16";
break;
case BinaryFormat_UnsignedInteger16:
format = "UINT_16";
break;
case BinaryFormat_Float32:
format = "IEEE_FLOAT_32";
break;
default:
this->getLogManager() << Kernel::LogLevel_Error << "EEG format unknown!\n";
break;
}
m_headerFile << "Brain Vision Data Exchange Header File Version 1.0" << std::endl
<< "; Data created by the Vision Recorder" << std::endl << std::endl
<< "[Common Infos]" << std::endl
<< "Codepage=ANSI" << std::endl
<< "DataFile=" << getShortName(m_dataFilename) << std::endl
<< "MarkerFile=" << getShortName(m_markerFilename) << std::endl
<< "DataFormat=BINARY" << std::endl
<< "; Data orientation: MULTIPLEXED=ch1,pt1, ch2,pt1 ..." << std::endl //sample 1, sample 2 ...
<< "DataOrientation=MULTIPLEXED" << std::endl
<< "NumberOfChannels=" << nChannel << std::endl
<< "; Sampling interval in microseconds" << std::endl
<< "SamplingInterval=" << std::fixed << std::setprecision(5) << samplingInterval << std::endl
<< std::endl
<< "[Binary Infos]" << std::endl
<< "BinaryFormat=" << format.toASCIIString() << std::endl
<< std::endl;
m_headerFile << "[Channel Infos]" << std::endl
<< "; Each entry: Ch<Channel number>=<Name>,<Reference channel name>," << std::endl
<< "; <Resolution in \"Unit\">,<Unit>, Future extensions.." << std::endl
<< "; Fields are delimited by commas, some fields might be omitted (empty)." << std::endl
<< "; Commas in channel names are coded as \"\\1\"." << std::endl;
for (size_t i = 0; i < nChannel; ++i)
{
m_headerFile << "Ch" << (i + 1) << "=" << m_matrix->getDimensionLabel(0, i) << ",,1," << std::endl; //resolution = 1
}
m_headerFile << std::endl;
m_headerFile << "[Comment]" << std::endl << std::endl
<< "A m p l i f i e r S e t u p" << std::endl
<< "============================" << std::endl
<< "Number of channels: " << nChannel << std::endl
<< "Sampling Rate [Hz]: " << m_sampling << std::endl
<< "Interval [µS]: " << std::fixed << std::setprecision(5) << samplingInterval << std::endl
<< std::endl;
return true;
}
std::string CBoxAlgorithmBrainampFileWriterGipsa::getShortName(std::string fullpath)
{
size_t pos = fullpath.find_last_of('\\');
if (pos == std::string::npos) { pos = fullpath.find_last_of('/'); }
if (pos != std::string::npos) { return fullpath.substr(pos + 1, fullpath.size() - pos); }
return fullpath;
}
std::string CBoxAlgorithmBrainampFileWriterGipsa::formatTime(const boost::posix_time::ptime now)
{
using namespace boost::posix_time;
static std::locale loc(std::wcout.getloc(), new wtime_facet(L"%Y%m%d%H%M%S"));
std::basic_stringstream<wchar_t> wss;
wss.imbue(loc);
wss << now;
std::wstring wstr = wss.str();
std::string str(wstr.begin(), wstr.end());
return str;
}
} // namespace FileIO
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,152 @@
/* Project: Gipsa-lab plugins for OpenVibe
* AUTHORS AND CONTRIBUTORS: Andreev A., Barachant A., Congedo M., Ionescu,Gelu,
* This file is part of "Gipsa-lab plugins for OpenVibe".
* You can redistribute it and/or modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* This file is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along with Brain Invaders. If not, see http://www.gnu.org/licenses/.*/
#pragma once
#include "../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <fstream>
#include <boost/date_time/posix_time/posix_time.hpp>
namespace OpenViBE {
namespace Plugins {
namespace FileIO {
class CBoxAlgorithmBrainampFileWriterGipsa final : public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
{
public:
CBoxAlgorithmBrainampFileWriterGipsa() { }
void release() override { delete this; }
bool initialize() override;
bool uninitialize() override;
bool processInput(const size_t index) override;
bool process() override;
bool writeHeaderFile(); //write the .vhdr file
static std::string getShortName(std::string fullpath); //of a file
static std::string formatTime(boost::posix_time::ptime now);
template <class T>
bool saveBuffer(const T /*myDummy*/)
{
std::vector<T> output(m_matrix->getBufferElementCount());
if (output.size() != m_matrix->getBufferElementCount()) { return false; }
const size_t nChannel = m_matrix->getDimensionSize(0);
const size_t nSamplesPerChunk = m_matrix->getDimensionSize(1);
double* input = m_matrix->getBuffer();
for (size_t k = 0; k < nChannel; ++k)
{
for (size_t j = 0; j < nSamplesPerChunk; ++j)
{
const size_t index = (k * nSamplesPerChunk) + j;
output[j * nChannel + k] = T(input[index]);
}
}
m_dataFile.write((char*)&output[0], m_matrix->getBufferElementCount() * sizeof(T));
return true;
}
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_BrainampFileWriterGipsa)
protected:
enum EBinaryFormat
{
BinaryFormat_Integer16,
BinaryFormat_UnsignedInteger16,
BinaryFormat_Float32,
};
//input signal 1
Toolkit::TSignalDecoder<CBoxAlgorithmBrainampFileWriterGipsa>* m_streamDecoder = nullptr;
CMatrix* m_matrix = nullptr;
uint64_t m_sampling = 0;
//input stimulation 1
Toolkit::TStimulationDecoder<CBoxAlgorithmBrainampFileWriterGipsa>* m_stimDecoder = nullptr;
//Kernel::TParameterHandler <const IMemoryBuffer* > ip_bufferToDecodeTrigger;
//Kernel::TParameterHandler < IStimulationSet* > op_pStimulationSetTrigger;
std::string m_headerFilename;
std::string m_dataFilename;
std::string m_markerFilename;
std::ofstream m_headerFile;
std::ofstream m_dataFile;
std::ofstream m_markerFile;
size_t m_binaryFormat = 0;
size_t m_stimulationCounter = 2; //because the first is outputed manually
bool m_isVmrkHeaderFileWritten = false; //Is the beginning of the .vmrk (file with stimulations is written)
};
class CBoxAlgorithmBrainampFileWriterGipsaListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
bool onInputTypeChanged(Kernel::IBox& /*box*/, const size_t /*index*/) override { return true; }
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
};
class CBoxAlgorithmBrainampFileWriterGipsaDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("BrainVision Format file writer (Gipsa)"); }
CString getAuthorName() const override { return CString("Anton Andreev"); }
CString getAuthorCompanyName() const override { return CString("Gipsa-lab"); }
CString getShortDescription() const override { return CString("Writes signal in the Brainamp file format."); }
CString getDetailedDescription() const override
{
return CString(
"You must select the location of the output header file .vhdr. The .eeg and .vmrk files will be created with the same name and in the same folder. Integer codes of OpenVibe stimulations are saved in the .vmrk file. OV Stimulation with code 233 will be saved as S233 on the .vmrk file.");
}
CString getCategory() const override { return CString("File reading and writing/BrainVision Format"); }
CString getVersion() const override { return CString("1.1"); }
CString getStockItemName() const override { return CString("gtk-save"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_BrainampFileWriterGipsa; }
IPluginObject* create() override { return new CBoxAlgorithmBrainampFileWriterGipsa; }
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmBrainampFileWriterGipsaListener; }
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Streamed matrix", OV_TypeId_Signal);
prototype.addInput("Input stimulation channel", OV_TypeId_Stimulations);
prototype.addSetting("Header filename", OV_TypeId_Filename, "record-[$core{date}-$core{time}].vhdr");
prototype.addSetting("Binary format", OVP_TypeId_BinaryFormat, "INT_16");
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_BrainampFileWriterGipsaDesc)
};
} // namespace FileIO
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,314 @@
#include "ovpCBoxAlgorithmEDFFileWriter.h"
#include <limits>
#include <sstream>
namespace OpenViBE {
namespace Plugins {
namespace FileIO {
/*******************************************************************************/
bool CBoxAlgorithmEDFFileWriter::initialize()
{
m_experimentInfoDecoder.initialize(*this, 0);
m_signalDecoder.initialize(*this, 1);
m_stimDecoder.initialize(*this, 2);
m_filename = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
m_sampling = 0;
m_nChannels = 0;
m_isFileOpened = false;
m_fileHandle = -1;
return true;
}
/*******************************************************************************/
bool CBoxAlgorithmEDFFileWriter::uninitialize()
{
if (!m_isFileOpened)
{
// Fine, we didn't manage to write anything
this->getLogManager() << Kernel::LogLevel_Warning << "Exiting without writing a file (open failed or no signal header received).\n";
// Clear the queue
std::queue<double> empty;
std::swap(m_buffer, empty);
m_experimentInfoDecoder.uninitialize();
m_signalDecoder.uninitialize();
m_stimDecoder.uninitialize();
return true;
}
this->getLogManager() << Kernel::LogLevel_Info << "Writing the file, this may take a moment.\n";
for (size_t c = 0; c < m_nChannels; ++c)
{
if (EdfSetSampling(m_fileHandle, c, m_sampling) == -1)
{
this->getLogManager() << Kernel::LogLevel_ImportantWarning << "edf_set_sampling failed!\n";
return false;
}
if (EdfSetPhysicalMaximum(m_fileHandle, c, m_channelInfo[c].max) == -1)//1.7*10^308)
{
this->getLogManager() << Kernel::LogLevel_ImportantWarning << "edf_set_physical_maximum failed!\n";
return false;
}
if (EdfSetPhysicalMinimum(m_fileHandle, c, m_channelInfo[c].min) == -1)//-1.7*10^308)
{
this->getLogManager() << Kernel::LogLevel_ImportantWarning << "edf_set_physical_minimum failed!\n";
return false;
}
if (EdfSetDigitalMaximum(m_fileHandle, c, 32767) == -1)
{
this->getLogManager() << Kernel::LogLevel_ImportantWarning << "edf_set_digital_maximum failed!\n";
return false;
}
if (EdfSetDigitalMinimum(m_fileHandle, c, -32768) == -1)
{
this->getLogManager() << Kernel::LogLevel_ImportantWarning << "edf_set_digital_minimum failed!\n";
return false;
}
if (EdfSetLabel(m_fileHandle, c, m_signalDecoder.getOutputMatrix()->getDimensionLabel(0, c)) == -1)
{
this->getLogManager() << Kernel::LogLevel_ImportantWarning << "edf_set_label failed!\n";
return false;
}
}
const size_t n = m_sampling * m_nChannels;
std::vector<double> tmpBuffer(n), tmpBufferToWrite(n); // A buffer chunk and Transposed buffer chunk
// Write out all the complete buffers
if (m_buffer.size() >= n)
{
this->getLogManager() << Kernel::LogLevel_Trace << "((int) buffer.size() >= m_sampling*m_NChannels)\n";
while (m_buffer.size() >= n)
{
this->getLogManager() << Kernel::LogLevel_Trace << "while((int)buffer.size() >= m_sampling*m_NChannels)\n";
for (size_t i = 0; i < n; ++i)
{
tmpBuffer[i] = double(m_buffer.front());
m_buffer.pop();
}
for (size_t c = 0; c < m_nChannels; ++c)
{
for (size_t s = 0; s < m_sampling; ++s) { tmpBufferToWrite[c * m_sampling + s] = tmpBuffer[s * m_nChannels + c]; }
}
if (EdfBlockwritePhysicalSamples(m_fileHandle, tmpBufferToWrite.data()) == -1)
{
this->getLogManager() << Kernel::LogLevel_ImportantWarning << "edf_blockwrite_physical_samples: Could not write samples in file [" << m_filename << "]\n";
return false;
}
}
}
// Do we have a partial buffer? If so, write it out padded by zeroes
if (!m_buffer.empty())
{
this->getLogManager() << Kernel::LogLevel_Trace << "if(buffer.size() > 0))\n";
for (size_t i = 0; i < n; ++i) { tmpBuffer[i] = 0; }
for (size_t i = 0; i < m_buffer.size(); ++i)
{
tmpBuffer[i] = double(m_buffer.front());
m_buffer.pop();
}
for (size_t c = 0; c < m_nChannels; ++c)
{
for (size_t s = 0; s < m_sampling; ++s) { tmpBufferToWrite[c * m_sampling + s] = tmpBuffer[s * m_nChannels + c]; }
}
if (EdfBlockwritePhysicalSamples(m_fileHandle, tmpBufferToWrite.data()) == -1)
{
this->getLogManager() << Kernel::LogLevel_ImportantWarning << "edf_blockwrite_physical_samples: Could not write samples in file [" << m_filename << "]\n";
return false;
}
}
if (EdfcloseFile(m_fileHandle) == -1)
{
this->getLogManager() << Kernel::LogLevel_ImportantWarning << "edfclose_file: Could not close file [" << m_filename << "]\n";
return false;
}
m_experimentInfoDecoder.uninitialize();
m_signalDecoder.uninitialize();
m_stimDecoder.uninitialize();
return true;
}
/*******************************************************************************/
bool CBoxAlgorithmEDFFileWriter::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
/*******************************************************************************/
bool CBoxAlgorithmEDFFileWriter::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
//iterate over all chunk on signal input
for (size_t i = 0; i < boxContext.getInputChunkCount(1); ++i)
{
m_signalDecoder.decode(i);
if (m_signalDecoder.isHeaderReceived())
{
m_sampling = size_t(m_signalDecoder.getOutputSamplingRate());
m_nChannels = m_signalDecoder.getOutputMatrix()->getDimensionSize(0);
m_nSamplesPerChunk = m_signalDecoder.getOutputMatrix()->getDimensionSize(1);
m_fileHandle = EdfopenFileWriteonly(m_filename.toASCIIString(), EDFLIB_FILETYPE_EDFPLUS, m_nChannels);
if (m_fileHandle < 0)
{
this->getLogManager() << Kernel::LogLevel_ImportantWarning << "Could not open file [" << m_filename << "]\n";
switch (m_fileHandle)
{
case EDFLIB_MALLOC_ERROR:
this->getLogManager() << Kernel::LogLevel_ImportantWarning << "EDFLIB_MALLOC_ERROR: ";
break;
case EDFLIB_NO_SUCH_FILE_OR_DIRECTORY:
this->getLogManager() << Kernel::LogLevel_ImportantWarning << "EDFLIB_NO_SUCH_FILE_OR_DIRECTORY: ";
break;
case EDFLIB_MAXFILES_REACHED:
this->getLogManager() << Kernel::LogLevel_ImportantWarning << "EDFLIB_MAXFILES_REACHED: ";
break;
case EDFLIB_FILE_ALREADY_OPENED:
this->getLogManager() << Kernel::LogLevel_ImportantWarning << "EDFLIB_FILE_ALREADY_OPENED: ";
break;
case EDFLIB_NUMBER_OF_SIGNALS_INVALID:
this->getLogManager() << Kernel::LogLevel_ImportantWarning << "EDFLIB_NUMBER_OF_SIGNALS_INVALID: ";
break;
default:
this->getLogManager() << Kernel::LogLevel_ImportantWarning << "EDFLIB_UNKNOWN_ERROR: ";
break;
}
return false;
}
//set file parameters
EdfSetStartdatetime(m_fileHandle, 0, 0, 0, 0, 0, 0);
for (size_t c = 0; c < m_nChannels; ++c)
{
//Creation of one information channel structure per channel
channel_info_t channelInfo = { std::numeric_limits<double>::max(), -std::numeric_limits<double>::max() };
m_channelInfo.push_back(channelInfo);
}
m_isFileOpened = true;
}
if (m_signalDecoder.isBufferReceived())
{
//put sample in the buffer
CMatrix* matrix = m_signalDecoder.getOutputMatrix();
for (size_t s = 0; s < m_nSamplesPerChunk; ++s)
{
for (size_t c = 0; c < m_nChannels; ++c)
{
double value = matrix->getBuffer()[c * m_nSamplesPerChunk + s];
if (value > m_channelInfo[c].max) { m_channelInfo[c].max = value; }
if (value < m_channelInfo[c].min) { m_channelInfo[c].min = value; }
m_buffer.push(value);
}
}
}
if (m_signalDecoder.isEndReceived()) { }
}
if (m_isFileOpened)
{
//iterate over all chunk on experiment information input
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
m_experimentInfoDecoder.decode(i);
if (m_experimentInfoDecoder.isHeaderReceived())
{
//set patient code
EdfSetPatientcode(m_fileHandle, std::to_string(m_experimentInfoDecoder.getOutputSubjectID()).c_str());
//set patient gender
switch (m_experimentInfoDecoder.getOutputSubjectGender())
{
case 2: //female
EdfSetGender(m_fileHandle, 0);
break;
case 1: //male
EdfSetGender(m_fileHandle, 1);
break;
case 0: //unknown
break;
case 9: //unspecified
default:
break;
}
//set patient age
const char* patientAge = ("Patient age = " + std::to_string(m_experimentInfoDecoder.getOutputSubjectAge())).c_str();
EdfSetPatientAdditional(m_fileHandle, patientAge);
}
}
}
//iterate over all chunk on stimulation input
for (size_t i = 0; i < boxContext.getInputChunkCount(2); ++i)
{
m_stimDecoder.decode(i);
if (m_stimDecoder.isHeaderReceived()) { }
if (m_stimDecoder.isBufferReceived())
{
const IStimulationSet* stimSet = m_stimDecoder.getOutputStimulationSet();
for (size_t j = 0; j < stimSet->getStimulationCount(); ++j)
{
const uint64_t date = stimSet->getStimulationDate(j);
const int64_t stimDate = int64_t(CTime(date).toSeconds() / 0.0001);
const uint64_t duration = stimSet->getStimulationDuration(j);
const int64_t stimDuration = int64_t(CTime(duration).toSeconds() / 0.0001);
const uint64_t id = stimSet->getStimulationIdentifier(j);
CString stimID = this->getTypeManager().getEnumerationEntryNameFromValue(OV_TypeId_Stimulation, id);
if (stimID.length() == 0)
{
// If the stimulation number has not been registered as an enum, just pass the number
std::stringstream ss;
ss << id;
stimID = ss.str().c_str();
}
const int result = EdfwriteAnnotationUTF8(m_fileHandle, stimDate, stimDuration, stimID.toASCIIString());
if (result == -1)
{
this->getLogManager() << Kernel::LogLevel_ImportantWarning << "edfwrite_annotation_utf8 failed!\n";
return false;
}
}
}
if (m_stimDecoder.isEndReceived()) { }
}
return true;
}
} // namespace FileIO
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,92 @@
#pragma once
#include "../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include "edf/edflib.h"
#include <queue>
#include <deque>
#include <vector>
namespace OpenViBE {
namespace Plugins {
namespace FileIO {
typedef struct
{
double min;
double max;
} channel_info_t;
class CBoxAlgorithmEDFFileWriter final : public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
{
public:
//CBoxAlgorithmEDFFileWriter();
void release() override { delete this; }
bool initialize() override;
bool uninitialize() override;
bool processInput(const size_t index) override;
bool process() override;
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_EDFFileWriter)
protected:
Toolkit::TExperimentInfoDecoder<CBoxAlgorithmEDFFileWriter> m_experimentInfoDecoder;
Toolkit::TSignalDecoder<CBoxAlgorithmEDFFileWriter> m_signalDecoder;
Toolkit::TStimulationDecoder<CBoxAlgorithmEDFFileWriter> m_stimDecoder;
CString m_filename;
bool m_isFileOpened = false;
int m_fileHandle = 0;
size_t m_sampling = 0;
size_t m_nChannels = 0;
size_t m_nSamplesPerChunk = 0;
std::queue<double, std::deque<double>> m_buffer;
//int * m_pTemporyBuffer;
//int * m_pTemporyBufferToWrite;
std::vector<channel_info_t> m_channelInfo;
};
class CBoxAlgorithmEDFFileWriterDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("EDF File Writer"); }
CString getAuthorName() const override { return CString("Aurelien Van Langhenhove"); }
CString getAuthorCompanyName() const override { return CString("CICIT Garches"); }
CString getShortDescription() const override { return CString("Writes experiment information, signal and stimulations in a EDF file"); }
CString getDetailedDescription() const override { return CString(""); }
CString getCategory() const override { return CString("File reading and writing/EDF"); }
CString getVersion() const override { return CString("1.0"); }
CString getStockItemName() const override { return CString("gtk-save"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_EDFFileWriter; }
IPluginObject* create() override { return new CBoxAlgorithmEDFFileWriter; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Experiment information", OV_TypeId_ExperimentInfo);
prototype.addInput("Signal", OV_TypeId_Signal);
prototype.addInput("Stimulations", OV_TypeId_Stimulations);
prototype.addSetting("Filename", OV_TypeId_Filename, "record-[$core{date}-$core{time}].edf");
//prototype.addFlag(Kernel::BoxFlag_IsUnstable);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_EDFFileWriterDesc)
};
} // namespace FileIO
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,13 @@
#pragma once
// Boxes
//---------------------------------------------------------------------------------------------------
#define OVP_ClassId_BoxAlgorithm_EDFFileWriter OpenViBE::CIdentifier(0x0D454DCE, 0x470A4C02)
#define OVP_ClassId_BoxAlgorithm_EDFFileWriterDesc OpenViBE::CIdentifier(0x0D454DCE, 0x470A4C02)
#define OVP_ClassId_BoxAlgorithm_BrainampFileWriterGipsa OpenViBE::CIdentifier(0x0C7E0BDE, 0x4EC90F95)
#define OVP_ClassId_BoxAlgorithm_BrainampFileWriterGipsaDesc OpenViBE::CIdentifier(0x0A77142C, 0x316B6E47)
#define OVP_TypeId_BinaryFormat OpenViBE::CIdentifier(0x567234C5, 0x3D870DC3)
#define OVP_TypeId_BinaryFormat_int16_t OpenViBE::CIdentifier(0x1C777556, 0x123861C3)
#define OVP_TypeId_BinaryFormat_uint16_t OpenViBE::CIdentifier(0x6A7B6C73, 0x4B9D129D)
#define OVP_TypeId_BinaryFormat_float OpenViBE::CIdentifier(0x15183866, 0x7AAC69FC)
@@ -0,0 +1,35 @@
#include <vector>
#include <openvibe/ov_all.h>
// @BEGIN CICIT-GARCHES
#include "box-algorithms/ovpCBoxAlgorithmEDFFileWriter.h"
// @END CICIT-GARCHES
// @BEGIN GIPSA
#include "box-algorithms/ovpCBoxAlgorithmBrainampFileWriterGipsa.h"
// @END GIPSA
namespace OpenViBE {
namespace Plugins {
namespace FileIO {
OVP_Declare_Begin()
// @BEGIN CICIT-GARCHES
OVP_Declare_New(CBoxAlgorithmEDFFileWriterDesc)
// @END CICIT_GARCHES
// @BEGIN GIPSA
//Register dropdowns
context.getTypeManager().registerEnumerationType(OVP_TypeId_BinaryFormat, "Binary format select");
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_BinaryFormat, "INT_16", OVP_TypeId_BinaryFormat_int16_t.id());
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_BinaryFormat, "UINT_16", OVP_TypeId_BinaryFormat_uint16_t.id());
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_BinaryFormat, "IEEE_FLOAT_32", OVP_TypeId_BinaryFormat_float.id());
OVP_Declare_New(CBoxAlgorithmBrainampFileWriterGipsaDesc)
// @END GIPSA
OVP_Declare_End()
} // namespace Tools
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,28 @@
PROJECT(openvibe-plugins-contrib-misc)
SET(PROJECT_VERSION_MAJOR ${OV_GLOBAL_VERSION_MAJOR})
SET(PROJECT_VERSION ${OV_GLOBAL_VERSION_STRING})
FILE(GLOB_RECURSE SRC_FILES src/*.cpp src/*.h src/*.inl)
ADD_LIBRARY(${PROJECT_NAME} SHARED ${SRC_FILES})
SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
FOLDER ${PLUGINS_FOLDER}
COMPILE_FLAGS "-DOVP_Exports -DOVP_Shared")
# ---------------------------------
INCLUDE("FindOpenViBE")
INCLUDE("FindOpenViBECommon")
INCLUDE("FindOpenViBEToolkit")
INCLUDE("FindOpenViBEModuleEBML")
# INCLUDE("FindThirdPartyBoost")
INCLUDE("FindThirdPartyOpenAL")
# -----------------------------
# Install files
# -----------------------------
INSTALL(TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION ${DIST_BINDIR}
LIBRARY DESTINATION ${DIST_LIBDIR}
ARCHIVE DESTINATION ${DIST_LIBDIR})
@@ -0,0 +1,83 @@
#include "ovpCMouseControl.h"
#include <iostream>
#if defined TARGET_OS_Linux
#include <unistd.h>
#endif
namespace OpenViBE {
namespace Plugins {
namespace Tools { //Ambiguous without OpenViBEPlugins
bool CMouseControl::initialize()
{
m_decoder = new Toolkit::TStreamedMatrixDecoder<CMouseControl>(*this, 0);
#if !defined(TARGET_OS_Linux)
getLogManager() << Kernel::LogLevel_Error << "This box algorithm is not implemented for your operating system\n";
return false;
#else
return true;
#endif
}
bool CMouseControl::uninitialize()
{
m_decoder->uninitialize();
delete m_decoder;
return true;
}
bool CMouseControl::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CMouseControl::process()
{
Kernel::IBoxIO* boxContext = getBoxAlgorithmContext()->getDynamicBoxContext();
const size_t nInputChunk = boxContext->getInputChunkCount(0);
for (size_t i = 0; i < nInputChunk; ++i)
{
m_decoder->decode(i);
if (m_decoder->isBufferReceived())
{
CMatrix* iMatrix = m_decoder->getOutputMatrix();
if (iMatrix->getBufferElementCount() != 1)
{
getBoxAlgorithmContext()->getPlayerContext()->getLogManager() << Kernel::LogLevel_Error << "Error, dimension size isn't 1 for Amplitude input !\n";
return false;
}
#if defined TARGET_OS_Linux
const double* iBuffer = iMatrix->getBuffer();
m_pMainDisplay=::XOpenDisplay(NULL);
if (!m_pMainDisplay)
{
getLogManager() << Kernel::LogLevel_Error << "Impossible to open Display.\n";
return false;
}
m_oRootWindow=DefaultRootWindow(m_pMainDisplay); //all X11 screens
::XSelectInput(m_pMainDisplay, m_oRootWindow, ButtonPressMask|ButtonReleaseMask|ButtonMotionMask|OwnerGrabButtonMask);
int offsetY = 0;
int offsetX = int(iBuffer[0]*100.0);
getLogManager() << Kernel::LogLevel_Debug << "offsetX = " << offsetX << "\n";
::XWarpPointer(m_pMainDisplay, m_oRootWindow, 0, 0, 0, 0, 0, offsetX, offsetY);
::XCloseDisplay(m_pMainDisplay);
#endif
// TODO
// For windows use:
// SetCursorPos(int x, int y)
}
}
return true;
}
} // namespace Tools
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,77 @@
#pragma once
#include "../ovp_defines.h"
#include <toolkit/ovtk_all.h>
#include <string>
#include <map>
#if defined TARGET_OS_Linux
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#endif
namespace OpenViBE {
namespace Plugins {
namespace Tools {
class CMouseControl : public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
{
public:
CMouseControl() { }
void release() override { delete this; }
bool initialize() override;
bool uninitialize() override;
bool processInput(const size_t index) override;
bool process() override;
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_MouseControl)
protected:
//codec
Toolkit::TStreamedMatrixDecoder<CMouseControl>* m_decoder = nullptr;
#if defined TARGET_OS_Linux
::Display* m_pMainDisplay = nullptr;
::Window m_oRootWindow;
#endif
};
class CMouseControlDesc : public IBoxAlgorithmDesc
{
public:
CString getName() const override { return CString("Mouse Control"); }
CString getAuthorName() const override { return CString("Guillaume Gibert"); }
CString getAuthorCompanyName() const override { return CString("INSERM"); }
CString getShortDescription() const override { return CString("Mouse Control for Feedback"); }
CString getDetailedDescription() const override
{
return CString("Experimental box to move the mouse in x direction with respect to the input value. Only implemented on Linux.");
}
CString getCategory() const override { return CString("Tools"); }
CString getVersion() const override { return CString("0.1"); }
void release() override { }
CIdentifier getCreatedClass() const override { return OVP_ClassId_MouseControl; }
IPluginObject* create() override { return new CMouseControl(); }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Amplitude", OV_TypeId_StreamedMatrix);
prototype.addFlag(OV_AttributeId_Box_FlagIsUnstable);
prototype.addInputSupport(OV_TypeId_StreamedMatrix);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_MouseControlDesc)
};
} // namespace Tools
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,14 @@
#pragma once
// Boxes
//---------------------------------------------------------------------------------------------------
#define OVP_ClassId_MouseControl OpenViBE::CIdentifier(0xDA4B4EEB, 0x64FC6A16)
#define OVP_ClassId_MouseControlDesc OpenViBE::CIdentifier(0xB6B65C98, 0xA756ED0E)
// Global defines
//---------------------------------------------------------------------------------------------------
#ifdef TARGET_HAS_ThirdPartyOpenViBEPluginsGlobalDefines
#include "ovp_global_defines.h"
#endif // TARGET_HAS_ThirdPartyOpenViBEPluginsGlobalDefines
#define OV_AttributeId_Box_FlagIsUnstable OpenViBE::CIdentifier(0x666FFFFF, 0x666FFFFF)
@@ -0,0 +1,22 @@
#include <vector>
#include <openvibe/ov_all.h>
#include "ovp_defines.h"
#include "box-algorithms/ovpCMouseControl.h" // inserm
namespace OpenViBE {
namespace Plugins {
namespace Tools {
OVP_Declare_Begin()
// @BEGIN inserm
OVP_Declare_New(CMouseControlDesc);
context.getTypeManager().registerEnumerationEntry(OV_TypeId_BoxAlgorithmFlag, OV_AttributeId_Box_FlagIsUnstable.toString(),
OV_AttributeId_Box_FlagIsUnstable.id());
// @END inserm
OVP_Declare_End()
} // namespace Tools
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,27 @@
PROJECT(openvibe-plugins-contrib-network-io)
SET(PROJECT_VERSION_MAJOR ${OV_GLOBAL_VERSION_MAJOR})
SET(PROJECT_VERSION ${OV_GLOBAL_VERSION_STRING})
FILE(GLOB_RECURSE SRC_FILES src/*.cpp src/*.h src/*.inl)
ADD_LIBRARY(${PROJECT_NAME} SHARED ${SRC_FILES})
SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
FOLDER ${PLUGINS_FOLDER}
COMPILE_FLAGS "-DOVP_Exports -DOVP_Shared")
# ---------------------------------
INCLUDE("FindOpenViBE")
INCLUDE("FindOpenViBECommon")
INCLUDE("FindOpenViBEToolkit")
# INCLUDE("FindOpenViBEModuleEBML")
INCLUDE("FindThirdPartyLSL")
# -----------------------------
# Install files
# -----------------------------
INSTALL(TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION ${DIST_BINDIR}
LIBRARY DESTINATION ${DIST_LIBDIR}
ARCHIVE DESTINATION ${DIST_LIBDIR})
@@ -0,0 +1,75 @@
/**
* \page BoxAlgorithm_OSCController OSC Controller
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_OSCController_Description|
By this box, you can control OSC supporting devices, such as
synthesizers and oscillators. It can be used to turn OpenViBE
streams into sound, e.g. for brain music or auditory BCI.
The OSC Controller box simply sends the incoming data as UDP
messages to the specified OSC Server. Each message is flagged with
an OSC Address specifying the device that the message is intended to
control.
* |OVP_DocEnd_BoxAlgorithm_OSCController_Description|
__________________________________________________________________
Inputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_OSCController_Inputs|
* |OVP_DocEnd_BoxAlgorithm_OSCController_Inputs|
* |OVP_DocBegin_BoxAlgorithm_OSCController_Input1|
Data to send to the OSC Server. This input can be either a signal, a matrix, or a stimulation. The signal or matrix must have only 1 channel (row). Signals are sent as float and stimulations as size_t.
* |OVP_DocEnd_BoxAlgorithm_OSCController_Input1|
__________________________________________________________________
Settings description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_OSCController_Settings|
* |OVP_DocEnd_BoxAlgorithm_OSCController_Settings|
* |OVP_DocBegin_BoxAlgorithm_OSCController_Setting1|
Server address (IP or DNS)
* |OVP_DocEnd_BoxAlgorithm_OSCController_Setting1|
* |OVP_DocBegin_BoxAlgorithm_OSCController_Setting2|
Server port
* |OVP_DocEnd_BoxAlgorithm_OSCController_Setting2|
* |OVP_DocBegin_BoxAlgorithm_OSCController_Setting3|
The OSC Address specifying the device the messages are intended to, e.g. /oscillator/4/frequency
* |OVP_DocEnd_BoxAlgorithm_OSCController_Setting3|
__________________________________________________________________
Examples description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_OSCController_Examples|
Utilities such as OSC DataMonitor by Kasper Kamperman can be used to debug the messages sent out by the box.
* |OVP_DocEnd_BoxAlgorithm_OSCController_Examples|
__________________________________________________________________
Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_OSCController_Miscellaneous|
In the current implementation, the data is sent when received. OpenViBE internal timing is not passed to the OSC.
The box supports only one input, but the input type can be modified. The input must have only one channel. For signals, you can extract a
channel by using the Channel Selector box. Also, if input type is a signal, its sampling rate is ignored.
However, several OSC Controller boxes can be used at the same time if multiple sources are needed to be
sent to the OSC Server(s) or device(s). These limitations are to keep the code simple.
The box is not intended to transmit large chunks of numeric data. It may be meaningful to limit the
amount of data on the OpenViBE side e.g. with boxes such as Signal Average, Downsampling or Signal Decimation, or Stimulation Filter.
For more information about the OSC protocol and applications that support it, please see http://www.opensoundcontrol.org
* |OVP_DocEnd_BoxAlgorithm_OSCController_Miscellaneous|
*/
@@ -0,0 +1,850 @@
/**
OSCPKT : a minimalistic OSC ( http://opensoundcontrol.org ) c++ library
Before using this file please take the time to read the OSC spec, it
is short and not complicated: http://opensoundcontrol.org/spec-1_0
Features:
- handles basic OSC types: TFihfdsb
- handles bundles
- handles OSC pattern-matching rules (wildcards etc in message paths)
- portable on win / macos / linux
- robust wrt malformed packets
- optional udp transport for packets
- concise, all in a single .h file
- does not throw exceptions
does not:
- take into account timestamp values.
- provide a cpu-scalable message dispatching.
- not suitable for use inside a realtime thread as it allocates memory when
building or reading messages.
There are basically 3 classes of interest:
- oscpkt::Message : read/write the content of an OSC message
- oscpkt::PacketReader : read the bundles/messages embedded in an OSC packet
- oscpkt::PacketWriter : write bundles/messages into an OSC packet
And optionaly:
- oscpkt::UdpSocket : read/write OSC packets over UDP.
example: oscpkt_demo.cc
example: oscpkt_test.cc
*/
/* Copyright (C) 2010 Julien Pommier
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
(this is the zlib license)
*/
#pragma once
#ifndef _MSC_VER
#include <stdint.h>
#else
namespace oscpkt {
typedef __int32 int32_t;
typedef unsigned __int32 uint32_t;
typedef __int64 int64_t;
typedef unsigned __int64 uint64_t;
}
#endif
#include <cstring>
#include <cassert>
#include <string>
#include <vector>
#include <list>
#if defined(OSCPKT_OSTREAM_OUTPUT) || defined(OSCPKT_TEST)
#include <iostream>
#endif
namespace oscpkt {
/**
OSC timetag stuff, the highest 32-bit are seconds, the lowest are fraction of a second.
*/
class TimeTag
{
uint64_t v = 1;
public:
TimeTag() {}
explicit TimeTag(const uint64_t w): v(w) {}
operator uint64_t() const { return v; }
static TimeTag immediate() { return TimeTag(1); }
};
/* the various types that we handle (OSC 1.0 specifies that INT32/FLOAT/STRING/BLOB are the bare minimum) */
enum
{
TYPE_TAG_TRUE = 'T',
TYPE_TAG_FALSE = 'F',
TYPE_TAG_INT32 = 'i',
TYPE_TAG_INT64 = 'h',
TYPE_TAG_FLOAT = 'f',
TYPE_TAG_DOUBLE = 'd',
TYPE_TAG_STRING = 's',
TYPE_TAG_BLOB = 'b'
};
/* a few utility functions follow.. */
// round to the next multiple of 4, works for size_t and pointer arguments
template <typename Type>
Type ceil4(Type p) { return Type((size_t(p) + 3) & (~size_t(3))); }
// check that a memory area is zero padded until the next address which is a multiple of 4
inline bool isZeroPaddingCorrect(const char* p)
{
const char* q = ceil4(p);
for (; p < q; ++p) { if (*p != 0) { return false; } }
return true;
}
// stuff for reading / writing POD ("Plain Old Data") variables to unaligned bytes.
template <typename POD>
union PodBytes
{
char bytes[sizeof(POD)];
POD value;
};
inline bool isBigEndian()
{ // a compile-time constant would certainly improve performances..
PodBytes<int32_t> p;
p.value = 0x12345678;
return p.bytes[0] == 0x12;
}
/** read unaligned bytes into a POD type, assuming the bytes are a little endian representation */
template <typename POD>
POD bytes2pod(const char* bytes)
{
PodBytes<POD> p;
for (size_t i = 0; i < sizeof(POD); ++i)
{
if (isBigEndian()) { p.bytes[i] = bytes[i]; }
else { p.bytes[i] = bytes[sizeof(POD) - i - 1]; }
}
return p.value;
}
/** stored a POD type into an unaligned bytes array, using little endian representation */
template <typename POD>
void pod2bytes(const POD value, char* bytes)
{
PodBytes<POD> p;
p.value = value;
for (size_t i = 0; i < sizeof(POD); ++i)
{
if (isBigEndian()) { bytes[i] = p.bytes[i]; }
else { bytes[i] = p.bytes[sizeof(POD) - i - 1]; }
}
}
/** internal stuff, handles the dynamic storage with correct alignments to 4 bytes */
struct Storage
{
std::vector<char> data;
Storage() { data.reserve(200); }
char* getBytes(const size_t sz)
{
assert((data.size() & 3) == 0);
if (data.size() + sz > data.capacity()) { data.reserve((data.size() + sz) * 2); }
const size_t sz4 = ceil4(sz);
const size_t pos = data.size();
data.resize(pos + sz4); // resize will fill with zeros, so the zero padding is OK
return &(data[pos]);
}
char* begin() { return !data.empty() ? &data.front() : nullptr; }
char* end() { return begin() + size(); }
const char* begin() const { return !data.empty() ? &data.front() : nullptr; }
const char* end() const { return begin() + size(); }
size_t size() const { return data.size(); }
void assign(const char* beg, const char* end) { data.assign(beg, end); }
void clear() { data.resize(0); }
};
/** check if the path matches the supplied path pattern , according to the OSC spec pattern rules ('*' and '//' wildcards, '{}' alternatives, brackets etc) */
bool fullPatternMatch(const std::string& pattern, const std::string& path);
/** check if the path matches the beginning of pattern */
bool partialPatternMatch(const std::string& pattern, const std::string& path);
#if defined(OSCPKT_DEBUG)
#define OSCPKT_SET_ERR(errcode) do { if (!err) { err = errcode; std::cerr << "set " #errcode << " at line " << __LINE__ << "\n"; } } while (0)
#else
#define OSCPKT_SET_ERR(errcode) do { if (!err) err = errcode; } while (0)
#endif
typedef enum
{
OK_NO_ERROR=0,
// errors raised by the Message class:
MALFORMED_ADDRESS_PATTERN,
MALFORMED_TYPE_TAGS,
MALFORMED_ARGUMENTS,
UNHANDLED_TYPE_TAGS,
// errors raised by ArgReader
TYPE_MISMATCH,
NOT_ENOUGH_ARG,
PATTERN_MISMATCH,
// errors raised by PacketReader/PacketWriter
INVALID_BUNDLE,
INVALID_PACKET_SIZE,
BUNDLE_REQUIRED_FOR_MULTI_MESSAGES
} ErrorCode;
/**
struct used to hold an OSC message that will be written or read.
The list of arguments is exposed as a sort of queue. You "pop"
arguments from the front of the queue when reading, you push
arguments at the back of the queue when writing.
Many functions return *this, so they can be chained: init("/foo").pushInt32(2).pushStr("kllk")...
Example of use:
creation of a message:
@code
msg.init("/foo").pushInt32(4).pushStr("bar");
@endcode
reading a message, with error detection:
@code
if (msg.match("/foo/b*ar/plop")) {
int i; std::string s; std::vector<char> b;
if (msg.arg().popInt32(i).popStr(s).popBlob(b).isOkNoMoreArgs()) {
process message...;
} else arguments mismatch;
}
@endcode
*/
class Message
{
TimeTag time_tag;
std::string address;
std::string type_tags;
std::vector<std::pair<size_t, size_t>> arguments; // array of pairs (pos,size), pos being an index into the 'storage' array.
Storage storage; // the arguments data is stored here
ErrorCode err;
public:
/** ArgReader is used for popping arguments from a Message, holds a
pointer to the original Message, and maintains a local error code */
class ArgReader
{
const Message* msg;
ErrorCode err;
size_t arg_idx; // arg index of the next arg that will be popped out.
public:
ArgReader(const Message& m, const ErrorCode e = OK_NO_ERROR) : msg(&m), err(msg->getErr()), arg_idx(0)
{
if (e != OK_NO_ERROR && err == OK_NO_ERROR) { err = e; }
}
ArgReader(const ArgReader& other) : msg(other.msg), err(other.err), arg_idx(other.arg_idx) {}
bool isBool() { return currentTypeTag() == TYPE_TAG_TRUE || currentTypeTag() == TYPE_TAG_FALSE; }
bool isInt32() { return currentTypeTag() == TYPE_TAG_INT32; }
bool isInt64() { return currentTypeTag() == TYPE_TAG_INT64; }
bool isFloat() { return currentTypeTag() == TYPE_TAG_FLOAT; }
bool isDouble() { return currentTypeTag() == TYPE_TAG_DOUBLE; }
bool isStr() { return currentTypeTag() == TYPE_TAG_STRING; }
bool isBlob() { return currentTypeTag() == TYPE_TAG_BLOB; }
size_t nbArgRemaining() const { return msg->arguments.size() - arg_idx; }
bool isOk() const { return err == OK_NO_ERROR; }
operator bool() const { return isOk(); } // implicit bool conversion is handy here
/** call this at the end of the popXXX() chain to make sure everything is ok and
all arguments have been popped */
bool isOkNoMoreArgs() const { return err == OK_NO_ERROR && nbArgRemaining() == 0; }
ErrorCode getErr() const { return err; }
/** retrieve an int32_t argument */
ArgReader& popInt32(int32_t& i) { return popPod<int32_t>(TYPE_TAG_INT32, i); }
/** retrieve an int64_t argument */
ArgReader& popInt64(int64_t& i) { return popPod<int64_t>(TYPE_TAG_INT64, i); }
/** retrieve a single precision floating point argument */
ArgReader& popFloat(float& f) { return popPod<float>(TYPE_TAG_FLOAT, f); }
/** retrieve a double precision floating point argument */
ArgReader& popDouble(double& d) { return popPod<double>(TYPE_TAG_DOUBLE, d); }
/** retrieve a string argument (no check performed on its content, so it may contain any byte value except 0) */
ArgReader& popStr(std::string& s)
{
if (precheck(TYPE_TAG_STRING)) { s = argBeg(arg_idx++); }
return *this;
}
/** retrieve a binary blob */
ArgReader& popBlob(std::vector<char>& b)
{
if (precheck(TYPE_TAG_BLOB))
{
b.assign(argBeg(arg_idx) + 4, argEnd(arg_idx));
++arg_idx;
}
return *this;
}
/** retrieve a bool argument */
ArgReader& popBool(bool& b)
{
b = false;
if (arg_idx >= msg->arguments.size()) { OSCPKT_SET_ERR(NOT_ENOUGH_ARG); }
else if (currentTypeTag() == TYPE_TAG_TRUE) { b = true; }
else if (currentTypeTag() == TYPE_TAG_FALSE) { b = false; }
else { OSCPKT_SET_ERR(TYPE_MISMATCH); }
++arg_idx;
return *this;
}
/** skip whatever comes next */
ArgReader& pop()
{
if (arg_idx >= msg->arguments.size()) { OSCPKT_SET_ERR(NOT_ENOUGH_ARG); }
else { ++arg_idx; }
return *this;
}
private:
const char* argBeg(const size_t idx) const
{
if (err || idx >= msg->arguments.size()) { return nullptr; }
return msg->storage.begin() + msg->arguments[idx].first;
}
const char* argEnd(const size_t idx) const
{
if (err || idx >= msg->arguments.size()) { return nullptr; }
return msg->storage.begin() + msg->arguments[idx].first + msg->arguments[idx].second;
}
int currentTypeTag()
{
if (!err && arg_idx < msg->type_tags.size()) { return msg->type_tags[arg_idx]; }
OSCPKT_SET_ERR(NOT_ENOUGH_ARG);
return -1;
}
template <typename POD>
ArgReader& popPod(const int tag, POD& v)
{
if (precheck(tag))
{
v = bytes2pod<POD>(argBeg(arg_idx));
++arg_idx;
}
else { v = POD(0); }
return *this;
}
/* pre-check stuff before popping an argument from the message */
bool precheck(const int tag)
{
if (arg_idx >= msg->arguments.size()) { OSCPKT_SET_ERR(NOT_ENOUGH_ARG); }
else if (!err && currentTypeTag() != tag) { OSCPKT_SET_ERR(TYPE_MISMATCH); }
return err == OK_NO_ERROR;
}
};
Message() { clear(); }
Message(const std::string& s, const TimeTag tt = TimeTag::immediate()) : time_tag(tt), address(s), err(OK_NO_ERROR) {}
Message(const void* ptr, const size_t sz, const TimeTag tt = TimeTag::immediate())
{
buildFromRawData(ptr, sz);
time_tag = tt;
}
bool isOk() const { return err == OK_NO_ERROR; }
ErrorCode getErr() const { return err; }
/** return the type_tags string, with its initial ',' stripped. */
const std::string& typeTags() const { return type_tags; }
/** retrieve the address pattern. If you want to follow to the whole OSC spec, you
have to handle its matching rules for address specifications -- this file does
not provide this functionality */
const std::string& addressPattern() const { return address; }
TimeTag timeTag() const { return time_tag; }
/** clear the message and start a new message with the supplied address and time_tag. */
Message& init(const std::string& addr, const TimeTag tt = TimeTag::immediate())
{
clear();
address = addr;
time_tag = tt;
if (address.empty() || address[0] != '/') { OSCPKT_SET_ERR(MALFORMED_ADDRESS_PATTERN); }
return *this;
}
/** start a matching test. The typical use-case is to follow this by
a sequence of calls to popXXX() and a final call to
isOkNoMoreArgs() which will allow to check that everything went
fine. For example:
@code
if (msg.match("/foo").popInt32(i).isOkNoMoreArgs()) { blah(i); }
else if (msg.match("/bar").popStr(s).popInt32(i).isOkNoMoreArgs()) { plop(s,i); }
else std::cerr << "unhandled message: " << msg << "\n";
@endcode
*/
ArgReader match(const std::string& test) const { return ArgReader(*this, fullPatternMatch(address, test) ? OK_NO_ERROR : PATTERN_MISMATCH); }
/** return true if the 'test' path matched by the first characters of addressPattern().
For ex. ("/foo/bar").partialMatch("/foo/") is true */
ArgReader partialMatch(const std::string& test) const { return ArgReader(*this, partialPatternMatch(address, test) ? OK_NO_ERROR : PATTERN_MISMATCH); }
ArgReader arg() const { return ArgReader(*this, OK_NO_ERROR); }
/** build the osc message for raw data (the message will keep a copy of that data) */
void buildFromRawData(const void* ptr, const size_t sz)
{
clear();
storage.assign((const char*)ptr, (const char*)ptr + sz);
const char* address_beg = storage.begin();
const char* address_end = (const char*)memchr(address_beg, 0, storage.end() - address_beg);
if (!address_end || !isZeroPaddingCorrect(address_end + 1) || address_beg[0] != '/')
{
OSCPKT_SET_ERR(MALFORMED_ADDRESS_PATTERN);
return;
}
address.assign(address_beg, address_end);
const char* type_tags_beg = ceil4(address_end + 1);
const char* type_tags_end = (const char*)memchr(type_tags_beg, 0, storage.end() - type_tags_beg);
if (!type_tags_end || !isZeroPaddingCorrect(type_tags_end + 1) || type_tags_beg[0] != ',')
{
OSCPKT_SET_ERR(MALFORMED_TYPE_TAGS);
return;
}
type_tags.assign(type_tags_beg + 1, type_tags_end);
// we do not copy the initial ','
const char* arg = ceil4(type_tags_end + 1);
assert(arg <= storage.end());
size_t iarg = 0;
while (isOk() && iarg < type_tags.size())
{
assert(arg <= storage.end());
size_t len = getArgSize(type_tags[iarg], arg);
if (isOk()) { arguments.push_back(std::make_pair(arg - storage.begin(), len)); }
arg += ceil4(len);
++iarg;
}
if (iarg < type_tags.size() || arg != storage.end()) { OSCPKT_SET_ERR(MALFORMED_ARGUMENTS); }
}
/* below are all the functions that serve when *writing* a message */
Message& pushBool(const bool b)
{
type_tags += (b ? TYPE_TAG_TRUE : TYPE_TAG_FALSE);
arguments.push_back(std::make_pair(storage.size(), storage.size()));
return *this;
}
Message& pushInt32(const int32_t i) { return pushPod(TYPE_TAG_INT32, i); }
Message& pushInt64(const int64_t h) { return pushPod(TYPE_TAG_INT64, h); }
Message& pushFloat(const float f) { return pushPod(TYPE_TAG_FLOAT, f); }
Message& pushDouble(const double d) { return pushPod(TYPE_TAG_DOUBLE, d); }
Message& pushStr(const std::string& s)
{
assert(s.size() < 2147483647); // insane values are not welcome
type_tags += TYPE_TAG_STRING;
arguments.push_back(std::make_pair(storage.size(), s.size() + 1));
strcpy(storage.getBytes(s.size() + 1), s.c_str());
return *this;
}
Message& pushBlob(void* ptr, const size_t size)
{
assert(size < 2147483647); // insane values are not welcome
type_tags += TYPE_TAG_BLOB;
arguments.push_back(std::make_pair(storage.size(), size + 4));
pod2bytes<int32_t>(int32_t(size), storage.getBytes(4));
if (size) { memcpy(storage.getBytes(size), ptr, size); }
return *this;
}
/** reset the message to a clean state */
void clear()
{
address.clear();
type_tags.clear();
storage.clear();
arguments.clear();
err = OK_NO_ERROR;
time_tag = TimeTag::immediate();
}
/** write the raw message data (used by PacketWriter) */
void packMessage(Storage& s, const bool size) const
{
if (!isOk()) { return; }
const size_t addr = address.size() + 1;
const size_t type = type_tags.size() + 2;
if (size) { pod2bytes<uint32_t>(uint32_t(ceil4(addr) + ceil4(type) + ceil4(storage.size())), s.getBytes(4)); }
strcpy(s.getBytes(addr), address.c_str());
strcpy(s.getBytes(type), ("," + type_tags).c_str());
if (storage.size()) { memcpy(s.getBytes(storage.size()), const_cast<Storage&>(storage).begin(), storage.size()); }
}
private:
/* get the number of bytes occupied by the argument */
size_t getArgSize(const int type, const char* p)
{
if (err) { return 0; }
size_t sz = 0;
assert(p >= storage.begin() && p <= storage.end());
switch (type)
{
case TYPE_TAG_TRUE:
case TYPE_TAG_FALSE: sz = 0;
break;
case TYPE_TAG_INT32:
case TYPE_TAG_FLOAT: sz = 4;
break;
case TYPE_TAG_INT64:
case TYPE_TAG_DOUBLE: sz = 8;
break;
case TYPE_TAG_STRING:
{
const char* q = (const char*)memchr(p, 0, storage.end() - p);
if (!q) { OSCPKT_SET_ERR(MALFORMED_ARGUMENTS); }
else { sz = (q - p) + 1; }
}
break;
case TYPE_TAG_BLOB:
{
if (p == storage.end())
{
OSCPKT_SET_ERR(MALFORMED_ARGUMENTS);
return 0;
}
sz = 4 + bytes2pod<uint32_t>(p);
}
break;
default:
{
OSCPKT_SET_ERR(UNHANDLED_TYPE_TAGS);
return 0;
}
}
if (p + sz > storage.end() || /* string or blob too large.. */
p + sz < p /* or even blob so large that it did overflow */)
{
OSCPKT_SET_ERR(MALFORMED_ARGUMENTS);
return 0;
}
if (!isZeroPaddingCorrect(p + sz))
{
OSCPKT_SET_ERR(MALFORMED_ARGUMENTS);
return 0;
}
return sz;
}
template <typename POD>
Message& pushPod(const int tag, POD v)
{
type_tags += char(tag);
arguments.push_back(std::make_pair(storage.size(), sizeof(POD)));
pod2bytes(v, storage.getBytes(sizeof(POD)));
return *this;
}
#ifdef OSCPKT_OSTREAM_OUTPUT
friend std::ostream &operator<<(std::ostream &os, const Message &msg) {
os << "osc_address: '" << msg.address << "', types: '" << msg.type_tags << "', timetag=" << msg.time_tag << ", args=[";
Message::ArgReader arg(msg);
while (arg.nbArgRemaining() && arg.isOk()) {
if (arg.isBool()) { bool b; arg.popBool(b); os << (b?"True":"False"); }
else if (arg.isInt32()) { int32_t i; arg.popInt32(i); os << i; }
else if (arg.isInt64()) { int64_t h; arg.popInt64(h); os << h << "ll"; }
else if (arg.isFloat()) { float f; arg.popFloat(f); os << f << "f"; }
else if (arg.isDouble()) { double d; arg.popDouble(d); os << d; }
else if (arg.isStr()) { std::string s; arg.popStr(s); os << "'" << s << "'"; }
else if (arg.isBlob()) { std::vector<char> b; arg.popBlob(b); os << "Blob " << b.size() << " bytes"; }
else {
assert(0); // I forgot a case..
}
if (arg.nbArgRemaining()) os << ", ";
}
if (!arg.isOk()) { os << " ERROR#" << arg.getErr(); }
os << "]";
return os;
}
#endif
};
/**
parse an OSC packet and extracts the embedded OSC messages.
*/
class PacketReader
{
public:
PacketReader() { err = OK_NO_ERROR; }
/** pointer and size of the osc packet to be parsed. */
PacketReader(const void* ptr, size_t sz) { init(ptr, sz); }
void init(const void* ptr, const size_t sz)
{
err = OK_NO_ERROR;
messages.clear();
if ((sz % 4) == 0) { parse((const char*)ptr, (const char*)ptr + sz, TimeTag::immediate()); }
else { OSCPKT_SET_ERR(INVALID_PACKET_SIZE); }
it_messages = messages.begin();
}
/** extract the next osc message from the packet. return 0 when all messages have been read, or in case of error. */
Message* popMessage()
{
if (!err && !messages.empty() && it_messages != messages.end()) { return &*it_messages++; }
return nullptr;
}
bool isOk() const { return err == OK_NO_ERROR; }
ErrorCode getErr() const { return err; }
private:
std::list<Message> messages;
std::list<Message>::iterator it_messages;
ErrorCode err;
void parse(const char* beg, const char* end, const TimeTag time_tag)
{
assert(beg <= end && !err);
assert(((end-beg)%4)==0);
if (beg == end) { return; }
if (*beg == '#')
{
/* it's a bundle */
if (end - beg >= 20
&& memcmp(beg, "#bundle\0", 8) == 0)
{
const TimeTag timeTag2(bytes2pod<uint64_t>(beg + 8));
const char* pos = beg + 16;
do
{
const uint32_t sz = bytes2pod<uint32_t>(pos);
pos += 4;
if ((sz & 3) != 0 || pos + sz > end || pos + sz < pos) { OSCPKT_SET_ERR(INVALID_BUNDLE); }
else
{
parse(pos, pos + sz, timeTag2);
pos += sz;
}
} while (!err && pos != end);
}
else { OSCPKT_SET_ERR(INVALID_BUNDLE); }
}
else
{
messages.push_back(Message(beg, end - beg, time_tag));
if (!messages.back().isOk()) { OSCPKT_SET_ERR(messages.back().getErr()); }
}
}
};
/**
Assemble messages into an OSC packet. Example of use:
@code
PacketWriter pkt;
Message msg;
pkt.startBundle();
pkt.addMessage(msg.init("/foo").pushBool(true).pushStr("plop").pushFloat(3.14f));
pkt.addMessage(msg.init("/bar").pushBool(false));
pkt.endBundle();
if (pkt.isOk()) {
send(pkt.data(), pkt.size());
}
@endcode
*/
class PacketWriter
{
public:
PacketWriter() { init(); }
PacketWriter& init()
{
err = OK_NO_ERROR;
storage.clear();
bundles.clear();
return *this;
}
/** begin a new bundle. If you plan to pack more than one message in the Osc packet, you have to
put them in a bundle. Nested bundles inside bundles are also allowed. */
PacketWriter& startBundle(const TimeTag ts = TimeTag::immediate())
{
char* p;
if (!bundles.empty()) { p = storage.getBytes(4); } // hold the bundle size
p = storage.getBytes(8);
strcpy(p, "#bundle");
bundles.push_back(p - storage.begin());
p = storage.getBytes(8);
pod2bytes<uint64_t>(ts, p);
return *this;
}
/** close the current bundle. */
PacketWriter& endBundle()
{
if (!bundles.empty())
{
if (storage.size() - bundles.back() == 16) { pod2bytes<uint32_t>(0, storage.getBytes(4)); } // the 'empty bundle' case, not very elegant
if (bundles.size() > 1) // no size stored for the top-level bundle
{
pod2bytes<uint32_t>(uint32_t(storage.size() - bundles.back()), storage.begin() + bundles.back() - 4);
}
bundles.pop_back();
}
else { OSCPKT_SET_ERR(INVALID_BUNDLE); }
return *this;
}
/** insert an Osc message into the current bundle / packet.
*/
PacketWriter& addMessage(const Message& msg)
{
if (storage.size() != 0 && bundles.empty()) { OSCPKT_SET_ERR(BUNDLE_REQUIRED_FOR_MULTI_MESSAGES); }
else { msg.packMessage(storage, !bundles.empty()); }
if (!msg.isOk()) { OSCPKT_SET_ERR(msg.getErr()); }
return *this;
}
/** the error flag will be raised if an opened bundle is not closed, or if more than one message is
inserted in the packet without a bundle */
bool isOk() const { return err == OK_NO_ERROR; }
ErrorCode getErr() const { return err; }
/** return the number of bytes of the osc packet -- will always be a
multiple of 4 -- returns 0 if the construction of the packet has
failed. */
size_t packetSize() const { return err ? 0 : storage.size(); }
/** return the bytes of the osc packet (NULL if the construction of the packet has failed) */
char* packetData() { return err ? nullptr : storage.begin(); }
private:
std::vector<size_t> bundles; // hold the position in the storage array of the beginning marker of each bundle
Storage storage;
ErrorCode err;
};
// see the OSC spec for the precise pattern matching rules
inline const char* internalPatternMatch(const char* pattern, const char* path)
{
while (*pattern)
{
const char* p = pattern;
if (*p == '?' && *path)
{
++p;
++path;
}
else if (*p == '[' && *path)
{ // bracketted range, e.g. [a-zABC]
++p;
bool reverse = false;
if (*p == '!')
{
reverse = true;
++p;
}
bool match = reverse;
for (; *p && *p != ']'; ++p)
{
const char c0 = *p;
char c1 = c0;
if (p[1] == '-' && p[2])
{
p += 2;
c1 = *p;
}
if (*path >= c0 && *path <= c1) { match = !reverse; }
}
if (!match || *p != ']') { return pattern; }
++p;
++path;
}
else if (*p == '*')
{ // wildcard '*'
while (*p == '*') { ++p; }
const char* best = nullptr;
while (true)
{
const char* ret = internalPatternMatch(p, path);
if (ret && ret > best) { best = ret; }
if (*path == 0 || *path == '/') { break; }
++path;
}
return best;
}
else if (*p == '/' && *(p + 1) == '/')
{ // the super-wildcard '//'
while (*(p + 1) == '/') { ++p; }
const char* best = nullptr;
while (true)
{
const char* ret = internalPatternMatch(p, path);
if (ret && ret > best) { best = ret; }
if (*path == 0) { break; }
if (*path == 0 || (path = strchr(path + 1, '/')) == nullptr) { break; }
}
return best;
}
else if (*p == '{')
{ // braced list {foo,bar,baz}
const char *end = strchr(p, '}'), *q;
if (!end) { return nullptr; } // syntax error in brace list..
bool match = false;
do
{
++p;
q = strchr(p, ',');
if (q == nullptr || q > end) { q = end; }
if (strncmp(p, path, q - p) == 0)
{
path += (q - p);
p = end + 1;
match = true;
}
else { p = q; }
} while (q != end && !match);
if (!match) { return pattern; }
}
else if (*p == *path)
{
++p;
++path;
} // any other character
else { break; }
pattern = p;
}
return (*path == 0 ? pattern : nullptr);
}
inline bool partialPatternMatch(const std::string& pattern, const std::string& path)
{
const char* q = internalPatternMatch(pattern.c_str(), path.c_str());
return q != nullptr;
}
inline bool fullPatternMatch(const std::string& pattern, const std::string& path)
{
const char* q = internalPatternMatch(pattern.c_str(), path.c_str());
return q && *q == 0;
}
} // namespace oscpkt
@@ -0,0 +1,345 @@
/*
This file provides a dumb c++ wrapper for sending OSC packets over UDP.
*/
/* Copyright (C) 2010 Julien Pommier
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
(this is the zlib license)
*/
#pragma once
#if defined(_MSC_VER) || defined(WIN32)
/*
if windows.h has been already included, be prepared for tons of
compile errors. winsock2 must be included BEFORE windows.h . -- OR
define WIN32_LEAN_AND_MEAN before the first #include <windows.h> to
prevent it from including tons of crap (winsock.h etc)
*/
#include <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>
#if defined(_MSC_VER)
# pragma comment(lib, "ws2_32.lib")
#endif
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/time.h>
#include <unistd.h>
#endif
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <cerrno>
#include <cassert>
#include <string>
#include <vector>
namespace oscpkt {
/** a wrapper class for holding an ip address, mostly used internnally */
class SockAddr
{
union
{
sockaddr_storage ss; // hold an IPv4 or IPv6 address
struct sockaddr sa;
} addr_;
public:
struct sockaddr& addr() { return addr_.sa; }
const struct sockaddr& addr() const { return addr_.sa; }
size_t maxLen() const { return sizeof addr_; }
size_t actualLen() const
{
if (addr().sa_family == AF_UNSPEC) { return 0; }
if (addr().sa_family == AF_INET) { return sizeof(struct sockaddr_in); }
if (addr().sa_family == AF_INET6) { return sizeof(struct sockaddr_in6); }
return sizeof addr_;
}
SockAddr() { memset(&addr_, 0, sizeof addr_); }
bool empty() const
{
return addr().sa_family == AF_UNSPEC; /* this is the 0 value */
}
/** retrieve the current port number, -1 in case of error */
int getPort() const
{
char servname[512];
const int err = getnameinfo(&addr_.sa, sizeof addr_, nullptr, 0, servname, sizeof servname, NI_NUMERICSERV);
return (err == 0 ? atoi(servname) : -1);
}
/* convert to a string representation (ip:port) */
std::string asString() const
{
std::string s;
if (addr().sa_family)
{
char hostname[512], servname[512];
const int err = getnameinfo(&addr_.sa, sizeof addr_, hostname, sizeof hostname, servname, sizeof servname, NI_NUMERICHOST | NI_NUMERICSERV);
if (err == 0)
{
s = hostname;
s += ":";
s += servname;
}
}
return s;
}
/* NOTE: This breaks build with OpenViBE
friend std::ostream &operator<<(std::ostream &os, const SockAddr &ip) {
os << "[";
switch (ip.addr().sa_family) {
case AF_UNSPEC: os << "AF_UNSPEC"; break;
case AF_INET: os << "IPv4"; break;
case AF_INET6: os << "IPv6"; break;
default: os << "unknown family '" << ip.addr().sa_family << "'"; break;
}
os << " " << ip.asString() << "]";
return os;
}*/
};
/**
just a wrapper over the classical socket stuff
should be robust, simple to use, IPv6 ready (avoids all deprecated
stuff such as gethostbyname etc), and portable (mac/linux/windows)
Try to avoid sending packets larger than 8192 because some other
implementation may truncate them (python's DatagramRequestHandler
of OSC.py for example).
*/
struct UdpSocket
{
std::string error_message;
int handle; /* the file descriptor for the socket */
SockAddr local_addr /* initialised only for bound sockets */;
SockAddr remote_addr; /* initialised for connected sockets. Also updated for bound sockets after each datagram received */
std::vector<char> buffer;
UdpSocket() : handle(-1)
{
#ifdef WIN32
WSADATA wsa_data;
if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0) { setErr("winsock failed to initialise"); }
#endif
}
~UdpSocket()
{
close();
#ifdef WIN32
WSACleanup();
#endif
}
void close()
{
if (handle != -1)
{
#ifdef WIN32
closesocket(handle);
#else
::close(handle);
#endif
handle = -1;
}
}
bool isOk() const { return error_message.empty(); }
const std::string& errorMessage() const { return error_message; }
bool isBound() const { return !local_addr.empty(); }
int boundPort() const { return local_addr.getPort(); }
std::string boundPortAsString() const
{
char s[512];
#ifndef _MSC_VER
snprintf(s, 512, "%d", boundPort());
#else
_snprintf_s(s, 512, 512, "%d", boundPort());
#endif
return s;
}
int socketHandle() const { return handle; }
static std::string localHostName()
{
/* this stuff is not very nice but this is what liblo does in order to
find out a sensible name for the local host */
char hostname_buf[512];
if (gethostname(hostname_buf, sizeof hostname_buf) != 0) { hostname_buf[0] = 0; }
hostname_buf[sizeof hostname_buf - 1] = 0;
struct hostent* host = gethostbyname(hostname_buf);
if (host) { return host->h_name; }
return hostname_buf[0] ? hostname_buf : "localhost";
}
std::string localHostNameWithPort() const { return (localHostName() + ":") + boundPortAsString(); }
enum
{
OPTION_UNSPEC = 0,
OPTION_FORCE_IPV4 = 1,
OPTION_FORCE_IPV6 = 2,
OPTION_DEFAULT = OPTION_FORCE_IPV4 // according to liblo's README, using ipv6 sockets causes issues with other non-ipv6 enabled osc software
};
/** open the socket, and prepare for sending datagrams to the specified host:port */
bool connectTo(const std::string& host, const std::string& port, const int options = OPTION_DEFAULT) { return openSocket(host, port, options); }
bool connectTo(const std::string& host, const int port, const int options = OPTION_DEFAULT) { return openSocket(host, port, options); }
void setErr(const std::string& msg) { if (error_message.empty()) { error_message = msg; } }
void* packetData() { return buffer.empty() ? nullptr : &buffer[0]; }
size_t packetSize() const { return buffer.size(); }
SockAddr& packetOrigin() { return remote_addr; }
bool sendPacket(const void* ptr, const size_t sz) { return sendPacketTo(ptr, sz, remote_addr); }
bool sendPacketTo(const void* ptr, const size_t sz, SockAddr& addr)
{
if (!isOk() || handle == -1)
{
setErr("not opened..");
return false;
}
if (!ptr || sz == 0) { return false; }
int sent = 0;
do
{
int res;
errno = 0;
if (isBound()) { res = sendto(handle, (const char*)ptr, int(sz), 0, &addr.addr(), int(addr.actualLen())); }
else
{
res = send(handle, (const char*)ptr, int(sz), 0);
// res = write(handle, ptr, sz);
}
#ifdef WIN32
if (res == -1 && WSAGetLastError() == WSAEINTR) { continue; }
sent = res;
#else
//if (res == -1) std::cerr << "sendto handle=" << handle << ", res:" << res << ", sz=" << sz << ", errno=" << errno << " " << strerror(errno) << "\n";
if (res == -1 && errno == EINTR) continue;
else sent = res;
#endif
} while (false);
return size_t(sent) == sz;
}
private:
bool openSocket(const std::string& hostname, const int port, const int options)
{
char port_string[64];
#ifdef _MSC_VER
_snprintf_s(port_string, 64, 64, "%d", port);
#else
snprintf(port_string, 64, "%d", port);
#endif
return openSocket(hostname, port_string, options);
}
bool openSocket(const std::string& hostname, const std::string& port, const int options)
{
const bool binding = hostname.empty();
close();
error_message.clear();
struct addrinfo hints;
struct addrinfo* result = nullptr;
memset(&hints, 0, sizeof(struct addrinfo));
if (options == OPTION_FORCE_IPV4) { hints.ai_family = AF_INET; }
else if (options == OPTION_FORCE_IPV6) { hints.ai_family = AF_INET6; }
else { hints.ai_family = AF_UNSPEC; } // Allow IPv4 or IPv6 -- in case of problem, try with AF_INET ...
hints.ai_socktype = SOCK_DGRAM; // Datagram socket
hints.ai_flags = (binding ? AI_PASSIVE : 0); // AI_PASSIVE means socket address is intended for bind
const int err = getaddrinfo(binding ? nullptr : hostname.c_str(), port.empty() ? nullptr : port.c_str(), &hints, &result);
if (err != 0)
{
setErr(gai_strerror(err));
return false;
}
struct addrinfo* rp = result;
for (; rp && handle == -1; rp = rp->ai_next)
{
handle = socket(rp->ai_family, rp->ai_socktype,
rp->ai_protocol);
if (handle == -1) { continue; }
if (binding)
{
if (bind(handle, rp->ai_addr, socklen_t(rp->ai_addrlen)) != 0) { close(); }
else
{
socklen_t len = socklen_t(local_addr.maxLen());
if (getsockname(handle, &local_addr.addr(), &len) == 0)
{
/* great */
}
break;
}
}
else
{
if (connect(handle, rp->ai_addr, socklen_t(rp->ai_addrlen)) != 0) { close(); }
else
{
assert(size_t(rp->ai_addrlen) <= sizeof remote_addr);
memcpy(&remote_addr.addr(), rp->ai_addr, rp->ai_addrlen);
break;
}
}
}
freeaddrinfo(result);
result = nullptr;
if (!rp)
{ // we failed miserably
setErr(binding ? "bind failed" : "connect failed");
assert(handle == -1);
return false;
}
return true;
}
};
} // namespace oscpkt
@@ -0,0 +1,173 @@
#include "ovpCBoxAlgorithmOSCController.h"
// #include <iostream>
namespace OpenViBE {
namespace Plugins {
namespace NetworkIO {
bool CBoxAlgorithmOSCController::initialize()
{
const CString address = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
const uint64_t port = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1);
m_oscAddress = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2);
const char* tmp = m_oscAddress.toASCIIString();
if (!tmp || !tmp[0] || tmp[0] != '/')
{
this->getLogManager() << Kernel::LogLevel_Error << "OSC Address must start with a '/'\n";
return false;
}
// Connect the socket
const std::string str = std::string(address.toASCIIString());
m_udpSocket.connectTo(str, uint32_t(port));
if (!m_udpSocket.isOk())
{
this->getLogManager() << Kernel::LogLevel_Error << "Error connecting to socket\n";
return false;
}
// Get appropriate decoder
CIdentifier streamType;
this->getStaticBoxContext().getInputType(0, streamType);
m_decoder = nullptr;
if (this->getTypeManager().isDerivedFromStream(streamType,OV_TypeId_StreamedMatrix))
{
m_decoder = &this->getAlgorithmManager().getAlgorithm(
this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_StreamedMatrixDecoder));
}
else if (streamType == OV_TypeId_Stimulations)
{
m_decoder = &this->getAlgorithmManager().getAlgorithm(
this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_StimulationDecoder));
}
else
{
this->getLogManager() << Kernel::LogLevel_Error << "Unsupported type\n";
return false;
}
m_decoder->initialize();
return true;
}
bool CBoxAlgorithmOSCController::uninitialize()
{
if (m_udpSocket.isOk()) { m_udpSocket.close(); }
if (m_decoder)
{
this->getAlgorithmManager().releaseAlgorithm(*m_decoder);
m_decoder = nullptr;
}
return true;
}
bool CBoxAlgorithmOSCController::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmOSCController::process()
{
// the dynamic box context describes the current state of the box inputs and outputs (i.e. the chunks)
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
CIdentifier streamType;
this->getStaticBoxContext().getInputType(0, streamType);
oscpkt::PacketWriter pw;
oscpkt::Message msg;
bool haveData = false;
for (size_t j = 0; j < boxContext.getInputChunkCount(0); ++j)
{
if (this->getTypeManager().isDerivedFromStream(streamType,OV_TypeId_StreamedMatrix))
{
Kernel::TParameterHandler<const IMemoryBuffer*> ip_buffer(
m_decoder->getInputParameter(OVP_GD_Algorithm_StreamedMatrixDecoder_InputParameterId_MemoryBufferToDecode));
Kernel::TParameterHandler<const CMatrix*> op_pMatrix(
m_decoder->getOutputParameter(OVP_GD_Algorithm_StreamedMatrixDecoder_OutputParameterId_Matrix));
ip_buffer = boxContext.getInputChunk(0, j);
m_decoder->process();
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_StreamedMatrixDecoder_OutputTriggerId_ReceivedBuffer))
{
// Check that the dimensions are acceptable
const CMatrix* matrix = op_pMatrix;
if (matrix->getDimensionCount() < 1 || matrix->getDimensionCount() > 2)
{
this->getLogManager() << Kernel::LogLevel_Error << "Only matrixes of 1 or 2 dimensions are supported\n";
return false;
}
if (matrix->getDimensionCount() == 2 && matrix->getDimensionSize(0) != 1)
{
this->getLogManager() << Kernel::LogLevel_Error << "The matrix should have only 1 channel. Use e.g. Channel Selector to prune\n";
return false;
}
if (!haveData)
{
haveData = true;
pw.startBundle();
}
for (size_t k = 0; k < matrix->getBufferElementCount(); ++k)
{
const float inputVal = float(matrix->getBuffer()[k]);
pw.addMessage(msg.init(m_oscAddress.toASCIIString()).pushFloat(inputVal));
// std::cout << "Add float " << inputVal << "\n";
}
}
}
else if (streamType == OV_TypeId_Stimulations)
{
Kernel::TParameterHandler<const IMemoryBuffer*> ip_buffer(
m_decoder->getInputParameter(OVP_GD_Algorithm_StimulationDecoder_InputParameterId_MemoryBufferToDecode));
const Kernel::TParameterHandler<const IStimulationSet*> op_pStimulationSet(
m_decoder->getOutputParameter(OVP_GD_Algorithm_StimulationDecoder_OutputParameterId_StimulationSet));
ip_buffer = boxContext.getInputChunk(0, j);
m_decoder->process();
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_StimulationDecoder_OutputTriggerId_ReceivedBuffer))
{
if (!haveData)
{
haveData = true;
pw.startBundle();
}
for (size_t k = 0; k < op_pStimulationSet->getStimulationCount(); ++k)
{
const uint64_t stimulus = op_pStimulationSet->getStimulationIdentifier(k);
pw.addMessage(msg.init(m_oscAddress.toASCIIString()).pushInt32(int32_t(stimulus)));
// std::cout << "Add stimulus " << stimulus << "\n";
}
}
}
else
{
this->getLogManager() << Kernel::LogLevel_Error << "Unknown stream type\n";
return false;
}
boxContext.markInputAsDeprecated(0, j);
}
if (haveData)
{
pw.endBundle();
if (!m_udpSocket.sendPacket(pw.packetData(), pw.packetSize())) { this->getLogManager() << Kernel::LogLevel_Warning << "Error sending out UDP packet\n"; }
}
return true;
}
} // namespace NetworkIO
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,108 @@
#pragma once
#include "../../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include "oscpkt.h"
#include "oscpkt_udp.h"
namespace OpenViBE {
namespace Plugins {
namespace NetworkIO {
/**
* \class CBoxAlgorithmOSCController
* \author Ozan Caglayan (Galatasaray University)
* \date Thu May 8 20:57:24 2014
* \brief The class CBoxAlgorithmOSCController describes the box OSC Controller.
*
*/
class CBoxAlgorithmOSCController final : virtual public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
{
public:
void release() override { delete this; }
bool initialize() override;
bool uninitialize() override;
//Here is the different process callbacks possible
// - On new input received (the most common behaviour for signal processing) :
bool processInput(const size_t index) override;
bool process() override;
// As we do with any class in openvibe, we use the macro below
// to associate this box to an unique identifier.
// The inheritance information is also made available,
// as we provide the superclass Toolkit::TBoxAlgorithm < IBoxAlgorithm >
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_OSCController)
private:
// Decodes the stream
Kernel::IAlgorithmProxy* m_decoder = nullptr;
// UDP Socket (oscpkt_udp.h)
oscpkt::UdpSocket m_udpSocket;
// OSC Address to some device
CString m_oscAddress;
};
/**
* \class CBoxAlgorithmOSCControllerDesc
* \author Ozan Caglayan (Galatasaray University)
* \date Thu May 8 20:57:24 2014
* \brief Descriptor of the box OSC Controller.
*
*/
class CBoxAlgorithmOSCControllerDesc final : virtual public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("OSC Controller"); }
CString getAuthorName() const override { return CString("Ozan Caglayan"); }
CString getAuthorCompanyName() const override { return CString("Galatasaray University"); }
// + Stimulation support & some code refactoring in v1.1 by Jussi T. Lindgren / Inria
CString getShortDescription() const override { return CString("Sends OSC messages to an OSC controller"); }
CString getDetailedDescription() const override
{
return CString(
"This box allows OpenViBE to send OSC (Open Sound Control) messages to an OSC server. See http://www.opensoundcontrol.org to learn about the OSC protocol and its use cases.");
}
CString getCategory() const override { return CString("Acquisition and network IO"); }
CString getVersion() const override { return CString("1.1"); }
CString getStockItemName() const override { return CString("gtk-network"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_OSCController; }
IPluginObject* create() override { return new CBoxAlgorithmOSCController; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input",OV_TypeId_Signal);
prototype.addInputSupport(OV_TypeId_Signal);
prototype.addInputSupport(OV_TypeId_StreamedMatrix);
prototype.addInputSupport(OV_TypeId_Stimulations);
prototype.addFlag(Kernel::BoxFlag_CanModifyInput);
prototype.addSetting("OSC Server IP",OV_TypeId_String, "127.0.0.1");
prototype.addSetting("OSC Server Port",OV_TypeId_Integer, "9001");
prototype.addSetting("OSC Address",OV_TypeId_String, "/a/b/c");
prototype.addFlag(OV_AttributeId_Box_FlagIsUnstable);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_OSCControllerDesc)
};
} // namespace NetworkIO
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,184 @@
#if defined TARGET_HAS_ThirdPartyLSL
#include "ovpCBoxLSLExportGipsa.h"
namespace OpenViBE {
namespace Plugins {
namespace NetworkIO {
bool CBoxAlgorithmLSLExportGipsa::initialize()
{
m_inputChannel1.initialize(this);
m_streamName = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
m_streamType = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1);
m_outlet = nullptr;
m_stims.clear();
return true;
}
bool CBoxAlgorithmLSLExportGipsa::uninitialize()
{
m_inputChannel1.uninitialize();
m_stims.clear();
delete m_outlet;
return true;
}
bool CBoxAlgorithmLSLExportGipsa::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmLSLExportGipsa::process()
{
if (!m_inputChannel1.isWorking())
{
m_inputChannel1.waitForSignalHeader();
if (m_inputChannel1.isWorking())
{
try
{
//if it fails here then most likely you are using the wrong dll - e.x debug instead of release or vice-versa
lsl::stream_info info(m_streamName.toASCIIString(), m_streamType.toASCIIString(), int(m_inputChannel1.getNChannels()) + 1,
double(m_inputChannel1.getSamplingRate()), lsl::cf_float32);
lsl::xml_element channels = info.desc().append_child("channels");
for (size_t i = 0; i < m_inputChannel1.getNChannels(); ++i)
{
channels.append_child("channel")
.append_child_value("label", m_inputChannel1.getChannelName(i))
.append_child_value("type", "EEG")
.append_child_value("unit", "microvolts");
}
channels.append_child("channel")
.append_child_value("label", "Stimulations")
.append_child_value("type", "marker");
if (m_outlet != nullptr) { this->getLogManager() << Kernel::LogLevel_Error << "Possible double initialization!\n"; }
m_outlet = new lsl::stream_outlet(info); //here the length of the buffered signal can be specified
}
catch (std::exception& e)
{
this->getLogManager() << Kernel::LogLevel_Error << "Could not initialize LSL library: " << e.what() << "\n";
return false;
}
}
}
else
{
//stimulations
for (size_t i = 0; i < m_inputChannel1.getNStimulationBuffers(); ++i)
{
uint64_t tStart, tEnd;
IStimulationSet* set = m_inputChannel1.getStimulation(tStart, tEnd, i);
for (size_t j = 0; j < set->getStimulationCount(); ++j)
{
uint64_t time = m_inputChannel1.getStartTimestamp() + set->getStimulationDate(j);
const uint64_t identifier = set->getStimulationIdentifier(j);
if (m_stims.empty())
{
m_stims.push_back(std::pair<float, uint64_t>(float(identifier), time));
//std::cout<< "added: " << m_stims[m_stims.size()-1].first << " " << m_stims[m_stims.size()-1].second<< "\n";
}
else
{
const auto last = m_stims[m_stims.size() - 1];
if (last.first != identifier && last.second != time)
{
m_stims.push_back(std::pair<float, uint64_t>(float(identifier), time));
//std::cout<< "added: " << m_stims[m_stims.size()-1].first << " " << m_stims[m_stims.size()-1].second<< "\n";
}
else
{
//std::cout<< "duplicate: " << m_stims[m_stims.size()-1].first << " " << m_stims[m_stims.size()-1].second<< "\n";
}
}
}
}
//signal
for (size_t i = 0; i < m_inputChannel1.getNSignalBuffers(); ++i)
{
uint64_t tStart, tEnd;
double* inputBuffer = m_inputChannel1.getSignal(tStart, tEnd, i);
if (inputBuffer)
{
const size_t samplesPerChannelInput = m_inputChannel1.getNSamples();
std::vector<std::vector<float>> mychunk(samplesPerChannelInput);
for (size_t k = 0; k < samplesPerChannelInput; ++k) { mychunk[k] = std::vector<float>(m_inputChannel1.getNChannels() + 1); }
//Fill a matrix - OpenVibe provides the data ch1 (all values from all samples), ch2(all values from all samples) ... chN,
//In the generated chunk every row is a single sample (containing the data from all channels) and every column number is the number of the channel
for (size_t k = 0; k < m_inputChannel1.getNChannels(); ++k)
{
for (size_t j = 0; j < samplesPerChannelInput; ++j)
{
const size_t index = (k * samplesPerChannelInput) + j;
mychunk[j][k] = float(inputBuffer[index]); // @note 64bit->32bit conversion
}
}
//Process stimulations and add them to the output in a dedicated channel
std::vector<float> stimChan = std::vector<float>(samplesPerChannelInput);
auto it = m_stims.begin();
while (it != m_stims.end())
{
auto current = *it;
if (!(current.second >= tStart && current.second <= tEnd))
{
// not in current time range, do not send now.
++it;
continue;
}
const uint64_t posCurrent = CTime(current.second).toSampleCount(m_inputChannel1.getSamplingRate());
const uint64_t posStart = CTime(tStart).toSampleCount(m_inputChannel1.getSamplingRate());
//uint64_t posEnd = CTime(tStart).toSampleCount(m_inputChannel1.getSamplingRate());
int pos = int(posCurrent) - int(posStart);
if (pos < 0) { pos = 0; } //fix position
if (pos == int(stimChan.size())) { pos = int(stimChan.size() - 1); } //fix position
if (pos >= 0 && pos < int(stimChan.size()))
{
stimChan[pos] = float(current.first);
//std::cout<< "pos relative: " << pos << " value: " << stim_chan[pos] << " time:" << CTime(current.second).toSeconds()<< "\n";
}
else { this->getLogManager() << Kernel::LogLevel_Warning << "Bad stimulation position: " << pos << "stim code: " << current.first << "\n"; }
// processed, erase
it = m_stims.erase(it);
}
//add the stim channel at the end of the matrix
const size_t k = m_inputChannel1.getNChannels();
for (size_t j = 0; j < samplesPerChannelInput; ++j) { mychunk[j][k] = stimChan[j]; }
//send all channels
m_outlet->push_chunk(mychunk);
}
}
}
return true;
}
#endif
} // namespace NetworkIO
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,83 @@
#pragma once
#if defined TARGET_HAS_ThirdPartyLSL
#include "../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <vector>
#include "../ovpCInputChannel.h"
#include <lsl_cpp.h>
namespace OpenViBE {
namespace Plugins {
namespace NetworkIO {
class CBoxAlgorithmLSLExportGipsa final : public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
{
public:
CBoxAlgorithmLSLExportGipsa() : m_inputChannel1(0) {}
void release() override { delete this; }
bool initialize() override;
bool uninitialize() override;
bool processInput(const size_t index) override;
bool process() override;
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_CBoxAlgorithmLSLExportGipsa)
protected:
int64_t m_decimationFactor = 0;
uint64_t m_outputSampling = 0;
CString m_streamName;
CString m_streamType;
SignalProcessing::CInputChannel m_inputChannel1;
lsl::stream_outlet* m_outlet = nullptr;
std::vector<std::pair<float, uint64_t>> m_stims;//identifier,time
};
class CBoxAlgorithmLSLExportGipsaDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("LSL Export (Gipsa)"); }
CString getAuthorName() const override { return CString("Anton Andreev"); }
CString getAuthorCompanyName() const override { return CString("Gipsa-lab"); }
CString getShortDescription() const override { return CString("Streams signal outside OpenVibe using Lab Streaming Layer library"); }
CString getDetailedDescription() const override
{
return CString("More on how to read the signal in your application: https://code.google.com/p/labstreaminglayer/");
}
CString getCategory() const override { return CString("Acquisition and network IO"); }
CString getVersion() const override { return CString("1.0"); }
CString getStockItemName() const override { return CString("gtk-connect"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_CBoxAlgorithmLSLExportGipsa; }
IPluginObject* create() override { return new CBoxAlgorithmLSLExportGipsa; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input signal", OV_TypeId_Signal);
prototype.addInput("Input stimulations", OV_TypeId_Stimulations);
prototype.addSetting("Stream name", OV_TypeId_String, "OpenViBE Stream");
prototype.addSetting("Stream type", OV_TypeId_String, "EEG");
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_CBoxAlgorithmLSLExportGipsaDesc)
};
} // namespace NetworkIO
} // namespace Plugins
} // namespace OpenViBE
#endif
@@ -0,0 +1,130 @@
#include "ovpCInputChannel.h"
#include <iostream>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
bool CInputChannel::initialize(Toolkit::TBoxAlgorithm<IBoxAlgorithm>* boxAlgorithm)
{
m_isWorking = false;
m_startTimestamp = 0;
m_endTimestamp = 0;
m_stimulationSet = nullptr;
m_boxAlgorithm = boxAlgorithm;
m_signalDecoder = new Toolkit::TSignalDecoder<Toolkit::TBoxAlgorithm<IBoxAlgorithm>>();
m_signalDecoder->initialize(*m_boxAlgorithm, 0);
m_stimDecoder = new Toolkit::TStimulationDecoder<Toolkit::TBoxAlgorithm<IBoxAlgorithm>>();
m_stimDecoder->initialize(*m_boxAlgorithm, 1);
return true;
}
bool CInputChannel::uninitialize() const
{
m_stimDecoder->uninitialize();
delete m_stimDecoder;
m_signalDecoder->uninitialize();
delete m_signalDecoder;
return true;
}
bool CInputChannel::waitForSignalHeader()
{
Kernel::IBoxIO& boxContext = m_boxAlgorithm->getDynamicBoxContext();
if (boxContext.getInputChunkCount(m_signalChannel))
{
m_signalDecoder->decode(0);
if (m_signalDecoder->isHeaderReceived())
{
m_isWorking = true;
m_startTimestamp = boxContext.getInputChunkStartTime(m_signalChannel, 0);
m_endTimestamp = boxContext.getInputChunkEndTime(m_signalChannel, 0);
boxContext.markInputAsDeprecated(m_signalChannel, 0);
return true;
}
}
return false;
}
IStimulationSet* CInputChannel::getStimulation(uint64_t& startTime, uint64_t& endTime, const size_t index)
{
Kernel::IBoxIO& boxContext = m_boxAlgorithm->getDynamicBoxContext();
m_stimDecoder->decode(index);
m_stimulationSet = m_stimDecoder->getOutputStimulationSet();
startTime = boxContext.getInputChunkStartTime(m_stimulationChannel, index);
endTime = boxContext.getInputChunkEndTime(m_stimulationChannel, index);
boxContext.markInputAsDeprecated(m_stimulationChannel, index);
return m_stimulationSet;
}
IStimulationSet* CInputChannel::discardStimulation(const size_t index)
{
Kernel::IBoxIO& boxContext = m_boxAlgorithm->getDynamicBoxContext();
m_stimDecoder->decode(index);
m_stimulationSet = m_stimDecoder->getOutputStimulationSet();
boxContext.markInputAsDeprecated(m_stimulationChannel, index);
return m_stimulationSet;
}
double* CInputChannel::getSignal(uint64_t& startTime, uint64_t& endTime, const size_t index) const
{
Kernel::IBoxIO& boxContext = m_boxAlgorithm->getDynamicBoxContext();
m_signalDecoder->decode(index);
if (!m_signalDecoder->isBufferReceived()) { return nullptr; }
startTime = boxContext.getInputChunkStartTime(m_signalChannel, index);
endTime = boxContext.getInputChunkEndTime(m_signalChannel, index);
boxContext.markInputAsDeprecated(m_signalChannel, index);
return m_signalDecoder->getOutputMatrix()->getBuffer();
}
double* CInputChannel::discardSignal(const size_t index) const
{
Kernel::IBoxIO& boxContext = m_boxAlgorithm->getDynamicBoxContext();
m_signalDecoder->decode(index);
if (!m_signalDecoder->isBufferReceived()) { return nullptr; }
boxContext.markInputAsDeprecated(m_signalChannel, index);
return m_signalDecoder->getOutputMatrix()->getBuffer();
}
#if 0
void CInputChannel::copyData(const bool copyFirstBlock, size_t index)
{
CMatrix*& matrixBuffer = m_oMatrixBuffer[index & 1];
double* srcData = m_signalDecoder->getOutputMatrix()->getBuffer() + (copyFirstBlock ? 0 : m_firstBlock);
double* dstData = matrixBuffer->getBuffer() + (copyFirstBlock ? m_secondBlock : 0);
size_t size = (copyFirstBlock ? m_firstBlock : m_secondBlock)*sizeof(double);
for (size_t i=0; i < m_nChannels; i++, srcData += m_nSamples, dstData += m_nSamples) { System::Memory::copy(dstData, srcData, size); }
}
#endif
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,73 @@
#pragma once
// @author Gipsa-lab
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
/**
Use this class to receive send and stimulations channels
*/
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CInputChannel
{
typedef enum
{
SIGNAL_CHANNEL,
STIMULATION_CHANNEL,
NB_CHANNELS,
} channel_t;
public:
explicit CInputChannel(const uint16_t index = 0)
: m_signalChannel(index * NB_CHANNELS + SIGNAL_CHANNEL), m_stimulationChannel(index * NB_CHANNELS + STIMULATION_CHANNEL) {}
~CInputChannel() { }
bool initialize(Toolkit::TBoxAlgorithm<IBoxAlgorithm>* boxAlgorithm);
bool uninitialize() const;
bool isLastChannel(const size_t index) const { return index == m_stimulationChannel; }
bool isWorking() const { return m_isWorking; }
bool waitForSignalHeader();
size_t getNStimulationBuffers() const { return m_boxAlgorithm->getDynamicBoxContext().getInputChunkCount(m_stimulationChannel); }
size_t getNSignalBuffers() const { return m_boxAlgorithm->getDynamicBoxContext().getInputChunkCount(m_signalChannel); }
IStimulationSet* getStimulation(uint64_t& startTime, uint64_t& endTime, size_t index);
IStimulationSet* discardStimulation(size_t index);
double* getSignal(uint64_t& startTime, uint64_t& endTime, size_t index) const;
double* discardSignal(size_t index) const;
uint64_t getSamplingRate() const { return m_signalDecoder->getOutputSamplingRate(); }
size_t getNChannels() const { return m_signalDecoder->getOutputMatrix()->getDimensionSize(0); }
size_t getNSamples() const { return m_signalDecoder->getOutputMatrix()->getDimensionSize(1); }
uint64_t getStartTimestamp() const { return m_startTimestamp; }
uint64_t getEndTimestamp() const { return m_endTimestamp; }
const char* getChannelName(const size_t index) const { return m_signalDecoder->getOutputMatrix()->getDimensionLabel(0, index); }
const Kernel::TParameterHandler<CMatrix*>& getOpMatrix() const { return m_signalDecoder->getOutputMatrix(); }
protected:
size_t m_signalChannel = 0;
size_t m_stimulationChannel = 0;
bool m_isWorking = false;
uint64_t m_startTimestamp = 0;
uint64_t m_endTimestamp = 0;
IStimulationSet* m_stimulationSet = nullptr;
// parent memory
Toolkit::TBoxAlgorithm<IBoxAlgorithm>* m_boxAlgorithm = nullptr;
// signal section
//Kernel::IAlgorithmProxy* m_signalDecoder;
Toolkit::TSignalDecoder<Toolkit::TBoxAlgorithm<IBoxAlgorithm>>* m_signalDecoder = nullptr;
// stimulation section
Toolkit::TStimulationDecoder<Toolkit::TBoxAlgorithm<IBoxAlgorithm>>* m_stimDecoder = nullptr;
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,16 @@
#pragma once
// Boxes
//---------------------------------------------------------------------------------------------------
#define OVP_ClassId_BoxAlgorithm_OSCController OpenViBE::CIdentifier(0xC66F2F0C, 0x3BA5B424)
#define OVP_ClassId_BoxAlgorithm_OSCControllerDesc OpenViBE::CIdentifier(0xF7A35BD7, 0x6331C7D9)
#define OVP_ClassId_BoxAlgorithm_CBoxAlgorithmLSLExportGipsa OpenViBE::CIdentifier(0x591D2E94, 0x221C23AD)
#define OVP_ClassId_BoxAlgorithm_CBoxAlgorithmLSLExportGipsaDesc OpenViBE::CIdentifier(0x22AF11F5, 0x58F2787D)
// Global defines
//---------------------------------------------------------------------------------------------------
#ifdef TARGET_HAS_ThirdPartyOpenViBEPluginsGlobalDefines
#include "ovp_global_defines.h"
#endif // TARGET_HAS_ThirdPartyOpenViBEPluginsGlobalDefines
#define OV_AttributeId_Box_FlagIsUnstable OpenViBE::CIdentifier(0x666FFFFF, 0x666FFFFF)

Some files were not shown because too many files have changed in this diff Show More