init
@@ -0,0 +1 @@
|
||||
OV_ADD_PROJECTS("PLUGINS")
|
||||
@@ -0,0 +1,2 @@
|
||||
# Add all the subdirs as projects of the named branch
|
||||
OV_ADD_PROJECTS("PLUGINS_PROCESSING")
|
||||
@@ -0,0 +1,40 @@
|
||||
PROJECT(openvibe-plugins-sdk-classification)
|
||||
|
||||
SET(PROJECT_VERSION_MAJOR ${OV_GLOBAL_VERSION_MAJOR})
|
||||
SET(PROJECT_VERSION_MINOR ${OV_GLOBAL_VERSION_MINOR})
|
||||
SET(PROJECT_VERSION_PATCH ${OV_GLOBAL_VERSION_PATCH})
|
||||
SET(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH})
|
||||
|
||||
FILE(GLOB_RECURSE SRC_FILES src/*.cpp src/*.h src/*.hpp src/*.inl)
|
||||
INCLUDE("FindSourceRCProperties")
|
||||
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("FindOpenViBEModuleSystem")
|
||||
INCLUDE("FindOpenViBEModuleXML")
|
||||
INCLUDE("FindOpenViBEModuleFS")
|
||||
INCLUDE("FindThirdPartyEigen")
|
||||
|
||||
# ---------------------------------
|
||||
# Target macros
|
||||
# Defines target operating system, architecture, 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/box-tutorials)
|
||||
@@ -0,0 +1,28 @@
|
||||
<OpenViBE-Classifier-Box Creator="OpenViBE Designer" CreatorVersion="2.2.0" FormatVersion="4">
|
||||
<Strategy-Identifier class-id="(0xffffffff, 0xffffffff)">Native</Strategy-Identifier>
|
||||
<Algorithm-Identifier class-id="(0x2ba17a3c, 0x1bd46d84)">Linear Discrimimant Analysis (LDA)</Algorithm-Identifier>
|
||||
<Stimulations>
|
||||
<Class-Stimulation class-id="0">OVTK_StimulationId_Label_01</Class-Stimulation>
|
||||
<Class-Stimulation class-id="1">OVTK_StimulationId_Label_02</Class-Stimulation>
|
||||
<Class-Stimulation class-id="2">OVTK_StimulationId_Label_03</Class-Stimulation>
|
||||
</Stimulations>
|
||||
<OpenViBE-Classifier>
|
||||
<LDA version="1">
|
||||
<Classes>0 1 2 </Classes>
|
||||
<Class-config-list>
|
||||
<Class-config>
|
||||
<Weights> 1.334008e+002 1.260394e+002 1.328885e+002 1.490076e+002</Weights>
|
||||
<Bias>-3956.51</Bias>
|
||||
</Class-config>
|
||||
<Class-config>
|
||||
<Weights> 1.312276e+002 1.289672e+002 1.329751e+002 1.494476e+002</Weights>
|
||||
<Bias>-3975.31</Bias>
|
||||
</Class-config>
|
||||
<Class-config>
|
||||
<Weights> 1.313580e+002 1.262441e+002 1.355423e+002 1.492769e+002</Weights>
|
||||
<Bias>-3972.21</Bias>
|
||||
</Class-config>
|
||||
</Class-config-list>
|
||||
</LDA>
|
||||
</OpenViBE-Classifier>
|
||||
</OpenViBE-Classifier-Box>
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* \page BoxAlgorithm_ClassifierProcessor Classifier processor
|
||||
__________________________________________________________________
|
||||
|
||||
Detailed description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_ClassifierProcessor_Description|
|
||||
The <em>Classifier Processor</em> box is a generic box for classifying data (feature vectors).
|
||||
It works in conjunction with the \ref Doc_BoxAlgorithm_ClassifierTrainer box.
|
||||
This box' role is to expose a generic interface to the rest of the BCI pipeline. The
|
||||
vectors to classify are forwarded to an algorithm or a structure of algorithms depending on what is
|
||||
described in the loaded configuration file. The behavior is simple: at initialization phase, the classification
|
||||
structure is initialized and its configuration is loaded from the configuration file. Then each time this box
|
||||
receives a new feature vector, it is forwarded to the classification algorithm that classifies it. The box gets the algorithm
|
||||
status and the actual class value and translates this information to its output. The predicted class is sent out in
|
||||
the form of a stimulation and the algorithm status is sent in the form a streamed matrix. The stimulation can be generically
|
||||
interpreted by the rest of the pipeline but it is important to understand that each classification algorithm is
|
||||
free to report whatever it wants in its "status matrix". Consequently, the use of this output stream will be
|
||||
dependent on the chosen classification algorithm. For example, the LDA classifier sends the hyperplane distance
|
||||
value as its status.
|
||||
* |OVP_DocEnd_BoxAlgorithm_ClassifierProcessor_Description|
|
||||
__________________________________________________________________
|
||||
|
||||
Inputs description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_ClassifierProcessor_Inputs|
|
||||
* |OVP_DocEnd_BoxAlgorithm_ClassifierProcessor_Inputs|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_ClassifierProcessor_Input1|
|
||||
This input should be connected to the feature vector stream to classify. Each time a new feature vector arrives,
|
||||
a classification process will be triggered. Consequently, a classification stimulation will be sent on the
|
||||
first output of this box.
|
||||
* |OVP_DocEnd_BoxAlgorithm_ClassifierProcessor_Input1|
|
||||
__________________________________________________________________
|
||||
|
||||
Outputs description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_ClassifierProcessor_Outputs|
|
||||
* |OVP_DocEnd_BoxAlgorithm_ClassifierProcessor_Outputs|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_ClassifierProcessor_Output1|
|
||||
This output will contain the classification stimulations. Each time a new feature vector arrives to this box,
|
||||
a new classification process is triggered, resulting in the generation of the corresponding class stimulation.
|
||||
* |OVP_DocEnd_BoxAlgorithm_ClassifierProcessor_Output1|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_ClassifierProcessor_Output2|
|
||||
This output reflects the classification algorithm status in the form of a matrix of value. This output will contain one or several distances
|
||||
to an hyperplane if the classifier provide it. If not, the matrix will have 0 dimension. The format of this output directly depend on
|
||||
the classification algorithm and of the strategy used by the processor box.
|
||||
* |OVP_DocEnd_BoxAlgorithm_ClassifierProcessor_Output2|
|
||||
*
|
||||
* |OVP_DocBegin_BoxAlgorithm_ClassifierProcessor_Output3|
|
||||
This output reflects the classification algorithm status in the form of a matrix of value. This output will contains one or several probabilities
|
||||
for a data to be on a class if the classifier provide it. If not, the matrix will have 0 dimension. The format of this output directly depend on
|
||||
the classification algorithm and of the strategy used by the processor box.
|
||||
* |OVP_DocEnd_BoxAlgorithm_ClassifierProcessor_Output2|
|
||||
__________________________________________________________________
|
||||
|
||||
Settings description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_ClassifierProcessor_Settings|
|
||||
* |OVP_DocEnd_BoxAlgorithm_ClassifierProcessor_Settings|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_ClassifierProcessor_Setting1|
|
||||
This setting points to the configuration file of the box generated by the
|
||||
\ref Doc_BoxAlgorithm_ClassifierTrainer box. Its syntax depends on the selected algorithm.
|
||||
* |OVP_DocEnd_BoxAlgorithm_ClassifierProcessor_Setting1|
|
||||
|
||||
__________________________________________________________________
|
||||
|
||||
Examples description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_ClassifierProcessor_Examples|
|
||||
This box is used in BCI pipelines in order to classify cerebral activity states. For a detailed scenario using this
|
||||
box and its associated \ref Doc_BoxAlgorithm_ClassifierTrainer, please see the <b>motor imagary</b>
|
||||
BCI scenario in the sample scenarios.
|
||||
* |OVP_DocEnd_BoxAlgorithm_ClassifierProcessor_Examples|
|
||||
__________________________________________________________________
|
||||
|
||||
Miscellaneous description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_ClassifierProcessor_Miscellaneous|
|
||||
* |OVP_DocEnd_BoxAlgorithm_ClassifierProcessor_Miscellaneous|
|
||||
*/
|
||||
@@ -0,0 +1,285 @@
|
||||
/**
|
||||
* \page BoxAlgorithm_ClassifierTrainer Classifier trainer
|
||||
__________________________________________________________________
|
||||
|
||||
Detailed description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_ClassifierTrainer_Description|
|
||||
The <em>Classifier Trainer</em> box is a generic box for training models to classify input data.
|
||||
It works in conjunction with the \ref Doc_BoxAlgorithm_ClassifierProcessor box.
|
||||
This box' role is to expose a generic interface to the rest of the BCI pipelines. The box
|
||||
will generate an internal structure according to the multiclass strategy and the learning
|
||||
algorithm selected.
|
||||
|
||||
The behavior is simple, the box collects a number of feature vectors. Those feature vectors
|
||||
are labelled depending on the input they arrive on. When a specific stimulation arrives, a training
|
||||
process is triggered. This process can take some time so this box should be used offline. Depending on the
|
||||
settings you enter, you will be able to perform a k-fold test to estimate the accuracy of the learned
|
||||
classifier. When this training stimulation is received, the box generates a configuration file that will
|
||||
be usable online by the \ref Doc_BoxAlgorithm_ClassifierProcessor box.
|
||||
Finally, the box outputs a particular stimulation (OVTK_StimulationId_TrainCompleted)
|
||||
on its output, that can be used to trigger further treatments in the scenario.
|
||||
* |OVP_DocEnd_BoxAlgorithm_ClassifierTrainer_Description|
|
||||
__________________________________________________________________
|
||||
|
||||
Inputs description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_ClassifierTrainer_Inputs|
|
||||
This box can have a variable number of inputs. If you need more than two classes, feel free to add more
|
||||
inputs and to use a proper strategy/classifier combination to handle more than two classes.
|
||||
* |OVP_DocEnd_BoxAlgorithm_ClassifierTrainer_Inputs|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_ClassifierTrainer_Input1|
|
||||
The first input receives a stimulation stream. Only one stimulation of this stream is important, the one
|
||||
that triggers the training process. When this stimulation is received, all the feature vectors are labelled
|
||||
and sent to the classification algorithm. The training is triggered and executed. Then the classification
|
||||
algorithm generates a configuration file that will be used online by the \ref Doc_BoxAlgorithm_ClassifierProcessor box.
|
||||
* |OVP_DocEnd_BoxAlgorithm_ClassifierTrainer_Input1|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_ClassifierTrainer_Input2|
|
||||
This input receives the feature vector for the first class.
|
||||
* |OVP_DocEnd_BoxAlgorithm_ClassifierTrainer_Input2|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_ClassifierTrainer_Input3|
|
||||
This input receives the feature vector for the second class.
|
||||
* |OVP_DocEnd_BoxAlgorithm_ClassifierTrainer_Input3|
|
||||
__________________________________________________________________
|
||||
|
||||
Outputs description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_ClassifierTrainer_Outputs|
|
||||
* |OVP_DocEnd_BoxAlgorithm_ClassifierTrainer_Outputs|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_ClassifierTrainer_Output1|
|
||||
The stimulation OVTK_StimulationId_TrainCompleted is raised on this output when the classifier trainer has finished its job.
|
||||
* |OVP_DocEnd_BoxAlgorithm_ClassifierTrainer_Output1|
|
||||
|
||||
__________________________________________________________________
|
||||
|
||||
Settings description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_ClassifierTrainer_Settings|
|
||||
The number of settings of this box can vary depending on the classification algorithm you choose. Such algorithm
|
||||
could have specific input OpenViBE::Kernel::IParameter objects (see \ref OpenViBE::Kernel::IAlgorithmProxy for details). If
|
||||
the type of those parameters is simple enough to be handled in the GUI, then additional settings will be added to this box.
|
||||
<b>After switching a strategy or a classifier, you will have to close and re-open the settings configuration dialog to see the parameters of the new classifier.</b> Supported parameter types are : Integers, Floats, Enumerations, Booleans. The documentation for those
|
||||
parameters can not be done in this page because it is impossible to know at this time what classifier thus what hyper
|
||||
parameters you will have available. This will depend on the classification algorihtms that are be implemented in OpenViBE.
|
||||
* |OVP_DocEnd_BoxAlgorithm_ClassifierTrainer_Settings|
|
||||
*
|
||||
* * |OVP_DocBegin_BoxAlgorithm_ClassifierTrainer_Setting1|
|
||||
The stimulation that triggers the training process and save the learned classifier to disk.
|
||||
* |OVP_DocEnd_BoxAlgorithm_ClassifierTrainer_Setting1|
|
||||
*
|
||||
* * |OVP_DocBegin_BoxAlgorithm_ClassifierTrainer_Setting2|
|
||||
This setting points to the configuration file where to save the result of the training for later online use. This
|
||||
configuration file is used by the \ref Doc_BoxAlgorithm_ClassifierProcessor box. Its syntax
|
||||
depends on the selected algorithm.
|
||||
* |OVP_DocEnd_BoxAlgorithm_ClassifierTrainer_Setting2|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_ClassifierTrainer_Setting3|
|
||||
This setting is the strategy to use. You can choose any registered \c OVTK_TypeId_ClassificationStrategy
|
||||
strategy you want.
|
||||
* |OVP_DocEnd_BoxAlgorithm_ClassifierTrainer_Setting3|
|
||||
*
|
||||
* * |OVP_DocBegin_BoxAlgorithm_ClassifierTrainer_Setting4|
|
||||
This is the stimulation to send when the classifier algorithm detects a class-1 feature vector
|
||||
* |OVP_DocEnd_BoxAlgorithm_ClassifierTrainer_Setting4|
|
||||
*
|
||||
* |OVP_DocBegin_BoxAlgorithm_ClassifierTrainer_Setting5|
|
||||
This is the stimulation to send when the classifier algorithm detects a class-2 feature vector
|
||||
* |OVP_DocEnd_BoxAlgorithm_ClassifierTrainer_Setting5|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_ClassifierTrainer_Setting6|
|
||||
This setting is the classifier to use. You can choose any registered \c OVTK_TypeId_ClassifierAlgorithm
|
||||
algorithm you want.
|
||||
* |OVP_DocEnd_BoxAlgorithm_ClassifierTrainer_Setting6|
|
||||
|
||||
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_ClassifierTrainer_Setting10|
|
||||
If you want to perform a k-fold test, you should enter something else than 0 or 1 here. A k-fold test generally gives
|
||||
a better estimate of the classifiers accuracy than naive testing with the training data. The classifier may overfit
|
||||
the training data, and get a good accuracy with the observed data, but not be able to generalize to unseen data.
|
||||
In cross-validation, the idea is to divide the set of feature vectors in a number of partitions. The classification algorithm
|
||||
is trained on some of the partitions and its accuracy is tested on the others. However, the classifier produced by the box is
|
||||
the classifier trained with the whole data. The cross-validation is only an error estimation tool, it does not affect
|
||||
the resulting model. See the miscellaneous section for details on how the k-fold test is done in this box, and possible
|
||||
caveats about the cross-validation procedure.
|
||||
* |OVP_DocEnd_BoxAlgorithm_ClassifierTrainer_Setting10|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_ClassifierTrainer_Setting11|
|
||||
If the number of class labels is unbalanced, the classifiers tend to be biased towards the majority labels.
|
||||
This option can be used to resample the dataset to feature all classes equally.
|
||||
|
||||
The algorithm first looks how many examples there are in the majority class. Lets say this is n. Then, if class k has m examples,
|
||||
it will random sample n-m examples with replacement from class k, appending them to the dataset. This will be done for each class.
|
||||
In the end, each class will have n examples and all except the majority class will have some duplicate training vectors.
|
||||
This can be seen as a technique to weight the importance of examples for such classifiers that do not support setting example weights
|
||||
or class weight prior, and can in general be attempted with arbitrary learning algorithms.
|
||||
|
||||
Enabling this option may make sense if the box is used for incremental learning, where all classes may not be equally represented
|
||||
in the training data obtained so far, even if the design itself is balanced. Note that enabling this will make the cross-validation
|
||||
results optimistic. In most conditions, the feature should be disabled.
|
||||
* |OVP_DocEnd_BoxAlgorithm_ClassifierTrainer_Setting11|
|
||||
|
||||
__________________________________________________________________
|
||||
|
||||
Examples description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_ClassifierTrainer_Examples|
|
||||
This box is used in BCI pipelines in order to classify cerebral activity states. For a detailed scenario using this
|
||||
box and its associated \ref Doc_BoxAlgorithm_ClassifierProcessor, please see the <b>motor imagary</b>
|
||||
BCI scenario in the sample scenarios. An even more simple tutorial with artificial data
|
||||
is available in the <b>box-tutorials/</b> folder.
|
||||
|
||||
* |OVP_DocEnd_BoxAlgorithm_ClassifierTrainer_Examples|
|
||||
__________________________________________________________________
|
||||
|
||||
Miscellaneous description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_ClassifierTrainer_Miscellaneous|
|
||||
|
||||
The box supports various multiclass strategies and classifiers as plugins.
|
||||
|
||||
\par Available strategy:
|
||||
Strategy refers to how feature vectors are routed to one or more classifiers, which possibly can handle only 2 classes themselves.
|
||||
|
||||
\par Native
|
||||
Use the classifier training algorithm without a pairwise strategy. All the data is passed to a single classifier trainer.
|
||||
|
||||
\par One Vs All
|
||||
Use a pairwise strategy which consists of training each class against all the others, creating n classifiers for n classes.
|
||||
|
||||
\par One vs One
|
||||
Use a airwise strategy which trains one classifier for each pair of classes. Then we use a decision startegy to extract the most likely class. There are three differents decision strategy:
|
||||
\li Voting: method based on a simple majority voting process
|
||||
\li HT: method described in: Hastie, Trevor; Tibshirani, Robert. Classification by pairwise coupling. The Annals of Statistics 26 (1998), no. 2, 451--471
|
||||
\li PKPD: method describe in: Price, S. Knerr, L. Personnaz, and G. Dreyfus. Pairwise neural network classifiers with probabilistic outputs. In G. Tesauro, D. Touretzky, and T. Leen (eds.)
|
||||
Advances in Neural Information Processing Systems 7 (NIPS-94), pp. 1109-1116. MIT Press, 1995.
|
||||
You cannot use every algorithm with every decision strategy, but the interface will restain the choice according to your selection.
|
||||
|
||||
|
||||
|
||||
\par Available classifiers:
|
||||
|
||||
\par Support Vector Machine (SVM)
|
||||
A well-known classifier supporting non-linear classification via kernels. The implementation is based on LIBSVM 2.91, which is included in the OpenViBE source tree. The parameters exposed in the GUI correspond to LIBSVM parameters. For more information on LIBSVM, see <a href="http://www.csie.ntu.edu.tw/~cjlin/libsvm/">here</a>.
|
||||
\par
|
||||
This algorithm provides only probabilities.
|
||||
|
||||
\par Linear Discriminant Analysis (LDA)
|
||||
A simple and fast linear classifier. For description, see any major textbook on Machine Learning or Statistics (e.g. Duda, Hart & Stork, or Hastie, Tibshirani & Friedman). This algorithm can be used with a regularized covariance matrix
|
||||
according to a method proposed by Ledoit & Wolf: "A Well-Conditioned Estimator for Large-Dimensional Covariance Matrices", 2004.
|
||||
The Linear Discriminant Analysis has the following options.
|
||||
\par
|
||||
\li Use shrinkage: Use a classic or a regularized covariance matrix.
|
||||
\li Shrinkage: A value s between [0,1] sets a linear weight between dataCov and priorCov. I.e. cov=(1-s)*dataCov+s*priorCov.
|
||||
Value <0 is used to auto-estimate the shrinking coefficient (default). If var(x) is a vector of empirical variances of all data dimensions, priorCov is a
|
||||
diagonal matrix with a single value mean(var(x)) pasted on its diagonal. Used only if use shrinkage is checked.
|
||||
\li Force diagonal cov (DDA): This sets the nondiagonal entries of the covariance matrices to zero. Used only if Use shrinkage is checked.
|
||||
\par
|
||||
Note that setting shrinkage to 0 should get you the regular LDA behavior. If you additionally force the covariance to be diagonal, you should get a model resembling the Naive Bayes classifier.
|
||||
\par
|
||||
This algorithm provides both hyperplane distance and probabilities.
|
||||
|
||||
\par Multilayer Perceptron (MLP)
|
||||
A classifier algorithm which relies on an artificial neural network (<a href="https://hal.inria.fr/inria-00099922/en">Laurent Bougrain. Practical introduction to artificial neural networks. IFAC symposium on automation in Mining, Mineral and Metal Processing -
|
||||
MMM'04, Sep 2004, Nancy, France, 6 p, 2004.</a>). In OpenViBE, the MLP is a 2-layer neural network. The hyperbolic tangent is the activation function of the
|
||||
neurons inside the hidden layer. The network is trained using the backpropagation of the gradient. During the training, 80% of the training set is used to compute the gradient,
|
||||
and 20% is used to validate the new model. The different weights and biases are updated only once per iteration (just before the validation). A coefficient alpha (learning coefficient) is used to moderate the importance of
|
||||
the modification of weights and biases to avoid oscillations. The learning stops when the difference of the error per element (computed during validation) of two consecutive iterations is under the value epsilon given as a parameter.
|
||||
\par
|
||||
\li Number of neurons in hidden layer: number of neurons that will be used in the hidden layer.
|
||||
\li Learning stop condition : the epsilon value used to stop the learning
|
||||
\li Learning coefficient: a coefficient which influence the speed of learning. The smaller the coefficient is, the longer the learning will take, the more chance you will have to get a good solution.
|
||||
\par
|
||||
Note that feature vectors are normalized between -1 and 1 (using the min/max of the training set) to avoid saturation of the hyperbolic tangent.
|
||||
\par
|
||||
This algorithm provides both hyperplane distance (identity of output layer) and probabilites (softmax function on output layer).
|
||||
|
||||
\par Cross Validation
|
||||
|
||||
In this section, we will detail how the k-fold test is implemented in this box. For the k-fold test to be performed, you
|
||||
have to choose more than 1 partition in the related settings. Suppose you chose \c n partitions. Then when trigger stimulation
|
||||
is received, the feature vector set is splitted in \c n consecutive segments. The classification algorithm is trained on
|
||||
\c n-1 of those segments and tested on the last one. This is performed for each segment.
|
||||
|
||||
For example, suppose you have 5 partitions of feature vectors (\c FVs)
|
||||
\verbatim
|
||||
+------+ +------+ +------+ +------+ +------+
|
||||
| FVs1 | | FVs2 | | FVs3 | | FVs4 | | FVs5 |
|
||||
+------+ +------+ +------+ +------+ +------+
|
||||
\endverbatim
|
||||
For the first training, a feature vector set is built form the \c FVs2, \c FVs3, \c FVs4, \c FVs5. The classifier algorithm
|
||||
is trained on this feature vector set. Then the classifier is tested on the \c FVs1 :
|
||||
\verbatim
|
||||
+------+ +---------------------------------+
|
||||
| FVs1 | | Training Feature Vector Set 1 |
|
||||
+------+ +---------------------------------+
|
||||
\endverbatim
|
||||
Then, a feature vector set is built form the \c FVs1, \c FVs3, \c FVs4, \c FVs5. The classifier algorithm
|
||||
is trained on this feature vector set. Then the classifier is tested on the \c FVs2 :
|
||||
\verbatim
|
||||
+-------+ +------+ +------------------------+
|
||||
| Train | | FVs2 | | ing Feat. Vector Set 2 |
|
||||
+-------+ +------+ +------------------------+
|
||||
\endverbatim
|
||||
The same process if performed on all the partitions :
|
||||
\verbatim
|
||||
+---------------+ +------+ +---------------+
|
||||
|Training Featur| | FVs3 | |e Vector Set 3 |
|
||||
+---------------+ +------+ +---------------+
|
||||
+------------------------+ +------+ +------+
|
||||
|Training Feature Vector | | FVs4 | |Set 4 |
|
||||
+------------------------+ +------+ +------+
|
||||
+---------------------------------+ +------+
|
||||
| Training Feature Vector Set 5 | | FVs5 |
|
||||
+---------------------------------+ +------+
|
||||
\endverbatim
|
||||
|
||||
Important things to consider :
|
||||
- The more partitions you have, the more feature vectors you have in your training sets... and the less examples
|
||||
you'll have to test on. This means that the result of the test will probably be less reliable.
|
||||
|
||||
In conclusion, be careful when choosing this k-fold test setting. Typical value range from 4 partitions (train on 75% of the feature vectors and
|
||||
test on 25% - 4 times) to 10 partitions (train on 90% of the feature vectors and test on 10% - 10 times).
|
||||
|
||||
Note that the cross-validation performed by the classifier trainer box in OpenViBE may be optimistic.
|
||||
The cross-validation computation is working as it should, but it cannot take into account what happens outside
|
||||
the classifier trainer box. In OpenViBE scenarios, there may be e.g. time overlap from epoching, feature
|
||||
vectors drawn from the same epoch ending up in the same cross-validation partition, and (supervised)
|
||||
preprocessing such as CSP or xDAWN potentially overfitting the data before its given to the classifier trainer.
|
||||
Such situations are not compatible with the theoretical assumption that the feature vectors are
|
||||
independent and identically distributed (the typical iid assumption in machine learning) across
|
||||
train and test. To do cross-validation controlling for such issues, we have provided
|
||||
a more advanced cross-validation tutorial as part of the OpenViBE web documentation.
|
||||
|
||||
\par Confusion Matrices
|
||||
|
||||
At the end of the training, the box will print one or two confusion matrices, depending if cross-validation
|
||||
was used: one matrix for the cross-validation, the other for the training data. Each matrix will contain true
|
||||
class as rows, and predicted class as columns. The diagonal describes the percentage of correct predictions per class.
|
||||
Although the matrix can be optimistic (see above section about the cross-validation), it may give useful
|
||||
diagnostic information. For example, if the accuracy is very skewed towards one class, this may indicate
|
||||
a problem if the design is supposed to be balanced. The problem may originate e.g. from the original data
|
||||
source, the signal processing chains for the different classes, or the classifier learning algorithm. These need
|
||||
then to be investigated. Also, if very low accuracies are observed in these matrices, it may give reason
|
||||
to suspect that prediction accuracies on fresh data might be likewise lacking -- or worse.
|
||||
|
||||
\par Incremental Learning
|
||||
|
||||
The box can also be used for simple incremental (online) learning. To achieve this, simply send the box the training
|
||||
stimulation and it will train a classifier with all the data it has received so far. You can give it more
|
||||
feature vectors later, and trigger the learning again by sending another stimulation. Likewise, the corresponding
|
||||
classifier processor box can be made to load new classifiers during playback. With classifiers like LDA,
|
||||
this practice is usually feasible when the data is reasonably sized (as in basic motor imagery).
|
||||
|
||||
* |OVP_DocEnd_BoxAlgorithm_ClassifierTrainer_Miscellaneous|
|
||||
*/
|
||||
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* \page BoxAlgorithm_VotingClassifier Voting Classifier
|
||||
__________________________________________________________________
|
||||
|
||||
Detailed description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_VotingClassifier_Description|
|
||||
The purpose of this simple classifier is to choose between multiple two class classifiers which
|
||||
one mostly fits a condition. For example imagine \c n states. Each of those states can be either active
|
||||
or inactive. Additionally, imagine you want only one active state at a time. Then you can have \c n
|
||||
two-class classifiers telling for each state if it is active or not, and a following voting classifier
|
||||
that chooses which of those states is the <em>most active</em>.
|
||||
* |OVP_DocEnd_BoxAlgorithm_VotingClassifier_Description|
|
||||
__________________________________________________________________
|
||||
|
||||
Inputs description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_VotingClassifier_Inputs|
|
||||
You can add as many inputs as you need depending on the number of preceeding states.
|
||||
|
||||
The inputs of this classifier can be changed to either streamed matrix of stimulations.
|
||||
|
||||
In the case you choose stimulations, each active stimulation gives a point
|
||||
to the preceeding state. Both an inactive and a reject stimulation gives no point.
|
||||
Any other stimulation is ignored.
|
||||
After a number of repetitions, the state with the best score is chosen.
|
||||
|
||||
In the case you choose streamed matrix, the matrix must have only one
|
||||
element. This element is used as a score coefficient (in place of the simple 1-0 of the previous
|
||||
case). After a number of repetitions, the state with the best score is chosen.
|
||||
* |OVP_DocEnd_BoxAlgorithm_VotingClassifier_Inputs|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_VotingClassifier_Input1|
|
||||
Input stream for the first state.
|
||||
* |OVP_DocEnd_BoxAlgorithm_VotingClassifier_Input1|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_VotingClassifier_Input2|
|
||||
Input stream for the second state.
|
||||
* |OVP_DocEnd_BoxAlgorithm_VotingClassifier_Input2|
|
||||
__________________________________________________________________
|
||||
|
||||
Outputs description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_VotingClassifier_Outputs|
|
||||
* |OVP_DocEnd_BoxAlgorithm_VotingClassifier_Outputs|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_VotingClassifier_Output1|
|
||||
This output sends a new stimulation as soon as the classifier received the correct number of votes
|
||||
from the preceeding states. The output stimulation is based on the 5th setting of the box. First
|
||||
state being selected would send exactly this stimulation. Second state would send this
|
||||
stimulation + 1 etc.
|
||||
* |OVP_DocEnd_BoxAlgorithm_VotingClassifier_Output1|
|
||||
__________________________________________________________________
|
||||
|
||||
Settings description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_VotingClassifier_Settings|
|
||||
* |OVP_DocEnd_BoxAlgorithm_VotingClassifier_Settings|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_VotingClassifier_Setting1|
|
||||
This setting tells the box how many votes it needs before choosing. If the box uses stimulations,
|
||||
it must receive either the target or non target stimulation to consider a state has been voted.
|
||||
Additionaly, the box waits each state to be voted the correct number of times to take a decision.
|
||||
* |OVP_DocEnd_BoxAlgorithm_VotingClassifier_Setting1|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_VotingClassifier_Setting2|
|
||||
If the box uses stimulations, this settings tells what stimulation reflects that the state is active.
|
||||
* |OVP_DocEnd_BoxAlgorithm_VotingClassifier_Setting2|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_VotingClassifier_Setting3|
|
||||
If the box uses stimulations, this settings tells what stimulation reflects that the state is inactive.
|
||||
* |OVP_DocEnd_BoxAlgorithm_VotingClassifier_Setting3|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_VotingClassifier_Setting4|
|
||||
In case no choice can be made (for example, there are more than one state with the highest score), the
|
||||
voting classifier can choose to reject the vote and send a specific stimulation for this. This is more
|
||||
likely to happen when the box works on stimulation better than streamed matrix. You can force this box
|
||||
to choose using the 6th setting.
|
||||
* |OVP_DocEnd_BoxAlgorithm_VotingClassifier_Setting4|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_VotingClassifier_Setting5|
|
||||
This stimulation is used as a basis for the stimulations to send when a state is selected. First
|
||||
state being selected would send exactly this stimulation. Second state would send this
|
||||
stimulation + 1 etc.
|
||||
* |OVP_DocEnd_BoxAlgorithm_VotingClassifier_Setting5|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_VotingClassifier_Setting6|
|
||||
This setting can force the box to choose a state even if more than one state have the highest score.
|
||||
In such case, there won't be any rejection. The way the box decides between the ex-aequo candidates
|
||||
is undefined.
|
||||
* |OVP_DocEnd_BoxAlgorithm_VotingClassifier_Setting6|
|
||||
__________________________________________________________________
|
||||
|
||||
Examples description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_VotingClassifier_Examples|
|
||||
This box is used in the <em>P300 speller</em> and the <em>P300 magic card</em> BCIs. Please see those
|
||||
scenarios in the sample <em>openvibe-scenarios</em>.
|
||||
* |OVP_DocEnd_BoxAlgorithm_VotingClassifier_Examples|
|
||||
__________________________________________________________________
|
||||
|
||||
Miscellaneous description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_VotingClassifier_Miscellaneous|
|
||||
* |OVP_DocEnd_BoxAlgorithm_VotingClassifier_Miscellaneous|
|
||||
*/
|
||||
@@ -0,0 +1,101 @@
|
||||
.. _Doc_BoxAlgorithm_ClassifierProcessor:
|
||||
|
||||
Classifier processor
|
||||
====================
|
||||
|
||||
.. container:: attribution
|
||||
|
||||
:Author:
|
||||
Yann Renard, Guillaume Serriere
|
||||
:Company:
|
||||
INRIA/IRISA
|
||||
|
||||
.. image:: images/Doc_BoxAlgorithm_ClassifierProcessor.png
|
||||
|
||||
Classifies incoming feature vectors using a previously learned classifier.
|
||||
|
||||
The *Classifier Processor* box is a generic box for classifying data (feature vectors).
|
||||
It works in conjunction with the :ref:`Doc_BoxAlgorithm_ClassifierTrainer` box.
|
||||
This box' role is to expose a generic interface to the rest of the BCI pipeline. The
|
||||
vectors to classify are forwarded to an algorithm or a structure of algorithms depending on what is
|
||||
described in the loaded configuration file. The behavior is simple: at initialization phase, the classification
|
||||
structure is initialized and its configuration is loaded from the configuration file. Then each time this box
|
||||
receives a new feature vector, it is forwarded to the classification algorithm that classifies it. The box gets the algorithm
|
||||
status and the actual class value and translates this information to its output. The predicted class is sent out in
|
||||
the form of a stimulation and the algorithm status is sent in the form a streamed matrix. The stimulation can be generically
|
||||
interpreted by the rest of the pipeline but it is important to understand that each classification algorithm is
|
||||
free to report whatever it wants in its "status matrix". Consequently, the use of this output stream will be
|
||||
dependent on the chosen classification algorithm. For example, the LDA classifier sends the hyperplane distance
|
||||
value as its status.
|
||||
|
||||
Inputs
|
||||
------
|
||||
|
||||
.. csv-table::
|
||||
:header: "Input Name", "Stream Type"
|
||||
|
||||
"Features", "Feature vector"
|
||||
"Commands", "Stimulations"
|
||||
|
||||
Features
|
||||
~~~~~~~~
|
||||
|
||||
This input should be connected to the feature vector stream to classify. Each time a new feature vector arrives,
|
||||
a classification process will be triggered. Consequently, a classification stimulation will be sent on the
|
||||
first output of this box.
|
||||
|
||||
Outputs
|
||||
-------
|
||||
|
||||
.. csv-table::
|
||||
:header: "Output Name", "Stream Type"
|
||||
|
||||
"Labels", "Stimulations"
|
||||
"Hyperplane distance", "Streamed matrix"
|
||||
"Probability values", "Streamed matrix"
|
||||
|
||||
Labels
|
||||
~~~~~~
|
||||
|
||||
This output will contain the classification stimulations. Each time a new feature vector arrives to this box,
|
||||
a new classification process is triggered, resulting in the generation of the corresponding class stimulation.
|
||||
|
||||
Hyperplane distance
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This output reflects the classification algorithm status in the form of a matrix of value. This output will contain one or several distances
|
||||
to an hyperplane if the classifier provide it. If not, the matrix will have 0 dimension. The format of this output directly depend on
|
||||
the classification algorithm and of the strategy used by the processor box.
|
||||
|
||||
Probability values
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This output reflects the classification algorithm status in the form of a matrix of value. This output will contains one or several probabilities
|
||||
for a data to be on a class if the classifier provide it. If not, the matrix will have 0 dimension. The format of this output directly depend on
|
||||
the classification algorithm and of the strategy used by the processor box.
|
||||
|
||||
.. _Doc_BoxAlgorithm_ClassifierProcessor_Settings:
|
||||
|
||||
Settings
|
||||
--------
|
||||
|
||||
.. csv-table::
|
||||
:header: "Setting Name", "Type", "Default Value"
|
||||
|
||||
"Filename to load configuration from", "Filename", ""
|
||||
|
||||
Filename to load configuration from
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This setting points to the configuration file of the box generated by the
|
||||
:ref:`Doc_BoxAlgorithm_ClassifierTrainer` box. Its syntax depends on the selected algorithm.
|
||||
|
||||
.. _Doc_BoxAlgorithm_ClassifierProcessor_Examples:
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
This box is used in BCI pipelines in order to classify cerebral activity states. For a detailed scenario using this
|
||||
box and its associated :ref:`Doc_BoxAlgorithm_ClassifierTrainer`, please see the **motor imagary**
|
||||
BCI scenario in the sample scenarios.
|
||||
|
||||
@@ -0,0 +1,321 @@
|
||||
.. _Doc_BoxAlgorithm_ClassifierTrainer:
|
||||
|
||||
Classifier trainer
|
||||
==================
|
||||
|
||||
.. container:: attribution
|
||||
|
||||
:Author:
|
||||
Yann Renard, Guillaume Serriere
|
||||
:Company:
|
||||
INRIA/IRISA
|
||||
|
||||
.. image:: images/Doc_BoxAlgorithm_ClassifierTrainer.png
|
||||
|
||||
Performs classifier training with cross-validation -based error estimation
|
||||
|
||||
The *Classifier Trainer* box is a generic box for training models to classify input data.
|
||||
It works in conjunction with the :ref:`Doc_BoxAlgorithm_ClassifierProcessor` box.
|
||||
This box' role is to expose a generic interface to the rest of the BCI pipelines. The box
|
||||
will generate an internal structure according to the multiclass strategy and the learning
|
||||
algorithm selected.
|
||||
|
||||
The behavior is simple, the box collects a number of feature vectors. Those feature vectors
|
||||
are labelled depending on the input they arrive on. When a specific stimulation arrives, a training
|
||||
process is triggered. This process can take some time so this box should be used offline. Depending on the
|
||||
settings you enter, you will be able to perform a k-fold test to estimate the accuracy of the learned
|
||||
classifier. When this training stimulation is received, the box generates a configuration file that will
|
||||
be usable online by the :ref:`Doc_BoxAlgorithm_ClassifierProcessor` box.
|
||||
Finally, the box outputs a particular stimulation (OVTK_StimulationId_TrainCompleted)
|
||||
on its output, that can be used to trigger further treatments in the scenario.
|
||||
|
||||
Inputs
|
||||
------
|
||||
|
||||
.. csv-table::
|
||||
:header: "Input Name", "Stream Type"
|
||||
|
||||
"Stimulations", "Stimulations"
|
||||
"Features for class 1", "Feature vector"
|
||||
"Features for class 2", "Feature vector"
|
||||
|
||||
This box can have a variable number of inputs. If you need more than two classes, feel free to add more
|
||||
inputs and to use a proper strategy/classifier combination to handle more than two classes.
|
||||
|
||||
Stimulations
|
||||
~~~~~~~~~~~~
|
||||
|
||||
The first input receives a stimulation stream. Only one stimulation of this stream is important, the one
|
||||
that triggers the training process. When this stimulation is received, all the feature vectors are labelled
|
||||
and sent to the classification algorithm. The training is triggered and executed. Then the classification
|
||||
algorithm generates a configuration file that will be used online by the :ref:`Doc_BoxAlgorithm_ClassifierProcessor` box.
|
||||
|
||||
Features for class 1
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This input receives the feature vector for the first class.
|
||||
|
||||
Features for class 2
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This input receives the feature vector for the second class.
|
||||
|
||||
Outputs
|
||||
-------
|
||||
|
||||
.. csv-table::
|
||||
:header: "Output Name", "Stream Type"
|
||||
|
||||
"Train-completed Flag", "Stimulations"
|
||||
|
||||
Train-completed Flag
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The stimulation OVTK_StimulationId_TrainCompleted is raised on this output when the classifier trainer has finished its job.
|
||||
|
||||
.. _Doc_BoxAlgorithm_ClassifierTrainer_Settings:
|
||||
|
||||
Settings
|
||||
--------
|
||||
|
||||
.. csv-table::
|
||||
:header: "Setting Name", "Type", "Default Value"
|
||||
|
||||
"Train trigger", "Stimulation", "OVTK_StimulationId_Train"
|
||||
"Filename to save configuration to", "Filename", "${Path_UserData}/my-classifier.xml"
|
||||
"Multiclass strategy to apply", "Classification strategy", "Native"
|
||||
"Class 1 label", "Stimulation", "OVTK_StimulationId_Label_01"
|
||||
"Class 2 label", "Stimulation", "OVTK_StimulationId_Label_02"
|
||||
"Algorithm to use", "Classification algorithm", "Linear Discrimimant Analysis (LDA)"
|
||||
"Use shrinkage", "Boolean", "false"
|
||||
"Shrinkage coefficient (-1 == auto)", "Float", "-1.000000"
|
||||
"Shrinkage: Force diagonal cov (DDA)", "Boolean", "false"
|
||||
"Number of partitions for k-fold cross-validation test", "Integer", "10"
|
||||
"Balance classes", "Boolean", "false"
|
||||
|
||||
The number of settings of this box can vary depending on the classification algorithm you choose. Such algorithm
|
||||
could have specific input OpenViBE::Kernel::IParameter objects (see OpenViBE::Kernel::IAlgorithmProxy for details). If
|
||||
the type of those parameters is simple enough to be handled in the GUI, then additional settings will be added to this box.
|
||||
**After switching a strategy or a classifier, you will have to close and re-open the settings configuration dialog to see the parameters of the new classifier.** Supported parameter types are : Integers, Floats, Enumerations, Booleans. The documentation for those
|
||||
parameters can not be done in this page because it is impossible to know at this time what classifier thus what hyper
|
||||
parameters you will have available. This will depend on the classification algorihtms that are be implemented in OpenViBE.
|
||||
|
||||
Train trigger
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
The stimulation that triggers the training process and save the learned classifier to disk.
|
||||
|
||||
Filename to save configuration to
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This setting points to the configuration file where to save the result of the training for later online use. This
|
||||
configuration file is used by the :ref:`Doc_BoxAlgorithm_ClassifierProcessor` box. Its syntax
|
||||
depends on the selected algorithm.
|
||||
|
||||
Multiclass strategy to apply
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This setting is the strategy to use. You can choose any registered ``OVTK_TypeId_ClassificationStrategy``
|
||||
strategy you want.
|
||||
|
||||
Class 1 label
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
This is the stimulation to send when the classifier algorithm detects a class-1 feature vector
|
||||
|
||||
Class 2 label
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
This is the stimulation to send when the classifier algorithm detects a class-2 feature vector
|
||||
|
||||
Algorithm to use
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
This setting is the classifier to use. You can choose any registered ``OVTK_TypeId_ClassifierAlgorithm``
|
||||
algorithm you want.
|
||||
|
||||
Number of partitions for k-fold cross-validation test
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you want to perform a k-fold test, you should enter something else than 0 or 1 here. A k-fold test generally gives
|
||||
a better estimate of the classifiers accuracy than naive testing with the training data. The classifier may overfit
|
||||
the training data, and get a good accuracy with the observed data, but not be able to generalize to unseen data.
|
||||
In cross-validation, the idea is to divide the set of feature vectors in a number of partitions. The classification algorithm
|
||||
is trained on some of the partitions and its accuracy is tested on the others. However, the classifier produced by the box is
|
||||
the classifier trained with the whole data. The cross-validation is only an error estimation tool, it does not affect
|
||||
the resulting model. See the miscellaneous section for details on how the k-fold test is done in this box, and possible
|
||||
caveats about the cross-validation procedure.
|
||||
|
||||
Balance classes
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
If the number of class labels is unbalanced, the classifiers tend to be biased towards the majority labels.
|
||||
This option can be used to resample the dataset to feature all classes equally.
|
||||
|
||||
The algorithm first looks how many examples there are in the majority class. Lets say this is n. Then, if class k has m examples,
|
||||
it will random sample n-m examples with replacement from class k, appending them to the dataset. This will be done for each class.
|
||||
In the end, each class will have n examples and all except the majority class will have some duplicate training vectors.
|
||||
This can be seen as a technique to weight the importance of examples for such classifiers that do not support setting example weights
|
||||
or class weight prior, and can in general be attempted with arbitrary learning algorithms.
|
||||
|
||||
Enabling this option may make sense if the box is used for incremental learning, where all classes may not be equally represented
|
||||
in the training data obtained so far, even if the design itself is balanced. Note that enabling this will make the cross-validation
|
||||
results optimistic. In most conditions, the feature should be disabled.
|
||||
|
||||
.. _Doc_BoxAlgorithm_ClassifierTrainer_Examples:
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
This box is used in BCI pipelines in order to classify cerebral activity states. For a detailed scenario using this
|
||||
box and its associated :ref:`Doc_BoxAlgorithm_ClassifierProcessor`, please see the **motor imagary**
|
||||
BCI scenario in the sample scenarios. An even more simple tutorial with artificial data
|
||||
is available in the **box-tutorials/** folder.
|
||||
|
||||
.. _Doc_BoxAlgorithm_ClassifierTrainer_Miscellaneous:
|
||||
|
||||
Miscellaneous
|
||||
-------------
|
||||
|
||||
The box supports various multiclass strategies and classifiers as plugins.
|
||||
|
||||
\par Available strategy:
|
||||
Strategy refers to how feature vectors are routed to one or more classifiers, which possibly can handle only 2 classes themselves.
|
||||
|
||||
\par Native
|
||||
Use the classifier training algorithm without a pairwise strategy. All the data is passed to a single classifier trainer.
|
||||
|
||||
\par One Vs All
|
||||
Use a pairwise strategy which consists of training each class against all the others, creating n classifiers for n classes.
|
||||
|
||||
\par One vs One
|
||||
Use a airwise strategy which trains one classifier for each pair of classes. Then we use a decision startegy to extract the most likely class. There are three differents decision strategy:
|
||||
\li Voting: method based on a simple majority voting process
|
||||
\li HT: method described in: Hastie, Trevor ; Tibshirani, Robert. Classification by pairwise coupling. The Annals of Statistics 26 (1998), no. 2, 451--471
|
||||
\li PKPD: method describe in: Price, S. Knerr, L. Personnaz, and G. Dreyfus. Pairwise neural network classifiers with probabilistic outputs. In G. Tesauro, D. Touretzky, and T. Leen (eds.)
|
||||
Advances in Neural Information Processing Systems 7 (NIPS-94), pp. 1109-1116. MIT Press, 1995.
|
||||
You cannot use every algorithm with every decision strategy, but the interface will restain the choice according to your selection.
|
||||
|
||||
\par Available classifiers:
|
||||
|
||||
\par Support Vector Machine (SVM)
|
||||
A well-known classifier supporting non-linear classification via kernels. The implementation is based on LIBSVM 2.91, which is included in the OpenViBE source tree. The parameters exposed in the GUI correspond to LIBSVM parameters. For more information on LIBSVM, see <a href="http://www.csie.ntu.edu.tw/~cjlin/libsvm/">here</a>.
|
||||
\par
|
||||
This algorithm provides only probabilities.
|
||||
|
||||
\par Linear Discriminant Analysis (LDA)
|
||||
A simple and fast linear classifier. For description, see any major textbook on Machine Learning or Statistics (e.g. Duda, Hart & Stork, or Hastie, Tibshirani & Friedman). This algorithm can be used with a regularized covariance matrix
|
||||
according to a method proposed by Ledoit & Wolf: "A Well-Conditioned Estimator for Large-Dimensional Covariance Matrices", 2004.
|
||||
The Linear Discriminant Analysis has the following options.
|
||||
\par
|
||||
\li Use shrinkage: Use a classic or a regularized covariance matrix.
|
||||
\li Shrinkage: A value s between [0,1] sets a linear weight between dataCov and priorCov. I.e. cov=(1-s)\*dataCov+s\*priorCov.
|
||||
Value <0 is used to auto-estimate the shrinking coefficient (default). If var(x) is a vector of empirical variances of all data dimensions, priorCov is a
|
||||
diagonal matrix with a single value mean(var(x)) pasted on its diagonal. Used only if use shrinkage is checked.
|
||||
\li Force diagonal cov (DDA): This sets the nondiagonal entries of the covariance matrices to zero. Used only if Use shrinkage is checked.
|
||||
\par
|
||||
Note that setting shrinkage to 0 should get you the regular LDA behavior. If you additionally force the covariance to be diagonal, you should get a model resembling the Naive Bayes classifier.
|
||||
\par
|
||||
This algorithm provides both hyperplane distance and probabilities.
|
||||
|
||||
\par Multilayer Perceptron (MLP)
|
||||
A classifier algorithm which relies on an artificial neural network (<a href="https://hal.inria.fr/inria-00099922/en">Laurent Bougrain. Practical introduction to artificial neural networks. IFAC symposium on automation in Mining, Mineral and Metal Processing -
|
||||
MMM'04, Sep 2004, Nancy, France, 6 p, 2004.</a>). In OpenViBE, the MLP is a 2-layer neural network. The hyperbolic tangent is the activation function of the
|
||||
neurons inside the hidden layer. The network is trained using the backpropagation of the gradient. During the training, 80% of the training set is used to compute the gradient,
|
||||
and 20% is used to validate the new model. The different weights and biases are updated only once per iteration (just before the validation). A coefficient alpha (learning coefficient) is used to moderate the importance of
|
||||
the modification of weights and biases to avoid oscillations. The learning stops when the difference of the error per element (computed during validation) of two consecutive iterations is under the value epsilon given as a parameter.
|
||||
\par
|
||||
\li Number of neurons in hidden layer: number of neurons that will be used in the hidden layer.
|
||||
\li Learning stop condition : the epsilon value used to stop the learning
|
||||
\li Learning coefficient: a coefficient which influence the speed of learning. The smaller the coefficient is, the longer the learning will take, the more chance you will have to get a good solution.
|
||||
\par
|
||||
Note that feature vectors are normalized between -1 and 1 (using the min/max of the training set) to avoid saturation of the hyperbolic tangent.
|
||||
\par
|
||||
This algorithm provides both hyperplane distance (identity of output layer) and probabilites (softmax function on output layer).
|
||||
|
||||
\par Cross Validation
|
||||
|
||||
In this section, we will detail how the k-fold test is implemented in this box. For the k-fold test to be performed, you
|
||||
have to choose more than 1 partition in the related settings. Suppose you chose ``n`` partitions. Then when trigger stimulation
|
||||
is received, the feature vector set is splitted in ``n`` consecutive segments. The classification algorithm is trained on
|
||||
``n-1`` of those segments and tested on the last one. This is performed for each segment.
|
||||
|
||||
For example, suppose you have 5 partitions of feature vectors (``FVs)``
|
||||
|
||||
.. code::
|
||||
|
||||
+------+ +------+ +------+ +------+ +------+
|
||||
| FVs1 | | FVs2 | | FVs3 | | FVs4 | | FVs5 |
|
||||
+------+ +------+ +------+ +------+ +------+
|
||||
|
||||
For the first training, a feature vector set is built form the ``FVs2,`` ``FVs3,`` ``FVs4,`` ``FVs5.`` The classifier algorithm
|
||||
is trained on this feature vector set. Then the classifier is tested on the ``FVs1`` :
|
||||
|
||||
.. code::
|
||||
|
||||
+------+ +---------------------------------+
|
||||
| FVs1 | | Training Feature Vector Set 1 |
|
||||
+------+ +---------------------------------+
|
||||
|
||||
Then, a feature vector set is built form the ``FVs1,`` ``FVs3,`` ``FVs4,`` ``FVs5.`` The classifier algorithm
|
||||
is trained on this feature vector set. Then the classifier is tested on the ``FVs2`` :
|
||||
|
||||
.. code::
|
||||
|
||||
+-------+ +------+ +------------------------+
|
||||
| Train | | FVs2 | | ing Feat. Vector Set 2 |
|
||||
+-------+ +------+ +------------------------+
|
||||
|
||||
The same process if performed on all the partitions :
|
||||
|
||||
.. code::
|
||||
|
||||
+---------------+ +------+ +---------------+
|
||||
|Training Featur| | FVs3 | |e Vector Set 3 |
|
||||
+---------------+ +------+ +---------------+
|
||||
+------------------------+ +------+ +------+
|
||||
|Training Feature Vector | | FVs4 | |Set 4 |
|
||||
+------------------------+ +------+ +------+
|
||||
+---------------------------------+ +------+
|
||||
| Training Feature Vector Set 5 | | FVs5 |
|
||||
+---------------------------------+ +------+
|
||||
|
||||
Important things to consider :
|
||||
|
||||
- The more partitions you have, the more feature vectors you have in your training sets... and the less examples
|
||||
|
||||
you'll have to test on. This means that the result of the test will probably be less reliable.
|
||||
|
||||
In conclusion, be careful when choosing this k-fold test setting. Typical value range from 4 partitions (train on 75% of the feature vectors and
|
||||
test on 25% - 4 times) to 10 partitions (train on 90% of the feature vectors and test on 10% - 10 times).
|
||||
|
||||
Note that the cross-validation performed by the classifier trainer box in OpenViBE may be optimistic.
|
||||
The cross-validation computation is working as it should, but it cannot take into account what happens outside
|
||||
the classifier trainer box. In OpenViBE scenarios, there may be e.g. time overlap from epoching, feature
|
||||
vectors drawn from the same epoch ending up in the same cross-validation partition, and (supervised)
|
||||
preprocessing such as CSP or xDAWN potentially overfitting the data before its given to the classifier trainer.
|
||||
Such situations are not compatible with the theoretical assumption that the feature vectors are
|
||||
independent and identically distributed (the typical iid assumption in machine learning) across
|
||||
train and test. To do cross-validation controlling for such issues, we have provided
|
||||
a more advanced cross-validation tutorial as part of the OpenViBE web documentation.
|
||||
|
||||
\par Confusion Matrices
|
||||
|
||||
At the end of the training, the box will print one or two confusion matrices, depending if cross-validation
|
||||
was used: one matrix for the cross-validation, the other for the training data. Each matrix will contain true
|
||||
class as rows, and predicted class as columns. The diagonal describes the percentage of correct predictions per class.
|
||||
Although the matrix can be optimistic (see above section about the cross-validation), it may give useful
|
||||
diagnostic information. For example, if the accuracy is very skewed towards one class, this may indicate
|
||||
a problem if the design is supposed to be balanced. The problem may originate e.g. from the original data
|
||||
source, the signal processing chains for the different classes, or the classifier learning algorithm. These need
|
||||
then to be investigated. Also, if very low accuracies are observed in these matrices, it may give reason
|
||||
to suspect that prediction accuracies on fresh data might be likewise lacking -- or worse.
|
||||
|
||||
\par Incremental Learning
|
||||
|
||||
The box can also be used for simple incremental (online) learning. To achieve this, simply send the box the training
|
||||
stimulation and it will train a classifier with all the data it has received so far. You can give it more
|
||||
feature vectors later, and trigger the learning again by sending another stimulation. Likewise, the corresponding
|
||||
classifier processor box can be made to load new classifiers during playback. With classifiers like LDA,
|
||||
this practice is usually feasible when the data is reasonably sized (as in basic motor imagery).
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
.. _Doc_BoxAlgorithm_VotingClassifier:
|
||||
|
||||
Voting Classifier
|
||||
=================
|
||||
|
||||
.. container:: attribution
|
||||
|
||||
:Author:
|
||||
Yann Renard
|
||||
:Company:
|
||||
INRIA
|
||||
|
||||
.. image:: images/Doc_BoxAlgorithm_VotingClassifier.png
|
||||
|
||||
Each classifier used as input is assumed to have its own two-class output stream. Mainly designed for P300 scenario use.
|
||||
|
||||
The purpose of this simple classifier is to choose between multiple two class classifiers which
|
||||
one mostly fits a condition. For example imagine ``n`` states. Each of those states can be either active
|
||||
or inactive. Additionally, imagine you want only one active state at a time. Then you can have ``n``
|
||||
two-class classifiers telling for each state if it is active or not, and a following voting classifier
|
||||
that chooses which of those states is the *most active*.
|
||||
|
||||
Inputs
|
||||
------
|
||||
|
||||
.. csv-table::
|
||||
:header: "Input Name", "Stream Type"
|
||||
|
||||
"Classification result 1", "Stimulations"
|
||||
"Classification result 2", "Stimulations"
|
||||
|
||||
You can add as many inputs as you need depending on the number of preceeding states.
|
||||
|
||||
The inputs of this classifier can be changed to either streamed matrix of stimulations.
|
||||
|
||||
In the case you choose stimulations, each active stimulation gives a point
|
||||
to the preceeding state. Both an inactive and a reject stimulation gives no point.
|
||||
Any other stimulation is ignored.
|
||||
After a number of repetitions, the state with the best score is chosen.
|
||||
|
||||
In the case you choose streamed matrix, the matrix must have only one
|
||||
element. This element is used as a score coefficient (in place of the simple 1-0 of the previous
|
||||
case). After a number of repetitions, the state with the best score is chosen.
|
||||
|
||||
Classification result 1
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Input stream for the first state.
|
||||
|
||||
Classification result 2
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Input stream for the second state.
|
||||
|
||||
Outputs
|
||||
-------
|
||||
|
||||
.. csv-table::
|
||||
:header: "Output Name", "Stream Type"
|
||||
|
||||
"Classification choice", "Stimulations"
|
||||
|
||||
Classification choice
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This output sends a new stimulation as soon as the classifier received the correct number of votes
|
||||
from the preceeding states. The output stimulation is based on the 5th setting of the box. First
|
||||
state being selected would send exactly this stimulation. Second state would send this
|
||||
stimulation + 1 etc.
|
||||
|
||||
.. _Doc_BoxAlgorithm_VotingClassifier_Settings:
|
||||
|
||||
Settings
|
||||
--------
|
||||
|
||||
.. csv-table::
|
||||
:header: "Setting Name", "Type", "Default Value"
|
||||
|
||||
"Number of repetitions", "Integer", "12"
|
||||
"Target class label", "Stimulation", "OVTK_StimulationId_Target"
|
||||
"Non target class label", "Stimulation", "OVTK_StimulationId_NonTarget"
|
||||
"Reject class label", "Stimulation", "OVTK_StimulationId_Label_00"
|
||||
"Result class label base", "Stimulation", "OVTK_StimulationId_Label_01"
|
||||
"Choose one if ex-aequo", "Boolean", "false"
|
||||
|
||||
Number of repetitions
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This setting tells the box how many votes it needs before choosing. If the box uses stimulations,
|
||||
it must receive either the target or non target stimulation to consider a state has been voted.
|
||||
Additionaly, the box waits each state to be voted the correct number of times to take a decision.
|
||||
|
||||
Target class label
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If the box uses stimulations, this settings tells what stimulation reflects that the state is active.
|
||||
|
||||
Non target class label
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If the box uses stimulations, this settings tells what stimulation reflects that the state is inactive.
|
||||
|
||||
Reject class label
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In case no choice can be made (for example, there are more than one state with the highest score), the
|
||||
voting classifier can choose to reject the vote and send a specific stimulation for this. This is more
|
||||
likely to happen when the box works on stimulation better than streamed matrix. You can force this box
|
||||
to choose using the 6th setting.
|
||||
|
||||
Result class label base
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This stimulation is used as a basis for the stimulations to send when a state is selected. First
|
||||
state being selected would send exactly this stimulation. Second state would send this
|
||||
stimulation + 1 etc.
|
||||
|
||||
Choose one if ex-aequo
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This setting can force the box to choose a state even if more than one state have the highest score.
|
||||
In such case, there won't be any rejection. The way the box decides between the ex-aequo candidates
|
||||
is undefined.
|
||||
|
||||
.. _Doc_BoxAlgorithm_VotingClassifier_Examples:
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
This box is used in the *P300 speller* and the *P300 magic card* BCIs. Please see those
|
||||
scenarios in the sample *openvibe-scenarios*.
|
||||
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,423 @@
|
||||
#include "ovpCAlgorithmClassifierLDA.h"
|
||||
|
||||
#if defined TARGET_HAS_ThirdPartyEIGEN
|
||||
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
||||
#include <xml/IXMLHandler.h>
|
||||
|
||||
#include <Eigen/Eigenvalues>
|
||||
|
||||
#include "../algorithms/ovpCAlgorithmConditionedCovariance.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
|
||||
static const char* const TYPE_NODE_NAME = "LDA";
|
||||
static const char* const CLASSES_NODE_NAME = "Classes";
|
||||
//static const char* const COEFFICIENTS_NODE_NAME = "Weights";
|
||||
//static const char* const BIAS_DISTANCE_NODE_NAME = "Bias-distance";
|
||||
//static const char* const COEFFICIENT_PROBABILITY_NODE_NAME = "Coefficient-probability";
|
||||
static const char* const COMPUTATION_HELPERS_CONFIGURATION_NODE = "Class-config-list";
|
||||
static const char* const LDA_CONFIG_FILE_VERSION_ATTRIBUTE_NAME = "version";
|
||||
|
||||
extern const char* const CLASSIFIER_ROOT;
|
||||
|
||||
int LDAClassificationCompare(CMatrix& first, CMatrix& second)
|
||||
{
|
||||
//We first need to find the best classification of each.
|
||||
double* buffer = first.getBuffer();
|
||||
const double maxFirst = *(std::max_element(buffer, buffer + first.getBufferElementCount()));
|
||||
|
||||
buffer = second.getBuffer();
|
||||
const double maxSecond = *(std::max_element(buffer, buffer + second.getBufferElementCount()));
|
||||
|
||||
//Then we just compared them
|
||||
if (OVFloatEqual(maxFirst, maxSecond)) { return 0; }
|
||||
if (maxFirst > maxSecond) { return -1; }
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
#define LDA_DEBUG 0
|
||||
#if LDA_DEBUG
|
||||
void CAlgorithmClassifierLDA::dumpMatrix(ILogManager &rMgr, const MatrixXdRowMajor &mat, const CString &desc)
|
||||
{
|
||||
rMgr << Kernel::LogLevel_Info << desc << "\n";
|
||||
for (int i = 0 ; i < mat.rows() ; i++)
|
||||
{
|
||||
rMgr << Kernel::LogLevel_Info << "Row " << i << ": ";
|
||||
for (int j = 0 ; j < mat.cols() ; j++) { rMgr << mat(i,j) << " "; }
|
||||
rMgr << "\n";
|
||||
}
|
||||
}
|
||||
#else
|
||||
void CAlgorithmClassifierLDA::dumpMatrix(Kernel::ILogManager& /* rMgr */, const MatrixXdRowMajor& /*mat*/, const CString& /*desc*/) { }
|
||||
#endif
|
||||
|
||||
bool CAlgorithmClassifierLDA::initialize()
|
||||
{
|
||||
// Initialize the Conditioned Covariance Matrix algorithm
|
||||
m_covAlgorithm = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_ClassId_Algorithm_ConditionedCovariance));
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_covAlgorithm->initialize(), "Failed to initialize covariance algorithm", Kernel::ErrorType::Internal);
|
||||
|
||||
// This is the weight parameter local to this module and automatically exposed to the GUI. Its redirected to the corresponding parameter of the cov alg.
|
||||
Kernel::TParameterHandler<double> ip_shrinkage(this->getInputParameter(OVP_Algorithm_ClassifierLDA_InputParameterId_Shrinkage));
|
||||
ip_shrinkage.setReferenceTarget(m_covAlgorithm->getInputParameter(OVP_Algorithm_ConditionedCovariance_InputParameterId_Shrinkage));
|
||||
|
||||
Kernel::TParameterHandler<bool> ip_diagonalCov(this->getInputParameter(OVP_Algorithm_ClassifierLDA_InputParameterId_DiagonalCov));
|
||||
ip_diagonalCov = false;
|
||||
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> op_configuration(this->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_Config));
|
||||
op_configuration = nullptr;
|
||||
|
||||
return CAlgorithmClassifier::initialize();
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierLDA::uninitialize()
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(m_covAlgorithm->uninitialize(), "Failed to uninitialize covariance algorithm", Kernel::ErrorType::Internal);
|
||||
|
||||
this->getAlgorithmManager().releaseAlgorithm(*m_covAlgorithm);
|
||||
|
||||
return CAlgorithmClassifier::uninitialize();
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierLDA::train(const Toolkit::IFeatureVectorSet& dataset)
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(this->initializeExtraParameterMechanism(), "Failed to unitialize extra parameters", Kernel::ErrorType::Internal);
|
||||
|
||||
//We need to clear list because a instance of this class should support more that one training.
|
||||
m_labels.clear();
|
||||
m_discriminantFunctions.clear();
|
||||
|
||||
const bool useShrinkage = this->getBooleanParameter(OVP_Algorithm_ClassifierLDA_InputParameterId_UseShrinkage);
|
||||
|
||||
bool diagonalCov;
|
||||
if (useShrinkage)
|
||||
{
|
||||
this->getDoubleParameter(OVP_Algorithm_ClassifierLDA_InputParameterId_Shrinkage);
|
||||
diagonalCov = this->getBooleanParameter(OVP_Algorithm_ClassifierLDA_InputParameterId_DiagonalCov);
|
||||
}
|
||||
else
|
||||
{
|
||||
//If we don't use shrinkage we need to set lambda to 0.
|
||||
Kernel::TParameterHandler<double> ip_shrinkage(this->getInputParameter(OVP_Algorithm_ClassifierLDA_InputParameterId_Shrinkage));
|
||||
ip_shrinkage = 0.0;
|
||||
|
||||
Kernel::TParameterHandler<bool> ip_diagonalCov(this->getInputParameter(OVP_Algorithm_ClassifierLDA_InputParameterId_DiagonalCov));
|
||||
ip_diagonalCov = false;
|
||||
diagonalCov = false;
|
||||
}
|
||||
|
||||
OV_ERROR_UNLESS_KRF(this->uninitializeExtraParameterMechanism(), "Failed to ininitialize extra parameters", Kernel::ErrorType::Internal);
|
||||
|
||||
// IO to the covariance alg
|
||||
Kernel::TParameterHandler<CMatrix*> op_mean(m_covAlgorithm->getOutputParameter(OVP_Algorithm_ConditionedCovariance_OutputParameterId_Mean));
|
||||
Kernel::TParameterHandler<CMatrix*> op_covMatrix(m_covAlgorithm->getOutputParameter(OVP_Algorithm_ConditionedCovariance_OutputParameterId_CovarianceMatrix));
|
||||
Kernel::TParameterHandler<CMatrix*> ip_dataset(m_covAlgorithm->getInputParameter(OVP_Algorithm_ConditionedCovariance_InputParameterId_FeatureVectorSet));
|
||||
|
||||
const size_t nRows = dataset.getFeatureVectorCount();
|
||||
const size_t nCols = (nRows > 0 ? dataset[0].getSize() : 0);
|
||||
this->getLogManager() << Kernel::LogLevel_Debug << "Feature set input dims [" << dataset.getFeatureVectorCount() << "x" << nCols << "]\n";
|
||||
|
||||
OV_ERROR_UNLESS_KRF(nRows != 0 && nCols != 0, "Input data has a zero-size dimension, dims = [" << nRows << "x" << nCols << "]",
|
||||
Kernel::ErrorType::BadInput);
|
||||
|
||||
// The max amount of classes to be expected
|
||||
Kernel::TParameterHandler<uint64_t> ip_pNClasses(this->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_NClasses));
|
||||
m_nClasses = size_t(ip_pNClasses);
|
||||
|
||||
// Count the classes actually present
|
||||
std::vector<size_t> nClasses;
|
||||
nClasses.resize(m_nClasses);
|
||||
|
||||
for (size_t i = 0; i < dataset.getFeatureVectorCount(); ++i)
|
||||
{
|
||||
size_t classIdx = size_t(dataset[i].getLabel());
|
||||
nClasses[classIdx]++;
|
||||
}
|
||||
|
||||
// Get class labels
|
||||
for (size_t i = 0; i < m_nClasses; ++i)
|
||||
{
|
||||
m_labels.push_back(i);
|
||||
m_discriminantFunctions.push_back(CAlgorithmLDADiscriminantFunction());
|
||||
}
|
||||
|
||||
// Per-class means and a global covariance are used to form the LDA model
|
||||
std::vector<Eigen::MatrixXd> classMeans(m_nClasses);
|
||||
Eigen::MatrixXd globalCov = Eigen::MatrixXd::Zero(nCols, nCols);
|
||||
|
||||
// We need the means per class
|
||||
for (size_t classIdx = 0; classIdx < m_nClasses; classIdx++)
|
||||
{
|
||||
if (nClasses[classIdx] > 0)
|
||||
{
|
||||
// const double label = m_labels[l_classIdx];
|
||||
const size_t examplesInClass = nClasses[classIdx];
|
||||
|
||||
// Copy all the data of the class to a matrix
|
||||
CMatrix classData;
|
||||
classData.resize(examplesInClass, nCols);
|
||||
double* buffer = classData.getBuffer();
|
||||
for (size_t i = 0; i < nRows; ++i)
|
||||
{
|
||||
if (dataset[i].getLabel() == classIdx)
|
||||
{
|
||||
memcpy(buffer, dataset[i].getBuffer(), nCols * sizeof(double));
|
||||
buffer += nCols;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the mean out of it
|
||||
Eigen::Map<MatrixXdRowMajor> dataMapper(classData.getBuffer(), examplesInClass, nCols);
|
||||
const Eigen::MatrixXd classMean = dataMapper.colwise().mean().transpose();
|
||||
classMeans[classIdx] = classMean;
|
||||
}
|
||||
else
|
||||
{
|
||||
Eigen::MatrixXd tmp;
|
||||
tmp.resize(nCols, 1);
|
||||
tmp.setZero();
|
||||
classMeans[classIdx] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
// We need a global covariance, use the regularized cov algorithm
|
||||
{
|
||||
ip_dataset->resize(nRows, nCols);
|
||||
double* buffer = ip_dataset->getBuffer();
|
||||
|
||||
// Insert all data as the input of the cov algorithm
|
||||
for (size_t i = 0; i < nRows; ++i)
|
||||
{
|
||||
memcpy(buffer, dataset[i].getBuffer(), nCols * sizeof(double));
|
||||
buffer += nCols;
|
||||
}
|
||||
|
||||
// Compute cov
|
||||
if (!m_covAlgorithm->process()) { OV_ERROR_KRF("Global covariance computation failed", Kernel::ErrorType::Internal); }
|
||||
|
||||
// Get the results from the cov algorithm
|
||||
Eigen::Map<MatrixXdRowMajor> covMapper(op_covMatrix->getBuffer(), nCols, nCols);
|
||||
globalCov = covMapper;
|
||||
}
|
||||
|
||||
//dumpMatrix(this->getLogManager(), mean[l_classIdx], "Mean");
|
||||
//dumpMatrix(this->getLogManager(), globalCov, "Shrinked cov");
|
||||
|
||||
if (diagonalCov)
|
||||
{
|
||||
for (size_t i = 0; i < nCols; ++i)
|
||||
{
|
||||
for (size_t j = i + 1; j < nCols; ++j)
|
||||
{
|
||||
globalCov(i, j) = 0.0;
|
||||
globalCov(j, i) = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the pseudoinverse of the global cov using eigen decomposition for self-adjoint matrices
|
||||
const double tolerance = 1e-10;
|
||||
Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> solver;
|
||||
solver.compute(globalCov);
|
||||
Eigen::VectorXd eigenValues = solver.eigenvalues();
|
||||
for (size_t i = 0; i < nCols; ++i) { if (eigenValues(i) >= tolerance) { eigenValues(i) = 1.0 / eigenValues(i); } }
|
||||
const Eigen::MatrixXd globalCovInv = solver.eigenvectors() * eigenValues.asDiagonal() * solver.eigenvectors().inverse();
|
||||
|
||||
// const MatrixXd globalCovInv = globalCov.inverse();
|
||||
//We send the bias and the weight of each class to ComputationHelper
|
||||
for (size_t i = 0; i < getClassCount(); ++i)
|
||||
{
|
||||
const double examplesInClass = nClasses[i];
|
||||
if (examplesInClass > 0)
|
||||
{
|
||||
const size_t totalExamples = dataset.getFeatureVectorCount();
|
||||
|
||||
// This formula e.g. in Hastie, Tibshirani & Friedman: "Elements...", 2nd ed., p. 109
|
||||
const Eigen::VectorXd weigth = (globalCovInv * classMeans[i]);
|
||||
const Eigen::MatrixXd inter = -0.5 * classMeans[i].transpose() * globalCovInv * classMeans[i];
|
||||
const double bias = inter(0, 0) + std::log(examplesInClass / totalExamples);
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Debug << "Bias for " << i << " is " << bias << ", from " << examplesInClass / totalExamples
|
||||
<< ", " << examplesInClass << "/" << totalExamples << ", int = " << inter(0, 0) << "\n";
|
||||
// dumpMatrix(this->getLogManager(), perClassMeans[i], "Means");
|
||||
|
||||
m_discriminantFunctions[i].setWeight(weigth);
|
||||
m_discriminantFunctions[i].setBias(bias);
|
||||
}
|
||||
else { this->getLogManager() << Kernel::LogLevel_Debug << "Class " << i << " has no examples\n"; }
|
||||
}
|
||||
|
||||
// Hack for classes with zero examples, give them valid models but such that will always lose
|
||||
size_t nonZeroClassIdx = 0;
|
||||
for (size_t i = 0; i < getClassCount(); ++i)
|
||||
{
|
||||
if (nClasses[i] > 0)
|
||||
{
|
||||
nonZeroClassIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < getClassCount(); ++i)
|
||||
{
|
||||
if (nClasses[i] == 0)
|
||||
{
|
||||
m_discriminantFunctions[i].setWeight(m_discriminantFunctions[nonZeroClassIdx].getWeight());
|
||||
m_discriminantFunctions[i].setBias(m_discriminantFunctions[nonZeroClassIdx].getBias() - 1.0); // Will always lose to the orig
|
||||
}
|
||||
}
|
||||
|
||||
m_nCols = nCols;
|
||||
|
||||
// Debug output
|
||||
//dumpMatrix(this->getLogManager(), globalCov, "Global cov");
|
||||
//dumpMatrix(this->getLogManager(), eigenValues, "Eigenvalues");
|
||||
//dumpMatrix(this->getLogManager(), eigenSolver.eigenvectors(), "Eigenvectors");
|
||||
//dumpMatrix(this->getLogManager(), globalCovInv, "Global cov inverse");
|
||||
//dumpMatrix(this->getLogManager(), m_coefficients, "Hyperplane weights");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierLDA::classify(const Toolkit::IFeatureVector& sample, double& classId, Toolkit::IVector& distance, Toolkit::IVector& probability)
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(!m_discriminantFunctions.empty(), "LDA discriminant function list is empty", Kernel::ErrorType::BadConfig);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(sample.getSize() == m_discriminantFunctions[0].getNWeight(),
|
||||
"Classifier expected " << m_discriminantFunctions[0].getNWeight() << " features, got " << sample.getSize(),
|
||||
Kernel::ErrorType::BadInput);
|
||||
|
||||
const Eigen::Map<Eigen::VectorXd> featureVec(const_cast<double*>(sample.getBuffer()), sample.getSize());
|
||||
const Eigen::VectorXd weights = featureVec;
|
||||
const size_t nClass = getClassCount();
|
||||
|
||||
std::vector<double> buffer(nClass);
|
||||
std::vector<double> probabBuffer(nClass);
|
||||
//We ask for all computation helper to give the corresponding class value
|
||||
for (size_t i = 0; i < nClass; ++i) { buffer[i] = m_discriminantFunctions[i].getValue(weights); }
|
||||
|
||||
//p(Ck | x) = exp(ak) / sum[j](exp (aj))
|
||||
// with aj = (Weight for class j).transpose() * x + (Bias for class j)
|
||||
|
||||
//Exponential can lead to nan results, so we reduce the computation and instead compute
|
||||
// p(Ck | x) = 1 / sum[j](exp(aj - ak))
|
||||
|
||||
//All ak are given by computation helper
|
||||
errno = 0;
|
||||
for (size_t i = 0; i < nClass; ++i)
|
||||
{
|
||||
double expSum = 0.;
|
||||
for (size_t j = 0; j < nClass; ++j) { expSum += exp(buffer[j] - buffer[i]); }
|
||||
probabBuffer[i] = 1 / expSum;
|
||||
// std::cout << "p " << i << " = " << probabilityValue[i] << ", v=" << valueArray[i] << ", " << errno << "\n";
|
||||
}
|
||||
|
||||
//Then we just find the highest probability and take it as a result
|
||||
const size_t classIdx = size_t(std::distance(buffer.begin(), std::max_element(buffer.begin(), buffer.end())));
|
||||
|
||||
distance.setSize(nClass);
|
||||
probability.setSize(nClass);
|
||||
|
||||
for (size_t i = 0; i < nClass; ++i)
|
||||
{
|
||||
distance[i] = buffer[i];
|
||||
probability[i] = probabBuffer[i];
|
||||
}
|
||||
|
||||
classId = m_labels[classIdx];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
XML::IXMLNode* CAlgorithmClassifierLDA::saveConfig()
|
||||
{
|
||||
XML::IXMLNode* algorithmNode = XML::createNode(TYPE_NODE_NAME);
|
||||
algorithmNode->addAttribute(LDA_CONFIG_FILE_VERSION_ATTRIBUTE_NAME, "1");
|
||||
|
||||
// Write the classifier to an .xml
|
||||
std::stringstream classes;
|
||||
|
||||
for (size_t i = 0; i < getClassCount(); ++i) { classes << m_labels[i] << " "; }
|
||||
|
||||
//Only new version should be recorded so we don't need to test
|
||||
XML::IXMLNode* helpersConfig = XML::createNode(COMPUTATION_HELPERS_CONFIGURATION_NODE);
|
||||
for (size_t i = 0; i < m_discriminantFunctions.size(); ++i) { helpersConfig->addChild(m_discriminantFunctions[i].getConfiguration()); }
|
||||
|
||||
XML::IXMLNode* tmpNode = XML::createNode(CLASSES_NODE_NAME);
|
||||
tmpNode->setPCData(classes.str().c_str());
|
||||
algorithmNode->addChild(tmpNode);
|
||||
algorithmNode->addChild(helpersConfig);
|
||||
|
||||
return algorithmNode;
|
||||
}
|
||||
|
||||
|
||||
//Extract a double from the PCDATA of a node
|
||||
double getFloatFromNode(XML::IXMLNode* pNode)
|
||||
{
|
||||
std::stringstream ss(pNode->getPCData());
|
||||
double res;
|
||||
ss >> res;
|
||||
return res;
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierLDA::loadConfig(XML::IXMLNode* configNode)
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(configNode->hasAttribute(LDA_CONFIG_FILE_VERSION_ATTRIBUTE_NAME),
|
||||
"Invalid model: model trained with an obsolete version of LDA", Kernel::ErrorType::BadConfig);
|
||||
|
||||
m_labels.clear();
|
||||
m_discriminantFunctions.clear();
|
||||
|
||||
XML::IXMLNode* tmpNode = configNode->getChildByName(CLASSES_NODE_NAME);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(tmpNode != nullptr, "Failed to retrieve xml node", Kernel::ErrorType::BadParsing);
|
||||
|
||||
loadClassesFromNode(tmpNode);
|
||||
|
||||
|
||||
//We send corresponding data to the computation helper
|
||||
XML::IXMLNode* configsNode = configNode->getChildByName(COMPUTATION_HELPERS_CONFIGURATION_NODE);
|
||||
|
||||
for (size_t i = 0; i < configsNode->getChildCount(); ++i)
|
||||
{
|
||||
m_discriminantFunctions.push_back(CAlgorithmLDADiscriminantFunction());
|
||||
m_discriminantFunctions[i].loadConfig(configsNode->getChild(i));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CAlgorithmClassifierLDA::loadClassesFromNode(XML::IXMLNode* node)
|
||||
{
|
||||
std::stringstream ss(node->getPCData());
|
||||
double value;
|
||||
while (ss >> value) { m_labels.push_back(value); }
|
||||
m_nClasses = m_labels.size();
|
||||
}
|
||||
|
||||
//Load the weight vector
|
||||
void CAlgorithmClassifierLDA::loadCoefsFromNode(XML::IXMLNode* node)
|
||||
{
|
||||
std::stringstream ss(node->getPCData());
|
||||
|
||||
std::vector<double> coefs;
|
||||
double value;
|
||||
while (ss >> value) { coefs.push_back(value); }
|
||||
|
||||
m_weights.resize(1, coefs.size());
|
||||
m_nCols = coefs.size();
|
||||
for (size_t i = 0; i < coefs.size(); ++i) { m_weights(0, i) = coefs[i]; }
|
||||
}
|
||||
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
#endif // TARGET_HAS_ThirdPartyEIGEN
|
||||
@@ -0,0 +1,98 @@
|
||||
#pragma once
|
||||
|
||||
#if defined TARGET_HAS_ThirdPartyEIGEN
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include "ovpCAlgorithmLDADiscriminantFunction.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include <xml/IXMLNode.h>
|
||||
|
||||
#include <stack>
|
||||
|
||||
#include <Eigen/Dense>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
class CAlgorithmLDADiscriminantFunction;
|
||||
|
||||
int LDAClassificationCompare(CMatrix& first, CMatrix& second);
|
||||
|
||||
typedef Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> MatrixXdRowMajor;
|
||||
|
||||
class CAlgorithmClassifierLDA final : public Toolkit::CAlgorithmClassifier
|
||||
{
|
||||
public:
|
||||
bool initialize() override;
|
||||
bool uninitialize() override;
|
||||
bool train(const Toolkit::IFeatureVectorSet& dataset) override;
|
||||
bool classify(const Toolkit::IFeatureVector& sample, double& classId, Toolkit::IVector& distance, Toolkit::IVector& probability) override;
|
||||
XML::IXMLNode* saveConfig() override;
|
||||
bool loadConfig(XML::IXMLNode* configNode) override;
|
||||
size_t getNProbabilities() override { return m_discriminantFunctions.size(); }
|
||||
size_t getNDistances() override { return m_discriminantFunctions.size(); }
|
||||
|
||||
_IsDerivedFromClass_Final_(CAlgorithmClassifier, OVP_ClassId_Algorithm_ClassifierLDA)
|
||||
|
||||
protected:
|
||||
// Debug method. Prints the matrix to the logManager. May be disabled in implementation.
|
||||
static void dumpMatrix(Kernel::ILogManager& pMgr, const MatrixXdRowMajor& mat, const CString& desc);
|
||||
|
||||
std::vector<double> m_labels;
|
||||
std::vector<CAlgorithmLDADiscriminantFunction> m_discriminantFunctions;
|
||||
|
||||
Eigen::MatrixXd m_coefficients;
|
||||
Eigen::MatrixXd m_weights;
|
||||
double m_biasDistance = 0;
|
||||
double m_w0 = 0;
|
||||
|
||||
size_t m_nCols = 0;
|
||||
size_t m_nClasses = 0;
|
||||
|
||||
Kernel::IAlgorithmProxy* m_covAlgorithm = nullptr;
|
||||
|
||||
private:
|
||||
void loadClassesFromNode(XML::IXMLNode* node);
|
||||
void loadCoefsFromNode(XML::IXMLNode* node);
|
||||
|
||||
size_t getClassCount() const { return m_nClasses; }
|
||||
};
|
||||
|
||||
class CAlgorithmClassifierLDADesc final : public Toolkit::CAlgorithmClassifierDesc
|
||||
{
|
||||
public:
|
||||
void release() override { }
|
||||
CString getName() const override { return CString("LDA Classifier"); }
|
||||
CString getAuthorName() const override { return CString("Jussi T. Lindgren / Guillaume Serrière"); }
|
||||
CString getAuthorCompanyName() const override { return CString("Inria / Loria"); }
|
||||
CString getShortDescription() const override { return CString("Estimates LDA using regularized or classic covariances"); }
|
||||
CString getDetailedDescription() const override { return CString(""); }
|
||||
CString getCategory() const override { return CString(""); }
|
||||
CString getVersion() const override { return CString("2.0"); }
|
||||
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
|
||||
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_Algorithm_ClassifierLDA; }
|
||||
IPluginObject* create() override { return new CAlgorithmClassifierLDA; }
|
||||
|
||||
bool getAlgorithmPrototype(Kernel::IAlgorithmProto& prototype) const override
|
||||
{
|
||||
prototype.addInputParameter(OVP_Algorithm_ClassifierLDA_InputParameterId_UseShrinkage, "Use shrinkage", Kernel::ParameterType_Boolean);
|
||||
prototype.addInputParameter(OVP_Algorithm_ClassifierLDA_InputParameterId_DiagonalCov, "Shrinkage: Force diagonal cov (DDA)",
|
||||
Kernel::ParameterType_Boolean);
|
||||
prototype.addInputParameter(OVP_Algorithm_ClassifierLDA_InputParameterId_Shrinkage, "Shrinkage coefficient (-1 == auto)", Kernel::ParameterType_Float);
|
||||
|
||||
CAlgorithmClassifierDesc::getAlgorithmPrototype(prototype);
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(CAlgorithmClassifierDesc, OVP_ClassId_Algorithm_ClassifierLDADesc)
|
||||
};
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
|
||||
|
||||
#endif // TARGET_HAS_ThirdPartyEIGEN
|
||||
@@ -0,0 +1,60 @@
|
||||
#include "ovpCAlgorithmClassifierNULL.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
|
||||
|
||||
bool CAlgorithmClassifierNULL::initialize()
|
||||
{
|
||||
Kernel::TParameterHandler<bool> ip_bParameter1(this->getInputParameter(OVP_Algorithm_ClassifierNULL_InputParameterId_Parameter1));
|
||||
Kernel::TParameterHandler<double> ip_Parameter2(this->getInputParameter(OVP_Algorithm_ClassifierNULL_InputParameterId_Parameter2));
|
||||
Kernel::TParameterHandler<uint64_t> ip_parameter3(this->getInputParameter(OVP_Algorithm_ClassifierNULL_InputParameterId_Parameter3));
|
||||
|
||||
ip_bParameter1 = true;
|
||||
ip_Parameter2 = 3.141592654;
|
||||
ip_parameter3 = OVTK_StimulationId_Label_00;
|
||||
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> op_configuration(this->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_Config));
|
||||
op_configuration = nullptr;
|
||||
|
||||
return CAlgorithmClassifier::initialize();
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierNULL::train(const Toolkit::IFeatureVectorSet& /*featureVectorSet*/)
|
||||
{
|
||||
Kernel::TParameterHandler<bool> ip_bParameter1(this->getInputParameter(OVP_Algorithm_ClassifierNULL_InputParameterId_Parameter1));
|
||||
Kernel::TParameterHandler<double> ip_Parameter2(this->getInputParameter(OVP_Algorithm_ClassifierNULL_InputParameterId_Parameter2));
|
||||
Kernel::TParameterHandler<uint64_t> ip_parameter3(this->getInputParameter(OVP_Algorithm_ClassifierNULL_InputParameterId_Parameter3));
|
||||
|
||||
OV_WARNING_K("Parameter 1 : " << ip_bParameter1);
|
||||
OV_WARNING_K("Parameter 2 : " << ip_Parameter2);
|
||||
OV_WARNING_K("Parameter 3 : " << ip_parameter3);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierNULL::classify(const Toolkit::IFeatureVector& /*featureVector*/, double& classId, Toolkit::IVector& distance, Toolkit::IVector& probability)
|
||||
{
|
||||
classId = 1 + (rand() % 3);
|
||||
|
||||
distance.setSize(1);
|
||||
probability.setSize(1);
|
||||
if (classId == 1)
|
||||
{
|
||||
distance[0] = -1;
|
||||
probability[0] = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
distance[0] = 1;
|
||||
probability[0] = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include <xml/IXMLNode.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
class CAlgorithmClassifierNULL final : public Toolkit::CAlgorithmClassifier
|
||||
{
|
||||
public:
|
||||
|
||||
CAlgorithmClassifierNULL() { }
|
||||
bool initialize() override;
|
||||
bool train(const Toolkit::IFeatureVectorSet& featureVectorSet) override;
|
||||
bool classify(const Toolkit::IFeatureVector& featureVector, double& classId, Toolkit::IVector& distance, Toolkit::IVector& probability) override;
|
||||
XML::IXMLNode* saveConfig() override { return nullptr; }
|
||||
bool loadConfig(XML::IXMLNode* /*configurationNode*/) override { return true; }
|
||||
size_t getNProbabilities() override { return 1; }
|
||||
size_t getNDistances() override { return 1; }
|
||||
|
||||
_IsDerivedFromClass_Final_(CAlgorithmClassifier, OVP_ClassId_Algorithm_ClassifierNULL)
|
||||
};
|
||||
|
||||
class CAlgorithmClassifierNULLDesc final : public Toolkit::CAlgorithmClassifierDesc
|
||||
{
|
||||
public:
|
||||
void release() override { }
|
||||
CString getName() const override { return CString("NULL Classifier (does nothing)"); }
|
||||
CString getAuthorName() const override { return CString("Yann Renard"); }
|
||||
CString getAuthorCompanyName() const override { return CString("INRIA"); }
|
||||
CString getShortDescription() const override { return CString(""); }
|
||||
CString getDetailedDescription() const override { return CString(""); }
|
||||
CString getCategory() const override { return CString("Samples"); }
|
||||
CString getVersion() const override { return CString("1.0"); }
|
||||
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
|
||||
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_Algorithm_ClassifierNULL; }
|
||||
IPluginObject* create() override { return new CAlgorithmClassifierNULL; }
|
||||
|
||||
bool getAlgorithmPrototype(Kernel::IAlgorithmProto& prototype) const override
|
||||
{
|
||||
CAlgorithmClassifierDesc::getAlgorithmPrototype(prototype);
|
||||
prototype.addInputParameter(OVP_Algorithm_ClassifierNULL_InputParameterId_Parameter1, "Parameter 1", Kernel::ParameterType_Boolean);
|
||||
prototype.addInputParameter(OVP_Algorithm_ClassifierNULL_InputParameterId_Parameter2, "Parameter 2", Kernel::ParameterType_Float);
|
||||
prototype.addInputParameter(OVP_Algorithm_ClassifierNULL_InputParameterId_Parameter3, "Parameter 3", Kernel::ParameterType_Enumeration,
|
||||
OV_TypeId_Stimulation);
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(CAlgorithmClassifierDesc, OVP_ClassId_Algorithm_ClassifierNULLDesc)
|
||||
};
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,317 @@
|
||||
#include "ovpCAlgorithmClassifierOneVsAll.h"
|
||||
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
#include <iostream>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
|
||||
static const char* const TYPE_NODE_NAME = "OneVsAll";
|
||||
static const char* const SUB_CLASSIFIER_IDENTIFIER_NODE_NAME = "SubClassifierIdentifier";
|
||||
static const char* const ALGORITHM_ID_ATTRIBUTE = "algorithm-id";
|
||||
static const char* const SUB_CLASSIFIER_COUNT_NODE_NAME = "SubClassifierCount";
|
||||
static const char* const SUB_CLASSIFIERS_NODE_NAME = "SubClassifiers";
|
||||
//static const char* const SUB_CLASSIFIER_NODE_NAME = "SubClassifier";
|
||||
|
||||
typedef std::pair<CMatrix*, CMatrix*> CIMatrixPointerPair;
|
||||
typedef std::pair<double, CMatrix*> CClassifierOutput;
|
||||
|
||||
bool CAlgorithmClassifierOneVsAll::initialize()
|
||||
{
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> op_Config(this->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_Config));
|
||||
op_Config = nullptr;
|
||||
|
||||
return CAlgorithmPairingStrategy::initialize();
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierOneVsAll::uninitialize()
|
||||
{
|
||||
while (!m_subClassifiers.empty()) { this->removeClassifierAtBack(); }
|
||||
return CAlgorithmPairingStrategy::uninitialize();
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierOneVsAll::train(const Toolkit::IFeatureVectorSet& dataset)
|
||||
{
|
||||
const size_t nClass = m_subClassifiers.size();
|
||||
std::map<double, size_t> classLabels;
|
||||
|
||||
for (size_t i = 0; i < dataset.getFeatureVectorCount(); ++i)
|
||||
{
|
||||
if (!classLabels.count(dataset[i].getLabel())) { classLabels[dataset[i].getLabel()] = 0; }
|
||||
classLabels[dataset[i].getLabel()]++;
|
||||
}
|
||||
|
||||
OV_ERROR_UNLESS_KRF(classLabels.size() == nClass,
|
||||
"Invalid samples count for [" << classLabels.size() << "] classes (expected samples for " << nClass << " classes)",
|
||||
Kernel::ErrorType::BadConfig);
|
||||
|
||||
//We set the CMatrix fo the first classifier
|
||||
const size_t size = dataset[0].getSize();
|
||||
Kernel::TParameterHandler<CMatrix*> reference(m_subClassifiers[0]->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_FeatureVectorSet));
|
||||
reference->resize(dataset.getFeatureVectorCount(), size + 1);
|
||||
|
||||
double* buffer = reference->getBuffer();
|
||||
for (size_t j = 0; j < dataset.getFeatureVectorCount(); ++j)
|
||||
{
|
||||
memcpy(buffer, dataset[j].getBuffer(), size * sizeof(double));
|
||||
//We let the space for the label
|
||||
buffer += (size + 1);
|
||||
}
|
||||
|
||||
//And then we just change adapt the label for each feature vector but we don't copy them anymore
|
||||
for (size_t c = 0; c < m_subClassifiers.size(); ++c)
|
||||
{
|
||||
Kernel::TParameterHandler<CMatrix*> ip_dataset(m_subClassifiers[c]->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_FeatureVectorSet));
|
||||
ip_dataset = static_cast<CMatrix*>(reference);
|
||||
|
||||
buffer = ip_dataset->getBuffer();
|
||||
for (size_t j = 0; j < dataset.getFeatureVectorCount(); ++j)
|
||||
{
|
||||
//Modify the class of each featureVector
|
||||
const double classLabel = dataset[j].getLabel();
|
||||
if (size_t(classLabel) == c) { buffer[size] = 0; }
|
||||
else { buffer[size] = 1; }
|
||||
buffer += (size + 1);
|
||||
}
|
||||
|
||||
m_subClassifiers[c]->process(OVTK_Algorithm_Classifier_InputTriggerId_Train);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierOneVsAll::classify(const Toolkit::IFeatureVector& sample, double& classId, Toolkit::IVector& distance, Toolkit::IVector& probability)
|
||||
{
|
||||
std::vector<CClassifierOutput> classification;
|
||||
|
||||
const size_t size = sample.getSize();
|
||||
|
||||
for (size_t i = 0; i < m_subClassifiers.size(); ++i)
|
||||
{
|
||||
Kernel::IAlgorithmProxy* subClassifier = this->m_subClassifiers[i];
|
||||
Kernel::TParameterHandler<CMatrix*> ip_sample(subClassifier->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_FeatureVector));
|
||||
Kernel::TParameterHandler<double> op_class(subClassifier->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_Class));
|
||||
Kernel::TParameterHandler<CMatrix*> op_values(subClassifier->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_ClassificationValues));
|
||||
Kernel::TParameterHandler<CMatrix*> op_probabilities(subClassifier->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_ProbabilityValues));
|
||||
ip_sample->resize(size);
|
||||
|
||||
double* buffer = ip_sample->getBuffer();
|
||||
memcpy(buffer, sample.getBuffer(), size * sizeof(double));
|
||||
subClassifier->process(OVTK_Algorithm_Classifier_InputTriggerId_Classify);
|
||||
|
||||
CMatrix* probabilities = static_cast<CMatrix*>(op_probabilities);
|
||||
//If the algorithm give a probability we take it, instead we take the first value
|
||||
if (probabilities->getDimensionCount() != 0) { classification.push_back(CClassifierOutput(double(op_class), probabilities)); }
|
||||
else { classification.push_back(CClassifierOutput(double(op_class), static_cast<CMatrix*>(op_values))); }
|
||||
this->getLogManager() << Kernel::LogLevel_Debug << i << " " << double(op_class) << " " << double((*op_probabilities)[0]) << " " << double(
|
||||
(*op_probabilities)[1]) << "\n";
|
||||
}
|
||||
|
||||
//Now, we determine the best classification
|
||||
CClassifierOutput best = CClassifierOutput(-1.0, static_cast<CMatrix*>(nullptr));
|
||||
classId = -1;
|
||||
|
||||
for (size_t i = 0; i < classification.size(); ++i)
|
||||
{
|
||||
CClassifierOutput& tmp = classification[i];
|
||||
if (int(tmp.first) == 0) // Predicts its "own" class, class=0
|
||||
{
|
||||
if (best.second == nullptr)
|
||||
{
|
||||
best = tmp;
|
||||
classId = double(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((*m_fAlgorithmComparison)((*best.second), *(tmp.second)) > 0)
|
||||
{
|
||||
best = tmp;
|
||||
classId = double(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//If no one recognize the class, let's take the more relevant
|
||||
if (int(classId) == -1)
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Debug << "Unable to find a class in first instance\n";
|
||||
for (size_t nClassification = 0; nClassification < classification.size(); ++nClassification)
|
||||
{
|
||||
CClassifierOutput& tmp = classification[nClassification];
|
||||
if (best.second == nullptr)
|
||||
{
|
||||
best = tmp;
|
||||
classId = (double(nClassification));
|
||||
}
|
||||
else
|
||||
{
|
||||
//We take the one that is the least like the second class
|
||||
if ((*m_fAlgorithmComparison)((*best.second), *(tmp.second)) < 0)
|
||||
{
|
||||
best = tmp;
|
||||
classId = nClassification;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OV_ERROR_UNLESS_KRF(best.second != nullptr, "Unable to find a class for feature vector", Kernel::ErrorType::BadProcessing);
|
||||
|
||||
// Now that we made the calculation, we send the corresponding data
|
||||
|
||||
// For distances we just send the distance vector of the winner
|
||||
Kernel::IAlgorithmProxy* winner = this->m_subClassifiers[size_t(classId)];
|
||||
Kernel::TParameterHandler<CMatrix*> op_winnerValues(winner->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_ClassificationValues));
|
||||
CMatrix* tmpMatrix = static_cast<CMatrix*>(op_winnerValues);
|
||||
distance.setSize(tmpMatrix->getBufferElementCount());
|
||||
memcpy(distance.getBuffer(), tmpMatrix->getBuffer(), tmpMatrix->getBufferElementCount() * sizeof(double));
|
||||
|
||||
// We take the probabilities of the single class winning from each of the sub classifiers and normalize them
|
||||
double sum = 0;
|
||||
probability.setSize(m_subClassifiers.size());
|
||||
for (size_t i = 0; i < m_subClassifiers.size(); ++i)
|
||||
{
|
||||
Kernel::TParameterHandler<CMatrix*> op_Probabilities(m_subClassifiers[i]->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_ProbabilityValues));
|
||||
probability[i] = op_Probabilities->getBuffer()[0];
|
||||
sum += probability[i];
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < probability.getSize(); ++i) { probability[i] /= sum; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierOneVsAll::addNewClassifierAtBack()
|
||||
{
|
||||
const CIdentifier subClassifierAlgorithm = this->getAlgorithmManager().createAlgorithm(this->m_subClassifierAlgorithmID);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(subClassifierAlgorithm != CIdentifier::undefined(),
|
||||
"Invalid classifier identifier [" << this->m_subClassifierAlgorithmID.str() << "]", Kernel::ErrorType::BadConfig);
|
||||
|
||||
Kernel::IAlgorithmProxy* subClassifier = &this->getAlgorithmManager().getAlgorithm(subClassifierAlgorithm);
|
||||
subClassifier->initialize();
|
||||
|
||||
Kernel::TParameterHandler<uint64_t> ip_nClasses(subClassifier->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_NClasses));
|
||||
ip_nClasses = 2;
|
||||
|
||||
//Set a references to the extra parameters input of the pairing strategy
|
||||
Kernel::TParameterHandler<std::map<CString, CString>*> ip_params(subClassifier->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_ExtraParameter));
|
||||
ip_params.setReferenceTarget(this->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_ExtraParameter));
|
||||
|
||||
this->m_subClassifiers.push_back(subClassifier);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CAlgorithmClassifierOneVsAll::removeClassifierAtBack()
|
||||
{
|
||||
Kernel::IAlgorithmProxy* subClassifier = m_subClassifiers.back();
|
||||
subClassifier->uninitialize();
|
||||
this->getAlgorithmManager().releaseAlgorithm(*subClassifier);
|
||||
this->m_subClassifiers.pop_back();
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierOneVsAll::designArchitecture(const CIdentifier& id, const size_t nClass)
|
||||
{
|
||||
if (!this->setSubClassifierIdentifier(id)) { return false; }
|
||||
for (size_t i = 0; i < nClass; ++i) { if (!this->addNewClassifierAtBack()) { return false; } }
|
||||
return true;
|
||||
}
|
||||
|
||||
XML::IXMLNode* CAlgorithmClassifierOneVsAll::getClassifierConfig(Kernel::IAlgorithmProxy* classifier)
|
||||
{
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> op_config(classifier->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_Config));
|
||||
classifier->process(OVTK_Algorithm_Classifier_InputTriggerId_SaveConfig);
|
||||
XML::IXMLNode* res = op_config;
|
||||
return res;
|
||||
}
|
||||
|
||||
XML::IXMLNode* CAlgorithmClassifierOneVsAll::saveConfig()
|
||||
{
|
||||
XML::IXMLNode* oneVsAllNode = XML::createNode(TYPE_NODE_NAME);
|
||||
|
||||
XML::IXMLNode* tempNode = XML::createNode(SUB_CLASSIFIER_IDENTIFIER_NODE_NAME);
|
||||
tempNode->addAttribute(ALGORITHM_ID_ATTRIBUTE, this->m_subClassifierAlgorithmID.str().c_str());
|
||||
tempNode->setPCData(
|
||||
this->getTypeManager().getEnumerationEntryNameFromValue(OVTK_TypeId_ClassificationAlgorithm, m_subClassifierAlgorithmID.id()).toASCIIString());
|
||||
oneVsAllNode->addChild(tempNode);
|
||||
|
||||
tempNode = XML::createNode(SUB_CLASSIFIER_COUNT_NODE_NAME);
|
||||
tempNode->setPCData(std::to_string(getClassCount()).c_str());
|
||||
oneVsAllNode->addChild(tempNode);
|
||||
|
||||
XML::IXMLNode* subClassifersNode = XML::createNode(SUB_CLASSIFIERS_NODE_NAME);
|
||||
|
||||
//We now add configuration of each subclassifiers
|
||||
for (size_t i = 0; i < m_subClassifiers.size(); ++i) { subClassifersNode->addChild(getClassifierConfig(m_subClassifiers[i])); }
|
||||
oneVsAllNode->addChild(subClassifersNode);
|
||||
|
||||
return oneVsAllNode;
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierOneVsAll::loadConfig(XML::IXMLNode* configNode)
|
||||
{
|
||||
XML::IXMLNode* tempNode = configNode->getChildByName(SUB_CLASSIFIER_IDENTIFIER_NODE_NAME);
|
||||
CIdentifier id;
|
||||
id.fromString(tempNode->getAttribute(ALGORITHM_ID_ATTRIBUTE));
|
||||
if (m_subClassifierAlgorithmID != id)
|
||||
{
|
||||
while (!m_subClassifiers.empty()) { this->removeClassifierAtBack(); }
|
||||
if (!this->setSubClassifierIdentifier(id))
|
||||
{
|
||||
//if the sub classifier doesn't have comparison function it is an error
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
tempNode = configNode->getChildByName(SUB_CLASSIFIER_COUNT_NODE_NAME);
|
||||
std::stringstream countData(tempNode->getPCData());
|
||||
uint64_t nClass;
|
||||
countData >> nClass;
|
||||
|
||||
while (nClass != getClassCount())
|
||||
{
|
||||
if (nClass < getClassCount()) { this->removeClassifierAtBack(); }
|
||||
else { if (!this->addNewClassifierAtBack()) { return false; } }
|
||||
}
|
||||
|
||||
return loadSubClassifierConfig(configNode->getChildByName(SUB_CLASSIFIERS_NODE_NAME));
|
||||
}
|
||||
|
||||
size_t CAlgorithmClassifierOneVsAll::getNDistances()
|
||||
{
|
||||
Kernel::TParameterHandler<CMatrix*> op_distances(m_subClassifiers[0]->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_ClassificationValues));
|
||||
return op_distances->getDimensionSize(0);
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierOneVsAll::loadSubClassifierConfig(XML::IXMLNode* node)
|
||||
{
|
||||
for (size_t i = 0; i < node->getChildCount(); ++i)
|
||||
{
|
||||
XML::IXMLNode* subClassifierNode = node->getChild(i);
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> ip_config(m_subClassifiers[i]->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_Config));
|
||||
ip_config = subClassifierNode;
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_subClassifiers[i]->process(OVTK_Algorithm_Classifier_InputTriggerId_LoadConfig),
|
||||
"Unable to load the configuration of the classifier " << i + 1, Kernel::ErrorType::Internal);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierOneVsAll::setSubClassifierIdentifier(const CIdentifier& id)
|
||||
{
|
||||
m_subClassifierAlgorithmID = id;
|
||||
m_fAlgorithmComparison = Toolkit::getClassificationComparisonFunction(id);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_fAlgorithmComparison != nullptr,
|
||||
"No comparison function found for classifier [" << m_subClassifierAlgorithmID.str() << "]", Kernel::ErrorType::ResourceNotFound);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include <xml/IXMLNode.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
class CAlgorithmClassifierOneVsAll final : public Toolkit::CAlgorithmPairingStrategy
|
||||
{
|
||||
public:
|
||||
bool initialize() override;
|
||||
bool uninitialize() override;
|
||||
bool train(const Toolkit::IFeatureVectorSet& dataset) override;
|
||||
bool classify(const Toolkit::IFeatureVector& sample, double& classId, Toolkit::IVector& distance, Toolkit::IVector& probability) override;
|
||||
bool designArchitecture(const CIdentifier& id, const size_t nClass) override;
|
||||
XML::IXMLNode* saveConfig() override;
|
||||
bool loadConfig(XML::IXMLNode* configNode) override;
|
||||
size_t getNProbabilities() override { return m_subClassifiers.size(); }
|
||||
size_t getNDistances() override;
|
||||
|
||||
_IsDerivedFromClass_Final_(Toolkit::CAlgorithmPairingStrategy, OVP_ClassId_Algorithm_ClassifierOneVsAll)
|
||||
|
||||
|
||||
private:
|
||||
static XML::IXMLNode* getClassifierConfig(Kernel::IAlgorithmProxy* classifier);
|
||||
bool addNewClassifierAtBack();
|
||||
void removeClassifierAtBack();
|
||||
bool setSubClassifierIdentifier(const CIdentifier& id);
|
||||
size_t getClassCount() const { return m_subClassifiers.size(); }
|
||||
|
||||
bool loadSubClassifierConfig(XML::IXMLNode* node);
|
||||
|
||||
std::vector<Kernel::IAlgorithmProxy*> m_subClassifiers;
|
||||
fClassifierComparison m_fAlgorithmComparison = nullptr;
|
||||
};
|
||||
|
||||
class CAlgorithmClassifierOneVsAllDesc final : public Toolkit::CAlgorithmPairingStrategyDesc
|
||||
{
|
||||
public:
|
||||
void release() override { }
|
||||
CString getName() const override { return CString("OneVsAll pairing classifier"); }
|
||||
CString getAuthorName() const override { return CString("Guillaume Serriere"); }
|
||||
CString getAuthorCompanyName() const override { return CString("INRIA/Loria"); }
|
||||
CString getShortDescription() const override { return CString(""); }
|
||||
CString getDetailedDescription() const override { return CString(""); }
|
||||
CString getCategory() const override { return CString(""); }
|
||||
CString getVersion() const override { return CString("0.1"); }
|
||||
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
|
||||
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_Algorithm_ClassifierOneVsAll; }
|
||||
IPluginObject* create() override { return new CAlgorithmClassifierOneVsAll; }
|
||||
|
||||
bool getAlgorithmPrototype(Kernel::IAlgorithmProto& prototype) const override
|
||||
{
|
||||
CAlgorithmPairingStrategyDesc::getAlgorithmPrototype(prototype);
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(CAlgorithmPairingStrategyDesc, OVP_ClassId_Algorithm_ClassifierOneVsAllDesc)
|
||||
};
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,437 @@
|
||||
#include "ovpCAlgorithmClassifierOneVsOne.h"
|
||||
#include "ovpCAlgorithmPairwiseDecision.h"
|
||||
|
||||
#include <map>
|
||||
#include <cmath>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
#include <iostream>
|
||||
|
||||
extern const char* const CLASSIFIER_ROOT;
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
|
||||
static const char* const TYPE_NODE_NAME = "OneVsOne";
|
||||
static const char* const SUB_CLASSIFIER_IDENTIFIER_NODE_NAME = "SubClassifierIdentifier";
|
||||
static const char* const PAIRWISE_DECISION_NAME = "PairwiseDecision";
|
||||
static const char* const ALGORITHM_ID_ATTRIBUTE = "algorithm-id";
|
||||
static const char* const SUB_CLASSIFIER_COUNT_NODE_NAME = "SubClassifierCount";
|
||||
static const char* const SUB_CLASSIFIERS_NODE_NAME = "SubClassifiers";
|
||||
static const char* const SUB_CLASSIFIER_NODE_NAME = "SubClassifier";
|
||||
static const char* const FIRST_CLASS_ATRRIBUTE_NAME = "first-class";
|
||||
static const char* const SECOND_CLASS_ATTRIBUTE_NAME = "second-class";
|
||||
|
||||
//This map is used to record the decision strategies available for each algorithm
|
||||
//std::map<uint64_t, OpenViBE::CIdentifier> g_oDecisionMap;
|
||||
|
||||
bool CAlgorithmClassifierOneVsOne::initialize()
|
||||
{
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> op_configuration(this->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_Config));
|
||||
op_configuration = nullptr;
|
||||
|
||||
Kernel::TParameterHandler<uint64_t> ip_pPairwise(this->getInputParameter(OVP_Algorithm_OneVsOneStrategy_InputParameterId_DecisionType));
|
||||
ip_pPairwise = CIdentifier::undefined().id();
|
||||
|
||||
m_decisionStrategyAlgorithm = nullptr;
|
||||
m_pairwiseDecisionID = CIdentifier::undefined();
|
||||
|
||||
return CAlgorithmPairingStrategy::initialize();
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierOneVsOne::uninitialize()
|
||||
{
|
||||
if (m_decisionStrategyAlgorithm != nullptr)
|
||||
{
|
||||
m_decisionStrategyAlgorithm->uninitialize();
|
||||
this->getAlgorithmManager().releaseAlgorithm(*m_decisionStrategyAlgorithm);
|
||||
m_decisionStrategyAlgorithm = nullptr;
|
||||
}
|
||||
|
||||
for (auto& kv : m_subClassifiers)
|
||||
{
|
||||
Kernel::IAlgorithmProxy* subClassifier = kv.second;
|
||||
subClassifier->uninitialize();
|
||||
this->getAlgorithmManager().releaseAlgorithm(*subClassifier);
|
||||
}
|
||||
this->m_subClassifiers.clear();
|
||||
|
||||
return CAlgorithmPairingStrategy::uninitialize();
|
||||
}
|
||||
|
||||
|
||||
bool CAlgorithmClassifierOneVsOne::train(const Toolkit::IFeatureVectorSet& dataset)
|
||||
{
|
||||
Kernel::TParameterHandler<uint64_t> ip_nClasses(this->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_NClasses));
|
||||
m_nClasses = size_t(ip_nClasses);
|
||||
|
||||
m_nSubClassifiers = m_nClasses * (m_nClasses - 1) / 2;
|
||||
|
||||
createSubClassifiers();
|
||||
|
||||
//Create the decision strategy
|
||||
OV_ERROR_UNLESS_KRF(this->initializeExtraParameterMechanism(), "Failed to initialize extra parameters", Kernel::ErrorType::Internal);
|
||||
|
||||
m_pairwiseDecisionID = this->getEnumerationParameter(
|
||||
OVP_Algorithm_OneVsOneStrategy_InputParameterId_DecisionType, OVP_TypeId_ClassificationPairwiseStrategy);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_pairwiseDecisionID != CIdentifier::undefined(),
|
||||
"Invalid pairwise decision strategy [" << OVP_TypeId_ClassificationPairwiseStrategy.str() << "]",
|
||||
Kernel::ErrorType::BadConfig);
|
||||
|
||||
if (m_decisionStrategyAlgorithm != nullptr)
|
||||
{
|
||||
m_decisionStrategyAlgorithm->uninitialize();
|
||||
this->getAlgorithmManager().releaseAlgorithm(*m_decisionStrategyAlgorithm);
|
||||
m_decisionStrategyAlgorithm = nullptr;
|
||||
}
|
||||
m_decisionStrategyAlgorithm = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(m_pairwiseDecisionID));
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_decisionStrategyAlgorithm->initialize(), "Failed to unitialize decision strategy algorithm", Kernel::ErrorType::Internal);
|
||||
|
||||
Kernel::TParameterHandler<CIdentifier*> ip_classificationAlgorithm(
|
||||
m_decisionStrategyAlgorithm->getInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameterId_AlgorithmIdentifier));
|
||||
ip_classificationAlgorithm = &m_subClassifierAlgorithmID;
|
||||
Kernel::TParameterHandler<uint64_t> ip_classCount(m_decisionStrategyAlgorithm->getInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameter_ClassCount));
|
||||
ip_classCount = m_nClasses;
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_decisionStrategyAlgorithm->process(OVP_Algorithm_Classifier_Pairwise_InputTriggerId_Parameterize),
|
||||
"Failed to run decision strategy algorithm", Kernel::ErrorType::Internal);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(this->uninitializeExtraParameterMechanism(), "Failed to uninitialize extra parameters", Kernel::ErrorType::Internal);
|
||||
|
||||
//Calculate the amount of sample for each class
|
||||
std::map<double, size_t> classLabels;
|
||||
for (size_t i = 0; i < dataset.getFeatureVectorCount(); ++i)
|
||||
{
|
||||
if (!classLabels.count(dataset[i].getLabel())) { classLabels[dataset[i].getLabel()] = 0; }
|
||||
classLabels[dataset[i].getLabel()]++;
|
||||
}
|
||||
|
||||
OV_ERROR_UNLESS_KRF(
|
||||
classLabels.size() == m_nClasses,
|
||||
"There are samples for " << classLabels.size() << " classes but expected samples for " << m_nClasses << " classes.",
|
||||
Kernel::ErrorType::BadConfig);
|
||||
|
||||
//Now we create the corresponding repartition set
|
||||
Kernel::TParameterHandler<CMatrix*> ip_pRepartitionSet = m_decisionStrategyAlgorithm->getInputParameter(
|
||||
OVP_Algorithm_Classifier_Pairwise_InputParameterId_SetRepartition);
|
||||
ip_pRepartitionSet->resize(m_nClasses);
|
||||
|
||||
const size_t size = dataset[0].getSize();
|
||||
//Now let's train each classifier
|
||||
for (size_t i = 0; i < m_nClasses; ++i)
|
||||
{
|
||||
ip_pRepartitionSet->getBuffer()[i] = double(classLabels[double(i)]);
|
||||
|
||||
for (size_t j = i + 1; j < m_nClasses; ++j)
|
||||
{
|
||||
const size_t nFeature = classLabels[double(i)] + classLabels[double(j)];
|
||||
Kernel::IAlgorithmProxy* subClassifier = m_subClassifiers[std::pair<size_t, size_t>(i, j)];
|
||||
|
||||
Kernel::TParameterHandler<CMatrix*> ip_dataset(subClassifier->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_FeatureVectorSet));
|
||||
ip_dataset->resize(nFeature, size + 1);
|
||||
|
||||
double* buffer = ip_dataset->getBuffer();
|
||||
for (size_t k = 0; k < dataset.getFeatureVectorCount(); ++k)
|
||||
{
|
||||
const double tmp = dataset[k].getLabel();
|
||||
if (tmp == double(i) || tmp == double(j))
|
||||
{
|
||||
memcpy(buffer, dataset[k].getBuffer(), size * sizeof(double));
|
||||
|
||||
buffer[size] = size_t(tmp) == i ? 0 : 1;
|
||||
buffer += (size + 1);
|
||||
}
|
||||
}
|
||||
|
||||
OV_ERROR_UNLESS_KRF(
|
||||
subClassifier->process(OVTK_Algorithm_Classifier_InputTriggerId_Train),
|
||||
"Failed to train subclassifier [1st class = " << i << ", 2nd class = " << j << "]",
|
||||
Kernel::ErrorType::Internal);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierOneVsOne::classify(const Toolkit::IFeatureVector& sample, double& classId, Toolkit::IVector& distance, Toolkit::IVector& probability)
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(m_decisionStrategyAlgorithm, "No decision strategy algorithm set", Kernel::ErrorType::BadConfig);
|
||||
|
||||
const size_t size = sample.getSize();
|
||||
std::vector<classification_info_t> classificationList;
|
||||
|
||||
Kernel::TParameterHandler<CMatrix*> ip_proba = m_decisionStrategyAlgorithm->getInputParameter(OVP_Algorithm_Classifier_InputParameter_ProbabilityMatrix);
|
||||
CMatrix* matrix = static_cast<CMatrix*>(ip_proba);
|
||||
|
||||
matrix->resize(m_nClasses, m_nClasses);
|
||||
|
||||
for (size_t i = 0; i < matrix->getBufferElementCount(); ++i) { matrix->getBuffer()[i] = 0.0; }
|
||||
|
||||
//Let's generate the matrix of confidence score
|
||||
for (size_t i = 0; i < m_nClasses; ++i)
|
||||
{
|
||||
for (size_t j = i + 1; j < m_nClasses; ++j)
|
||||
{
|
||||
Kernel::IAlgorithmProxy* tmp = m_subClassifiers[std::pair<size_t, size_t>(i, j)];
|
||||
Kernel::TParameterHandler<CMatrix*> ip_sample(tmp->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_FeatureVector));
|
||||
Kernel::TParameterHandler<CMatrix*> op_values(tmp->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_ProbabilityValues));
|
||||
Kernel::TParameterHandler<double> op_label(tmp->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_Class));
|
||||
ip_sample->resize(size);
|
||||
|
||||
double* buffer = ip_sample->getBuffer();
|
||||
memcpy(buffer, sample.getBuffer(), size * sizeof(double));
|
||||
tmp->process(OVTK_Algorithm_Classifier_InputTriggerId_Classify);
|
||||
|
||||
classification_info_t classificationInfo = { double(i), double(j), op_label, op_values };
|
||||
classificationList.push_back(classificationInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// for (size_t i =0 ; i < nClass ; ++i )
|
||||
// {
|
||||
// for (size_t j = 0; j < nClass ; ++j) { std::cout << matrix->getBuffer()[i*nClass + j] << " " ; }
|
||||
// std::cout << std::endl;
|
||||
// }
|
||||
// std::cout << std::endl;
|
||||
|
||||
Kernel::TParameterHandler<std::vector<classification_info_t>*> ip_infos(
|
||||
m_decisionStrategyAlgorithm->getInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameter_ClassificationOutputs));
|
||||
ip_infos = &classificationList;
|
||||
|
||||
//Then ask to the strategy to make the decision
|
||||
OV_ERROR_UNLESS_KRF(m_decisionStrategyAlgorithm->process(OVP_Algorithm_Classifier_Pairwise_InputTriggerId_Compute), "Failed to compute decision strategy",
|
||||
Kernel::ErrorType::Internal);
|
||||
|
||||
Kernel::TParameterHandler<CMatrix*> op_proba = m_decisionStrategyAlgorithm->getOutputParameter(
|
||||
OVP_Algorithm_Classifier_OutputParameter_ProbabilityVector);
|
||||
double maxProb = -1;
|
||||
int selectedClassIdx = -1;
|
||||
|
||||
distance.setSize(0);
|
||||
probability.setSize(m_nClasses);
|
||||
|
||||
//We just have to take the most relevant now.
|
||||
for (size_t i = 0; i < m_nClasses; ++i)
|
||||
{
|
||||
const double tmp = op_proba->getBuffer()[i];
|
||||
if (tmp > maxProb)
|
||||
{
|
||||
selectedClassIdx = i;
|
||||
maxProb = tmp;
|
||||
}
|
||||
probability[i] = tmp;
|
||||
}
|
||||
|
||||
classId = double(selectedClassIdx);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierOneVsOne::createSubClassifiers()
|
||||
{
|
||||
// Clear any previous ones
|
||||
for (auto& kv : m_subClassifiers)
|
||||
{
|
||||
Kernel::IAlgorithmProxy* subClassifier = kv.second;
|
||||
subClassifier->uninitialize();
|
||||
this->getAlgorithmManager().releaseAlgorithm(*subClassifier);
|
||||
}
|
||||
this->m_subClassifiers.clear();
|
||||
|
||||
//Now let's instantiate all the sub classifiers
|
||||
for (size_t firstClass = 0; firstClass < m_nClasses; ++firstClass)
|
||||
{
|
||||
for (size_t secondClass = firstClass + 1; secondClass < m_nClasses; ++secondClass)
|
||||
{
|
||||
const CIdentifier subClassifierAlgorithm = this->getAlgorithmManager().createAlgorithm(this->m_subClassifierAlgorithmID);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(
|
||||
subClassifierAlgorithm != CIdentifier::undefined(),
|
||||
"Unable to instantiate classifier for class [" << this->m_subClassifierAlgorithmID.str() << "]",
|
||||
Kernel::ErrorType::BadConfig);
|
||||
|
||||
Kernel::IAlgorithmProxy* subClassifier = &this->getAlgorithmManager().getAlgorithm(subClassifierAlgorithm);
|
||||
subClassifier->initialize();
|
||||
|
||||
Kernel::TParameterHandler<uint64_t> ip_pNClasses(subClassifier->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_NClasses));
|
||||
ip_pNClasses = 2;
|
||||
|
||||
//Set a references to the extra parameters input of the pairing strategy
|
||||
Kernel::TParameterHandler<std::map<CString, CString>*> ip_params(
|
||||
subClassifier->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_ExtraParameter));
|
||||
ip_params.setReferenceTarget(this->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_ExtraParameter));
|
||||
|
||||
m_subClassifiers[std::pair<size_t, size_t>(firstClass, secondClass)] = subClassifier;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierOneVsOne::designArchitecture(const CIdentifier& id, const size_t classCount)
|
||||
{
|
||||
if (!setSubClassifierIdentifier(id)) { return false; }
|
||||
m_nClasses = classCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
XML::IXMLNode* CAlgorithmClassifierOneVsOne::getClassifierConfig(const double firstClass, const double secondClass, Kernel::IAlgorithmProxy* subClassifier)
|
||||
{
|
||||
XML::IXMLNode* res = XML::createNode(SUB_CLASSIFIER_NODE_NAME);
|
||||
|
||||
std::stringstream ssFirstClass, ssSecondClass;
|
||||
ssFirstClass << firstClass;
|
||||
ssSecondClass << secondClass;
|
||||
res->addAttribute(FIRST_CLASS_ATRRIBUTE_NAME, ssFirstClass.str().c_str());
|
||||
res->addAttribute(SECOND_CLASS_ATTRIBUTE_NAME, ssSecondClass.str().c_str());
|
||||
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> op_config(subClassifier->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_Config));
|
||||
subClassifier->process(OVTK_Algorithm_Classifier_InputTriggerId_SaveConfig);
|
||||
res->addChild(static_cast<XML::IXMLNode*>(op_config));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
XML::IXMLNode* CAlgorithmClassifierOneVsOne::getPairwiseDecisionConfiguration() const
|
||||
{
|
||||
if (!m_decisionStrategyAlgorithm) { return nullptr; }
|
||||
|
||||
XML::IXMLNode* tmp = XML::createNode(PAIRWISE_DECISION_NAME);
|
||||
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> op_config(m_decisionStrategyAlgorithm->getOutputParameter(OVP_Algorithm_Classifier_Pairwise_OutputParameterId_Config));
|
||||
m_decisionStrategyAlgorithm->process(OVP_Algorithm_Classifier_Pairwise_InputTriggerId_SaveConfig);
|
||||
tmp->addChild(static_cast<XML::IXMLNode*>(op_config));
|
||||
|
||||
tmp->addAttribute(ALGORITHM_ID_ATTRIBUTE, m_pairwiseDecisionID.str().c_str());
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
XML::IXMLNode* CAlgorithmClassifierOneVsOne::saveConfig()
|
||||
{
|
||||
std::stringstream nClassifier;
|
||||
nClassifier << m_nSubClassifiers;
|
||||
|
||||
XML::IXMLNode* oneVsOneNode = XML::createNode(TYPE_NODE_NAME);
|
||||
|
||||
XML::IXMLNode* tmp = XML::createNode(SUB_CLASSIFIER_IDENTIFIER_NODE_NAME);
|
||||
tmp->addAttribute(ALGORITHM_ID_ATTRIBUTE, this->m_subClassifierAlgorithmID.str().c_str());
|
||||
tmp->setPCData(
|
||||
this->getTypeManager().getEnumerationEntryNameFromValue(OVTK_TypeId_ClassificationAlgorithm, m_subClassifierAlgorithmID.id()).
|
||||
toASCIIString());
|
||||
oneVsOneNode->addChild(tmp);
|
||||
|
||||
tmp = XML::createNode(SUB_CLASSIFIER_COUNT_NODE_NAME);
|
||||
tmp->setPCData(nClassifier.str().c_str());
|
||||
oneVsOneNode->addChild(tmp);
|
||||
|
||||
oneVsOneNode->addChild(this->getPairwiseDecisionConfiguration());
|
||||
|
||||
XML::IXMLNode* subClassifersNode = XML::createNode(SUB_CLASSIFIERS_NODE_NAME);
|
||||
|
||||
for (auto& kv : m_subClassifiers) { subClassifersNode->addChild(getClassifierConfig(kv.first.first, kv.first.second, kv.second)); }
|
||||
oneVsOneNode->addChild(subClassifersNode);
|
||||
|
||||
return oneVsOneNode;
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierOneVsOne::loadConfig(XML::IXMLNode* configNode)
|
||||
{
|
||||
XML::IXMLNode* tempNode = configNode->getChildByName(SUB_CLASSIFIER_IDENTIFIER_NODE_NAME);
|
||||
|
||||
CIdentifier algorithmID;
|
||||
algorithmID.fromString(tempNode->getAttribute(ALGORITHM_ID_ATTRIBUTE));
|
||||
|
||||
if (!this->setSubClassifierIdentifier(algorithmID))
|
||||
{
|
||||
//if the sub classifier doesn't have comparison function it is an error
|
||||
return false;
|
||||
}
|
||||
|
||||
tempNode = configNode->getChildByName(PAIRWISE_DECISION_NAME);
|
||||
CIdentifier pairwiseID;
|
||||
pairwiseID.fromString(tempNode->getAttribute(ALGORITHM_ID_ATTRIBUTE));
|
||||
if (pairwiseID != m_pairwiseDecisionID)
|
||||
{
|
||||
if (m_decisionStrategyAlgorithm != nullptr)
|
||||
{
|
||||
m_decisionStrategyAlgorithm->uninitialize();
|
||||
this->getAlgorithmManager().releaseAlgorithm(*m_decisionStrategyAlgorithm);
|
||||
m_decisionStrategyAlgorithm = nullptr;
|
||||
}
|
||||
m_pairwiseDecisionID = pairwiseID;
|
||||
m_decisionStrategyAlgorithm = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(m_pairwiseDecisionID));
|
||||
m_decisionStrategyAlgorithm->initialize();
|
||||
}
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> ip_config(m_decisionStrategyAlgorithm->getInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameterId_Config));
|
||||
ip_config = tempNode->getChild(0);
|
||||
|
||||
Kernel::TParameterHandler<CIdentifier*> ip_algorithm(
|
||||
m_decisionStrategyAlgorithm->getInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameterId_AlgorithmIdentifier));
|
||||
ip_algorithm = &algorithmID;
|
||||
|
||||
tempNode = configNode->getChildByName(SUB_CLASSIFIER_COUNT_NODE_NAME);
|
||||
std::stringstream ss(tempNode->getPCData());
|
||||
ss >> m_nSubClassifiers;
|
||||
|
||||
// Invert the class count from subCls = numClass*(numClass-1)/2.
|
||||
const size_t deltaCarre = 1 + 8 * m_nSubClassifiers;
|
||||
m_nClasses = size_t((1 + sqrt(double(deltaCarre))) / 2);
|
||||
|
||||
Kernel::TParameterHandler<uint64_t> ip_classCount(m_decisionStrategyAlgorithm->getInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameter_ClassCount));
|
||||
ip_classCount = m_nClasses;
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_decisionStrategyAlgorithm->process(OVP_Algorithm_Classifier_Pairwise_InputTriggerId_LoadConfig),
|
||||
"Loading decision strategy configuration failed", Kernel::ErrorType::Internal);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_decisionStrategyAlgorithm->process(OVP_Algorithm_Classifier_Pairwise_InputTriggerId_Parameterize),
|
||||
"Parameterizing decision strategy failed", Kernel::ErrorType::Internal);
|
||||
|
||||
return loadSubClassifierConfig(configNode->getChildByName(SUB_CLASSIFIERS_NODE_NAME));
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierOneVsOne::loadSubClassifierConfig(XML::IXMLNode* node)
|
||||
{
|
||||
createSubClassifiers();
|
||||
|
||||
for (size_t i = 0; i < node->getChildCount(); ++i)
|
||||
{
|
||||
double firstClass, secondClass;
|
||||
|
||||
//Now we have to restore class indexes
|
||||
XML::IXMLNode* subClassifierNode = node->getChild(i);
|
||||
std::stringstream ss1(subClassifierNode->getAttribute(FIRST_CLASS_ATRRIBUTE_NAME));
|
||||
ss1 >> firstClass;
|
||||
std::stringstream ss2(subClassifierNode->getAttribute(SECOND_CLASS_ATTRIBUTE_NAME));
|
||||
ss2 >> secondClass;
|
||||
|
||||
Kernel::IAlgorithmProxy* subClassifier = m_subClassifiers[std::make_pair(size_t(firstClass), size_t(secondClass))];
|
||||
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> ip_config(subClassifier->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_Config));
|
||||
ip_config = subClassifierNode->getChild(0);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(subClassifier->process(OVTK_Algorithm_Classifier_InputTriggerId_LoadConfig),
|
||||
"Unable to load the configuration for the sub-classifier " << i + 1, Kernel::ErrorType::Internal);
|
||||
}
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_subClassifiers.size() == m_nSubClassifiers,
|
||||
"Invalid number of loaded classifiers [" << m_subClassifiers.size() << "] (expected = " << m_nSubClassifiers << ")",
|
||||
Kernel::ErrorType::Internal);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierOneVsOne::setSubClassifierIdentifier(const CIdentifier& id)
|
||||
{
|
||||
m_subClassifierAlgorithmID = id;
|
||||
m_algorithmComparison = Toolkit::getClassificationComparisonFunction(id);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_algorithmComparison != nullptr, "No comparison function found for classifier " << m_subClassifierAlgorithmID.str(),
|
||||
Kernel::ErrorType::ResourceNotFound);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,96 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include <xml/IXMLNode.h>
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
//The aim of this structure is to record informations returned by the sub-classifier. They will be used by
|
||||
// pairwise decision algorithms to compute probability vector.
|
||||
// Should be use only by OneVsOne and pairwise decision algorithm
|
||||
typedef struct
|
||||
{
|
||||
double firstClass;
|
||||
double secondClass;
|
||||
double classLabel;
|
||||
//This output is probabilist
|
||||
CMatrix* classificationValue;
|
||||
} classification_info_t;
|
||||
|
||||
|
||||
class CAlgorithmClassifierOneVsOne final : public Toolkit::CAlgorithmPairingStrategy
|
||||
{
|
||||
public:
|
||||
bool initialize() override;
|
||||
bool uninitialize() override;
|
||||
bool train(const Toolkit::IFeatureVectorSet& dataset) override;
|
||||
bool classify(const Toolkit::IFeatureVector& sample, double& classId, Toolkit::IVector& distance, Toolkit::IVector& probability) override;
|
||||
bool designArchitecture(const CIdentifier& id, const size_t classCount) override;
|
||||
XML::IXMLNode* saveConfig() override;
|
||||
bool loadConfig(XML::IXMLNode* configNode) override;
|
||||
size_t getNProbabilities() override { return m_nClasses; }
|
||||
size_t getNDistances() override { return 0; }
|
||||
|
||||
_IsDerivedFromClass_Final_(Toolkit::CAlgorithmPairingStrategy, OVP_ClassId_Algorithm_ClassifierOneVsOne)
|
||||
|
||||
protected:
|
||||
|
||||
bool createSubClassifiers();
|
||||
|
||||
private:
|
||||
size_t m_nClasses = 0;
|
||||
size_t m_nSubClassifiers = 0;
|
||||
|
||||
std::map<std::pair<size_t, size_t>, Kernel::IAlgorithmProxy*> m_subClassifiers;
|
||||
fClassifierComparison m_algorithmComparison = nullptr;
|
||||
|
||||
Kernel::IAlgorithmProxy* m_decisionStrategyAlgorithm = nullptr;
|
||||
CIdentifier m_pairwiseDecisionID = CIdentifier::undefined();
|
||||
|
||||
static XML::IXMLNode* getClassifierConfig(double firstClass, double secondClass, Kernel::IAlgorithmProxy* subClassifier);
|
||||
XML::IXMLNode* getPairwiseDecisionConfiguration() const;
|
||||
|
||||
// size_t getClassCount() const;
|
||||
|
||||
bool loadSubClassifierConfig(XML::IXMLNode* node);
|
||||
|
||||
// SSubClassifierDescriptor& getSubClassifierDescriptor(const size_t FirstClass, const size_t SecondClass);
|
||||
bool setSubClassifierIdentifier(const CIdentifier& id);
|
||||
};
|
||||
|
||||
class CAlgorithmClassifierOneVsOneDesc final : public Toolkit::CAlgorithmPairingStrategyDesc
|
||||
{
|
||||
public:
|
||||
void release() override { }
|
||||
CString getName() const override { return CString("OneVsOne pairing classifier"); }
|
||||
CString getAuthorName() const override { return CString("Guillaume Serriere"); }
|
||||
CString getAuthorCompanyName() const override { return CString("INRIA/Loria"); }
|
||||
CString getShortDescription() const override { return CString(""); }
|
||||
CString getDetailedDescription() const override { return CString(""); }
|
||||
CString getCategory() const override { return CString(""); }
|
||||
CString getVersion() const override { return CString("0.2"); }
|
||||
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
|
||||
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_Algorithm_ClassifierOneVsOne; }
|
||||
IPluginObject* create() override { return new CAlgorithmClassifierOneVsOne; }
|
||||
|
||||
bool getAlgorithmPrototype(Kernel::IAlgorithmProto& prototype) const override
|
||||
{
|
||||
CAlgorithmPairingStrategyDesc::getAlgorithmPrototype(prototype);
|
||||
prototype.addInputParameter(OVP_Algorithm_OneVsOneStrategy_InputParameterId_DecisionType, "Pairwise Decision Strategy",
|
||||
Kernel::ParameterType_Enumeration, OVP_TypeId_ClassificationPairwiseStrategy);
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(CAlgorithmPairingStrategyDesc, OVP_ClassId_Algorithm_ClassifierOneVsOneDesc)
|
||||
};
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,124 @@
|
||||
#include "ovpCAlgorithmConditionedCovariance.h"
|
||||
|
||||
/*
|
||||
* This implementation is based on the matlab code corresponding to
|
||||
*
|
||||
* Ledoit & Wolf: "A Well-Conditioned Estimator for Large-Dimensional Covariance Matrices", 2004.
|
||||
*
|
||||
*/
|
||||
#if defined TARGET_HAS_ThirdPartyEIGEN
|
||||
#include <iostream>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
|
||||
#define COV_DEBUG 0
|
||||
#if COV_DEBUG
|
||||
void CAlgorithmConditionedCovariance::dumpMatrix(Kernel::ILogManager &mgr, const MatrixXdRowMajor &mat, const CString &desc)
|
||||
{
|
||||
mgr << Kernel::LogLevel_Info << desc << "\n";
|
||||
for (int i = 0 ; i < mat.rows() ; i++)
|
||||
{
|
||||
mgr << Kernel::LogLevel_Info << "Row " << i << ": ";
|
||||
for (int j = 0 ; j < mat.cols() ; j++) { mgr << mat(i,j) << " "; }
|
||||
mgr << "\n";
|
||||
}
|
||||
}
|
||||
#else
|
||||
void CAlgorithmConditionedCovariance::dumpMatrix(Kernel::ILogManager& /* mgr */, const MatrixXdRowMajor& /*mat*/, const CString& /*desc*/) { }
|
||||
#endif
|
||||
|
||||
bool CAlgorithmConditionedCovariance::initialize()
|
||||
{
|
||||
// Default value setting
|
||||
Kernel::TParameterHandler<double> ip_shrinkage(getInputParameter(OVP_Algorithm_ConditionedCovariance_InputParameterId_Shrinkage));
|
||||
ip_shrinkage = -1.0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmConditionedCovariance::process()
|
||||
{
|
||||
// Set up the IO
|
||||
const Kernel::TParameterHandler<double> ip_shrinkage(getInputParameter(OVP_Algorithm_ConditionedCovariance_InputParameterId_Shrinkage));
|
||||
const Kernel::TParameterHandler<CMatrix*> ip_sample(getInputParameter(OVP_Algorithm_ConditionedCovariance_InputParameterId_FeatureVectorSet));
|
||||
Kernel::TParameterHandler<CMatrix*> op_mean(getOutputParameter(OVP_Algorithm_ConditionedCovariance_OutputParameterId_Mean));
|
||||
Kernel::TParameterHandler<CMatrix*> op_covMatrix(getOutputParameter(OVP_Algorithm_ConditionedCovariance_OutputParameterId_CovarianceMatrix));
|
||||
double shrinkage = ip_shrinkage;
|
||||
|
||||
OV_ERROR_UNLESS_KRF(shrinkage <= 1.0, "Invalid shrinkage value " << shrinkage << "(expected value <= 1.0)", Kernel::ErrorType::BadConfig);
|
||||
|
||||
|
||||
OV_ERROR_UNLESS_KRF(ip_sample->getDimensionCount() == 2,
|
||||
"Invalid dimension count for vector set " << ip_sample->getDimensionCount() << "(expected value = 2)", Kernel::ErrorType::BadInput);
|
||||
|
||||
const size_t nRows = ip_sample->getDimensionSize(0);
|
||||
const size_t nCols = ip_sample->getDimensionSize(1);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(nRows >= 1 && nCols >= 1, "Invalid input matrix [" << nRows << "x" << nCols << "] (expected at least 1x1 size)",
|
||||
Kernel::ErrorType::BadInput);
|
||||
|
||||
const double* buffer = ip_sample->getBuffer();
|
||||
|
||||
|
||||
OV_ERROR_UNLESS_KRF(buffer, "Invalid NULL feature set buffer", Kernel::ErrorType::BadInput);
|
||||
|
||||
// Set the output buffers so we can write the results to them without copy
|
||||
op_mean->resize(1, nCols);
|
||||
op_covMatrix->resize(nCols, nCols);
|
||||
|
||||
// Insert our data into an Eigen matrix. As Eigen doesn't have const double* constructor, we cast away the const.
|
||||
const Eigen::Map<MatrixXdRowMajor> dataMatrix(const_cast<double*>(buffer), nRows, nCols);
|
||||
|
||||
// Estimate the data center and center the data
|
||||
Eigen::Map<MatrixXdRowMajor> dataMean(op_mean->getBuffer(), 1, nCols);
|
||||
dataMean = dataMatrix.colwise().mean();
|
||||
const MatrixXdRowMajor dataCentered = dataMatrix.rowwise() - dataMean.row(0);
|
||||
|
||||
// Compute the sample cov matrix
|
||||
const Eigen::MatrixXd sampleCov = (dataCentered.transpose() * dataCentered) * (1 / double(nRows));
|
||||
|
||||
// Compute the prior cov matrix
|
||||
Eigen::MatrixXd priorCov = Eigen::MatrixXd::Zero(nCols, nCols);
|
||||
priorCov.diagonal().setConstant(sampleCov.diagonal().mean());
|
||||
|
||||
// Compute shrinkage coefficient if its not given
|
||||
if (shrinkage < 0)
|
||||
{
|
||||
const Eigen::MatrixXd dataSquared = dataCentered.cwiseProduct(dataCentered);
|
||||
const Eigen::MatrixXd phiMat = (dataSquared.transpose() * dataSquared) / double(nRows) - sampleCov.cwiseAbs2();
|
||||
|
||||
const double phi = phiMat.sum();
|
||||
const double gamma = (sampleCov - priorCov).squaredNorm(); // Frobenius norm
|
||||
const double kappa = phi / gamma;
|
||||
|
||||
shrinkage = std::max<double>(0, std::min<double>(1, kappa / double(nRows)));
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Debug << "Phi " << phi << " Gamma " << gamma << " kappa " << kappa << "\n";
|
||||
this->getLogManager() << Kernel::LogLevel_Debug << "Estimated shrinkage weight to be " << shrinkage << "\n";
|
||||
|
||||
dumpMatrix(this->getLogManager(), phiMat, "PhiMat");
|
||||
}
|
||||
else { this->getLogManager() << Kernel::LogLevel_Debug << "Using user-provided shrinkage weight " << shrinkage << "\n"; }
|
||||
|
||||
// Use the output as a buffer to avoid copying
|
||||
Eigen::Map<MatrixXdRowMajor> oCov(op_covMatrix->getBuffer(), nCols, nCols);
|
||||
|
||||
// Mix the prior and the sample estimates according to the shrinkage parameter
|
||||
oCov = shrinkage * priorCov + (1.0 - shrinkage) * sampleCov;
|
||||
|
||||
// Debug block
|
||||
dumpMatrix(this->getLogManager(), dataMean, "DataMean");
|
||||
dumpMatrix(this->getLogManager(), sampleCov, "Sample cov");
|
||||
dumpMatrix(this->getLogManager(), priorCov, "Prior cov");
|
||||
dumpMatrix(this->getLogManager(), oCov, "Output cov");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // TARGET_HAS_ThirdPartyEIGEN
|
||||
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
#if defined TARGET_HAS_ThirdPartyEIGEN
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include <Eigen/Dense>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
class CAlgorithmConditionedCovariance final : virtual public Toolkit::TAlgorithm<IAlgorithm>
|
||||
{
|
||||
typedef Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> MatrixXdRowMajor;
|
||||
|
||||
public:
|
||||
void release() override { delete this; }
|
||||
bool initialize() override;
|
||||
bool uninitialize() override { return true; }
|
||||
bool process() override;
|
||||
|
||||
_IsDerivedFromClass_Final_(Toolkit::TAlgorithm<IAlgorithm>, OVP_ClassId_Algorithm_ConditionedCovariance)
|
||||
|
||||
protected:
|
||||
// Debug method. Prints the matrix to the logManager. May be disabled in implementation.
|
||||
static void dumpMatrix(Kernel::ILogManager& mgr, const MatrixXdRowMajor& mat, const CString& desc);
|
||||
};
|
||||
|
||||
class CAlgorithmConditionedCovarianceDesc final : virtual public IAlgorithmDesc
|
||||
{
|
||||
public:
|
||||
void release() override { }
|
||||
CString getName() const override { return CString("Conditioned Covariance"); }
|
||||
CString getAuthorName() const override { return CString("Jussi T. Lindgren"); }
|
||||
CString getAuthorCompanyName() const override { return CString("Inria"); }
|
||||
CString getShortDescription() const override { return CString("Computes covariance with shrinkage."); }
|
||||
|
||||
CString getDetailedDescription() const override
|
||||
{
|
||||
return CString(
|
||||
"Shrinkage: {<0 = auto-estimate, [0,1] balance between prior and sample cov}. The conditioned covariance matrix may allow better accuracies with models that rely on inverting the cov matrix, in cases where the regular cov matrix is close to singular.");
|
||||
}
|
||||
|
||||
CString getCategory() const override { return CString(""); }
|
||||
CString getVersion() const override { return CString("1.0"); }
|
||||
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
|
||||
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_Algorithm_ConditionedCovariance; }
|
||||
IPluginObject* create() override { return new CAlgorithmConditionedCovariance; }
|
||||
|
||||
bool getAlgorithmPrototype(Kernel::IAlgorithmProto& prototype) const override
|
||||
{
|
||||
prototype.addInputParameter(OVP_Algorithm_ConditionedCovariance_InputParameterId_Shrinkage, "Shrinkage (-1 == auto)", Kernel::ParameterType_Float);
|
||||
prototype.addInputParameter(OVP_Algorithm_ConditionedCovariance_InputParameterId_FeatureVectorSet, "Feature vectors", Kernel::ParameterType_Matrix);
|
||||
|
||||
// The algorithm returns these outputs
|
||||
prototype.addOutputParameter(OVP_Algorithm_ConditionedCovariance_OutputParameterId_Mean, "Mean vector", Kernel::ParameterType_Matrix);
|
||||
prototype.addOutputParameter(OVP_Algorithm_ConditionedCovariance_OutputParameterId_CovarianceMatrix, "Covariance matrix", Kernel::ParameterType_Matrix);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(IAlgorithmDesc, OVP_ClassId_Algorithm_ConditionedCovarianceDesc)
|
||||
};
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
|
||||
|
||||
#endif // TARGET_HAS_ThirdPartyEIGEN
|
||||
@@ -0,0 +1,61 @@
|
||||
#include "ovpCAlgorithmLDADiscriminantFunction.h"
|
||||
#if defined TARGET_HAS_ThirdPartyEIGEN
|
||||
|
||||
#include <Eigen/Eigenvalues>
|
||||
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
|
||||
static const char* const BASE_NODE_NAME = "Class-config";
|
||||
static const char* const WEIGHT_NODE_NAME = "Weights";
|
||||
static const char* const BIAS_NODE_NAME = "Bias";
|
||||
|
||||
bool CAlgorithmLDADiscriminantFunction::loadConfig(const XML::IXMLNode* configuration)
|
||||
{
|
||||
std::stringstream bias(configuration->getChildByName(BIAS_NODE_NAME)->getPCData());
|
||||
bias >> m_bias;
|
||||
|
||||
std::stringstream data(configuration->getChildByName(WEIGHT_NODE_NAME)->getPCData());
|
||||
std::vector<double> coefficients;
|
||||
while (!data.eof())
|
||||
{
|
||||
double value;
|
||||
data >> value;
|
||||
coefficients.push_back(value);
|
||||
}
|
||||
|
||||
m_weight.resize(coefficients.size());
|
||||
for (size_t i = 0; i < coefficients.size(); ++i) { m_weight(i, 0) = coefficients[i]; }
|
||||
return true;
|
||||
}
|
||||
|
||||
XML::IXMLNode* CAlgorithmLDADiscriminantFunction::getConfiguration()
|
||||
{
|
||||
XML::IXMLNode* rootNode = XML::createNode(BASE_NODE_NAME);
|
||||
|
||||
std::stringstream weigths, bias;
|
||||
|
||||
weigths << std::scientific;
|
||||
for (int i = 0; i < m_weight.size(); ++i) { weigths << " " << m_weight(i, 0); }
|
||||
|
||||
bias << m_bias;
|
||||
|
||||
XML::IXMLNode* tempNode = XML::createNode(WEIGHT_NODE_NAME);
|
||||
tempNode->setPCData(weigths.str().c_str());
|
||||
rootNode->addChild(tempNode);
|
||||
|
||||
tempNode = XML::createNode(BIAS_NODE_NAME);
|
||||
tempNode->setPCData(bias.str().c_str());
|
||||
rootNode->addChild(tempNode);
|
||||
|
||||
return rootNode;
|
||||
}
|
||||
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
#endif
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#if defined TARGET_HAS_ThirdPartyEIGEN
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
|
||||
#include <Eigen/Eigenvalues>
|
||||
|
||||
#include "ovpCAlgorithmClassifierLDA.h"
|
||||
#include <xml/IXMLNode.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
//The purpose of this class is to compute the "membership" of a vector
|
||||
class CAlgorithmLDADiscriminantFunction
|
||||
{
|
||||
public:
|
||||
CAlgorithmLDADiscriminantFunction() {}
|
||||
|
||||
void setWeight(const Eigen::VectorXd& weigth) { m_weight = weigth; }
|
||||
void setBias(const double bias) { m_bias = bias; }
|
||||
|
||||
//Return the class membership of the feature vector
|
||||
double getValue(const Eigen::VectorXd& featureVector) { return (m_weight.transpose() * featureVector)(0) + m_bias; }
|
||||
size_t getNWeight() const { return m_weight.size(); }
|
||||
|
||||
|
||||
bool loadConfig(const XML::IXMLNode* configuration);
|
||||
XML::IXMLNode* getConfiguration();
|
||||
|
||||
const Eigen::VectorXd& getWeight() const { return m_weight; }
|
||||
double getBias() const { return m_bias; }
|
||||
|
||||
private:
|
||||
double m_bias = 0;
|
||||
Eigen::VectorXd m_weight;
|
||||
};
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,45 @@
|
||||
#include "ovpCAlgorithmPairwiseDecision.h"
|
||||
|
||||
#include <iostream>
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
|
||||
|
||||
bool CAlgorithmPairwiseDecision::process()
|
||||
{
|
||||
// @note there is essentially no test that these are called in correct order. Caller be careful!
|
||||
if (this->isInputTriggerActive(OVP_Algorithm_Classifier_Pairwise_InputTriggerId_Compute))
|
||||
{
|
||||
Kernel::TParameterHandler<std::vector<classification_info_t>*> ip_classifications = this->getInputParameter(
|
||||
OVP_Algorithm_Classifier_Pairwise_InputParameter_ClassificationOutputs);
|
||||
Kernel::TParameterHandler<CMatrix*> op_probability = this->getOutputParameter(OVP_Algorithm_Classifier_OutputParameter_ProbabilityVector);
|
||||
return this->compute(*static_cast<std::vector<classification_info_t>*>(ip_classifications), static_cast<CMatrix*>(op_probability));
|
||||
}
|
||||
if (this->isInputTriggerActive(OVP_Algorithm_Classifier_Pairwise_InputTriggerId_SaveConfig))
|
||||
{
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> op_configuration(this->getOutputParameter(OVP_Algorithm_Classifier_Pairwise_OutputParameterId_Config));
|
||||
XML::IXMLNode* tmp = this->saveConfig();
|
||||
|
||||
OV_ERROR_UNLESS_KRF(tmp != nullptr, "Invalid NULL xml node returned while saving configuration", Kernel::ErrorType::Internal);
|
||||
|
||||
op_configuration = tmp;
|
||||
return true;
|
||||
}
|
||||
if (this->isInputTriggerActive(OVP_Algorithm_Classifier_Pairwise_InputTriggerId_LoadConfig))
|
||||
{
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> op_config(this->getInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameterId_Config));
|
||||
XML::IXMLNode* tmp = static_cast<XML::IXMLNode*>(op_config);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(tmp != nullptr, "Invalid NULL xml node to load configuration in", Kernel::ErrorType::BadInput);
|
||||
|
||||
return this->loadConfig(*tmp);
|
||||
}
|
||||
if (this->isInputTriggerActive(OVP_Algorithm_Classifier_Pairwise_InputTriggerId_Parameterize)) { return this->parameterize(); }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include <xml/IXMLNode.h>
|
||||
#include "ovpCAlgorithmClassifierOneVsOne.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
/**
|
||||
* @brief The CAlgorithmPairwiseDecision class
|
||||
* This is the default class for every decision usable with the One Vs One pairwise strategy.
|
||||
*/
|
||||
class CAlgorithmPairwiseDecision : virtual public Toolkit::TAlgorithm<IAlgorithm>
|
||||
{
|
||||
public:
|
||||
void release() override { delete this; }
|
||||
bool initialize() override = 0;
|
||||
bool uninitialize() override = 0;
|
||||
|
||||
virtual bool parameterize() = 0;
|
||||
|
||||
virtual bool compute(std::vector<classification_info_t>& classifications, CMatrix* probabilities) = 0;
|
||||
virtual XML::IXMLNode* saveConfig() = 0;
|
||||
virtual bool loadConfig(XML::IXMLNode& node) = 0;
|
||||
bool process() override;
|
||||
|
||||
_IsDerivedFromClass_Final_(Toolkit::TAlgorithm<IAlgorithm>, OVP_ClassId_Algorithm_PairwiseDecision)
|
||||
};
|
||||
|
||||
class CAlgorithmPairwiseDecisionDesc : virtual public IAlgorithmDesc
|
||||
{
|
||||
public:
|
||||
bool getAlgorithmPrototype(Kernel::IAlgorithmProto& prototype) const override
|
||||
{
|
||||
prototype.addInputParameter(OVP_Algorithm_Classifier_InputParameter_ProbabilityMatrix, "Probability Matrix", Kernel::ParameterType_Matrix);
|
||||
prototype.addInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameterId_Config, "Configuration node", Kernel::ParameterType_Pointer);
|
||||
prototype.addInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameterId_SetRepartition, "Set repartition", Kernel::ParameterType_Matrix);
|
||||
prototype.addInputParameter(
|
||||
OVP_Algorithm_Classifier_Pairwise_InputParameterId_AlgorithmIdentifier, "Classification Algorithm", Kernel::ParameterType_Identifier);
|
||||
prototype.addInputParameter(
|
||||
OVP_Algorithm_Classifier_Pairwise_InputParameter_ClassificationOutputs, "Classification Outputs", Kernel::ParameterType_Pointer);
|
||||
prototype.addInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameter_ClassCount, "Class Count", Kernel::ParameterType_UInteger);
|
||||
|
||||
prototype.addOutputParameter(OVP_Algorithm_Classifier_OutputParameter_ProbabilityVector, "Probability Vector", Kernel::ParameterType_Matrix);
|
||||
prototype.addOutputParameter(OVP_Algorithm_Classifier_Pairwise_OutputParameterId_Config, "Configuration node", Kernel::ParameterType_Pointer);
|
||||
|
||||
prototype.addInputTrigger(OVP_Algorithm_Classifier_Pairwise_InputTriggerId_Compute, "Compute");
|
||||
prototype.addInputTrigger(OVP_Algorithm_Classifier_Pairwise_InputTriggerId_Parameterize, "Parametrize");
|
||||
prototype.addInputTrigger(OVP_Algorithm_Classifier_Pairwise_InputTriggerId_SaveConfig, "Save configuration");
|
||||
prototype.addInputTrigger(OVP_Algorithm_Classifier_Pairwise_InputTriggerId_LoadConfig, "Load configuration");
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(IAlgorithmDesc, OVP_ClassId_Algorithm_PairwiseDecisionDesc)
|
||||
};
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,188 @@
|
||||
#define HT_DEBUG 0
|
||||
|
||||
#define ALPHA_DELTA 0.01
|
||||
#include "ovpCAlgorithmPairwiseDecisionHT.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#include <xml/IXMLNode.h>
|
||||
#include <xml/IXMLHandler.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
|
||||
static const char* const TYPE_NODE_NAME = "PairwiseDecision_HT";
|
||||
static const char* const REPARTITION_NODE_NAME = "Repartition";
|
||||
|
||||
bool CAlgorithmPairwiseDecisionHT::parameterize()
|
||||
{
|
||||
Kernel::TParameterHandler<uint64_t> ip_nClass(this->getInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameter_ClassCount));
|
||||
m_nClass = size_t(ip_nClass);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_nClass >= 2, "Pairwise decision HT algorithm needs at least 2 classes [" << m_nClass << "] found", Kernel::ErrorType::BadInput);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool CAlgorithmPairwiseDecisionHT::compute(std::vector<classification_info_t>& classifications, CMatrix* probabilities)
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(m_nClass >= 2, "Pairwise decision HT algorithm needs at least 2 classes [" << m_nClass << "] found", Kernel::ErrorType::BadConfig);
|
||||
|
||||
Kernel::TParameterHandler<CMatrix*> ip_Repartition = this->getInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameterId_SetRepartition);
|
||||
std::vector<double> probability(m_nClass * m_nClass);
|
||||
|
||||
//First we set the diagonal to 0
|
||||
for (size_t i = 0; i < m_nClass; ++i) { probability[i * m_nClass + i] = 0.; }
|
||||
|
||||
#if HT_DEBUG
|
||||
for (size_t i = 0 ; i< m_nClass ; ++i){
|
||||
|
||||
for (size_t j = 0 ; j<m_nClass ; ++j){
|
||||
std::cout << probability[i*m_nClass + j] << " ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
for (size_t i = 0; i < classifications.size(); ++i)
|
||||
{
|
||||
classification_info_t& temp = classifications[i];
|
||||
const size_t firstIdx = size_t(temp.firstClass);
|
||||
const size_t secondIdx = size_t(temp.secondClass);
|
||||
const double* values = temp.classificationValue->getBuffer();
|
||||
probability[firstIdx * m_nClass + secondIdx] = values[0];
|
||||
probability[secondIdx * m_nClass + firstIdx] = 1 - values[0];
|
||||
}
|
||||
|
||||
std::vector<double> p(m_nClass);
|
||||
std::vector<std::vector<double>> mu(m_nClass);
|
||||
size_t amountSample = 0;
|
||||
|
||||
for (size_t i = 0; i < m_nClass; ++i) { mu[i].resize(m_nClass); }
|
||||
for (size_t i = 0; i < m_nClass; ++i) { amountSample += size_t(ip_Repartition->getBuffer()[i]); }
|
||||
for (size_t i = 0; i < m_nClass; ++i) { p[i] = ip_Repartition->getBuffer()[i] / amountSample; }
|
||||
|
||||
for (size_t i = 0; i < m_nClass; ++i)
|
||||
{
|
||||
for (size_t j = 0; j < m_nClass; ++j)
|
||||
{
|
||||
if (i != j) { mu[i][j] = p[i] / (p[i] + p[j]); }
|
||||
else { mu[i][i] = 0; }
|
||||
}
|
||||
}
|
||||
|
||||
#if HT_DEBUG
|
||||
std::cout << "Initial probability and Mu" << std::endl;
|
||||
for (size_t i = 0 ; i < m_nClass ; ++i) { std::cout << p[i] << " "; }
|
||||
std::cout << std::endl << std::endl;
|
||||
|
||||
for (size_t i = 0 ; i< m_nClass ; ++i)
|
||||
{
|
||||
for (size_t j = 0 ; j<m_nClass ; ++j) { std::cout << mu[i][j] << " "; }
|
||||
std::cout << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
#endif
|
||||
|
||||
|
||||
size_t consecutiveAlpha = 0;
|
||||
size_t index = 0;
|
||||
while (consecutiveAlpha != m_nClass)
|
||||
{
|
||||
double firstSum = 0.0;
|
||||
double secondSum = 0.0;
|
||||
|
||||
for (size_t j = 0; j < m_nClass; ++j)
|
||||
{
|
||||
if (j != index)
|
||||
{
|
||||
const size_t temp = size_t(probability[index] + ip_Repartition->getBuffer()[j]);
|
||||
|
||||
firstSum += temp * probability[index * m_nClass + j];
|
||||
secondSum += temp * mu[index][j];
|
||||
}
|
||||
}
|
||||
|
||||
const double alpha = (secondSum != 0) ? firstSum / secondSum : 1;
|
||||
|
||||
for (size_t j = 0; j < m_nClass; ++j)
|
||||
{
|
||||
if (j != index)
|
||||
{
|
||||
mu[index][j] = (alpha * mu[index][j]) / (alpha * mu[index][j] + mu[j][index]);
|
||||
mu[j][index] = 1 - mu[index][j];
|
||||
}
|
||||
}
|
||||
|
||||
p[index] *= alpha;
|
||||
if (alpha > 1 - ALPHA_DELTA && alpha < 1 + ALPHA_DELTA) { ++consecutiveAlpha; }
|
||||
else { consecutiveAlpha = 0; }
|
||||
index = (index + 1) % m_nClass;
|
||||
|
||||
#if HT_DEBUG
|
||||
std::cout << "Intermediate probability, MU and alpha" << std::endl << alpha << std::endl;
|
||||
for (size_t i = 0 ; i< m_nClass ; ++i) { std::cout << p[i] << " "; }
|
||||
std::cout << std::endl << std::endl;
|
||||
|
||||
for (size_t i = 0 ; i< m_nClass ; ++i)
|
||||
{
|
||||
for (size_t j = 0 ; j<m_nClass ; ++j) { std::cout << mu[i][j] << " "; }
|
||||
std::cout << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#if HT_DEBUG
|
||||
std::cout << "Result " << std::endl;
|
||||
for (size_t i = 0; i<m_nClass ; ++i) { std::cout << p[i] << " "; }
|
||||
std::cout << std::endl << std::endl;
|
||||
#endif
|
||||
|
||||
probabilities->resize(m_nClass);
|
||||
for (size_t i = 0; i < m_nClass; ++i) { probabilities->getBuffer()[i] = p[i]; }
|
||||
return true;
|
||||
}
|
||||
|
||||
XML::IXMLNode* CAlgorithmPairwiseDecisionHT::saveConfig()
|
||||
{
|
||||
XML::IXMLNode* node = XML::createNode(TYPE_NODE_NAME);
|
||||
|
||||
Kernel::TParameterHandler<CMatrix*> ip_repartition = this->getInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameterId_SetRepartition);
|
||||
const size_t nClass = ip_repartition->getDimensionSize(0);
|
||||
|
||||
std::stringstream ss;
|
||||
for (size_t i = 0; i < nClass; ++i) { ss << ip_repartition->getBuffer()[i] << " "; }
|
||||
XML::IXMLNode* repartition = XML::createNode(REPARTITION_NODE_NAME);
|
||||
repartition->setPCData(ss.str().c_str());
|
||||
node->addChild(repartition);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
bool CAlgorithmPairwiseDecisionHT::loadConfig(XML::IXMLNode& node)
|
||||
{
|
||||
std::stringstream ss(node.getChildByName(REPARTITION_NODE_NAME)->getPCData());
|
||||
Kernel::TParameterHandler<CMatrix*> ip_repartition = this->getInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameterId_SetRepartition);
|
||||
|
||||
|
||||
std::vector<double> repartition;
|
||||
while (!ss.eof())
|
||||
{
|
||||
size_t value;
|
||||
ss >> value;
|
||||
repartition.push_back(value);
|
||||
}
|
||||
|
||||
ip_repartition->resize(repartition.size());
|
||||
for (size_t i = 0; i < repartition.size(); ++i) { ip_repartition->getBuffer()[i] = repartition[i]; }
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include "ovpCAlgorithmPairwiseDecision.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
/**
|
||||
* @brief The CAlgorithmPairwiseDecisionHT class is a decision strategy for the One Vs One pairwise decision that implement the
|
||||
* method describe in the article Hastie, Trevor; Tibshirani, Robert. Classification by pairwise coupling. The Annals of Statistics 26 (1998), no. 2, 451--471
|
||||
*
|
||||
* Probability required
|
||||
*/
|
||||
class CAlgorithmPairwiseDecisionHT final : virtual public CAlgorithmPairwiseDecision
|
||||
{
|
||||
public:
|
||||
|
||||
CAlgorithmPairwiseDecisionHT() { }
|
||||
void release() override { delete this; }
|
||||
bool initialize() override { return true; }
|
||||
bool uninitialize() override { return true; }
|
||||
bool parameterize() override;
|
||||
bool compute(std::vector<classification_info_t>& classifications, CMatrix* probabilities) override;
|
||||
XML::IXMLNode* saveConfig() override;
|
||||
bool loadConfig(XML::IXMLNode& node) override;
|
||||
|
||||
_IsDerivedFromClass_Final_(CAlgorithmPairwiseDecision, OVP_ClassId_Algorithm_PairwiseDecision_HT)
|
||||
|
||||
private:
|
||||
size_t m_nClass = 0;
|
||||
};
|
||||
|
||||
class CAlgorithmPairwiseDecisionHTDesc final : virtual public CAlgorithmPairwiseDecisionDesc
|
||||
{
|
||||
public:
|
||||
void release() override { }
|
||||
CString getName() const override { return CString("Pairwise decision strategy based on HT"); }
|
||||
CString getAuthorName() const override { return CString("Serrière Guillaume"); }
|
||||
CString getAuthorCompanyName() const override { return CString("Inria"); }
|
||||
CString getShortDescription() const override { return CString("."); }
|
||||
|
||||
CString getDetailedDescription() const override
|
||||
{
|
||||
return CString("This method is based on the method describe in the article "
|
||||
"Hastie, Trevor; Tibshirani, Robert. Classification by pairwise coupling."
|
||||
"The Annals of Statistics 26 (1998), no. 2, 451--471");
|
||||
}
|
||||
|
||||
CString getCategory() const override { return CString(""); }
|
||||
CString getVersion() const override { return CString("0.1"); }
|
||||
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
|
||||
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_Algorithm_PairwiseDecision_HT; }
|
||||
IPluginObject* create() override { return new CAlgorithmPairwiseDecisionHT; }
|
||||
|
||||
bool getAlgorithmPrototype(Kernel::IAlgorithmProto& prototype) const override
|
||||
{
|
||||
CAlgorithmPairwiseDecisionDesc::getAlgorithmPrototype(prototype);
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(CAlgorithmPairwiseDecisionDesc, OVP_ClassId_Algorithm_PairwiseDecision_HTDesc)
|
||||
};
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,68 @@
|
||||
#define VOTING_DEBUG 0
|
||||
#include "ovpCAlgorithmPairwiseDecisionVoting.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <xml/IXMLNode.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
|
||||
static const char* const TYPE_NODE_NAME = "PairwiseDecision_Voting";
|
||||
|
||||
bool CAlgorithmPairwiseDecisionVoting::parameterize()
|
||||
{
|
||||
Kernel::TParameterHandler<uint64_t> ip_nClass(this->getInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameter_ClassCount));
|
||||
m_nClass = size_t(ip_nClass);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_nClass >= 2, "Pairwise decision Voting algorithm needs at least 2 classes [" << m_nClass << "] found", Kernel::ErrorType::BadInput);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmPairwiseDecisionVoting::compute(std::vector<classification_info_t>& classifications, CMatrix* probabilities)
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(m_nClass >= 2, "Pairwise decision Voting algorithm needs at least 2 classes [" << m_nClass << "] found", Kernel::ErrorType::BadInput);
|
||||
|
||||
#if VOTING_DEBUG
|
||||
std::cout << classifications.size() << std::endl;
|
||||
|
||||
for (size_t i = 0 ; i < classifications.size() ; ++i)
|
||||
{
|
||||
std::cout << classifications[i].firstClass << " " << classifications[i].secondClass << std::endl;
|
||||
std::cout << classifications[i].classLabel << std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::vector<size_t> win(m_nClass);
|
||||
for (size_t i = 0; i < m_nClass; ++i) { win[i] = 0; }
|
||||
|
||||
for (size_t i = 0; i < classifications.size(); ++i)
|
||||
{
|
||||
classification_info_t& temp = classifications[i];
|
||||
if (temp.classLabel == 0) { ++(win[size_t(temp.firstClass)]); }
|
||||
else { ++(win[size_t(temp.secondClass)]); }
|
||||
}
|
||||
|
||||
#if VOTING_DEBUG
|
||||
for (size_t i = 0; i < m_nClass ; ++i) { std::cout << (double(win[i])/ classifications.size() << " "; }
|
||||
std::cout << std::endl;
|
||||
#endif
|
||||
|
||||
probabilities->resize(m_nClass);
|
||||
|
||||
for (size_t i = 0; i < m_nClass; ++i) { probabilities->getBuffer()[i] = double(win[i]) / classifications.size(); }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
XML::IXMLNode* CAlgorithmPairwiseDecisionVoting::saveConfig()
|
||||
{
|
||||
XML::IXMLNode* node = XML::createNode(TYPE_NODE_NAME);
|
||||
return node;
|
||||
}
|
||||
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include "ovpCAlgorithmPairwiseDecision.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
/**
|
||||
* @brief The CAlgorithmPairwiseDecisionVoting class
|
||||
* This strategy relies on a basic voting system. If class A beats class B, class A win 1 point and B 0 point. At the end, the vector of
|
||||
* probability is composed by the normalized score of each class.
|
||||
*
|
||||
* Probability required.
|
||||
*/
|
||||
class CAlgorithmPairwiseDecisionVoting final : virtual public CAlgorithmPairwiseDecision
|
||||
{
|
||||
public:
|
||||
|
||||
CAlgorithmPairwiseDecisionVoting() { }
|
||||
void release() override { delete this; }
|
||||
bool initialize() override { return true; }
|
||||
bool uninitialize() override { return true; }
|
||||
bool parameterize() override;
|
||||
bool compute(std::vector<classification_info_t>& classifications, CMatrix* probabilities) override;
|
||||
XML::IXMLNode* saveConfig() override;
|
||||
bool loadConfig(XML::IXMLNode& /*node*/) override { return true; }
|
||||
|
||||
_IsDerivedFromClass_Final_(CAlgorithmPairwiseDecision, OVP_ClassId_Algorithm_PairwiseDecision_Voting)
|
||||
|
||||
private:
|
||||
size_t m_nClass = 0;
|
||||
};
|
||||
|
||||
class CAlgorithmPairwiseDecisionVotingDesc final : virtual public CAlgorithmPairwiseDecisionDesc
|
||||
{
|
||||
public:
|
||||
void release() override { }
|
||||
CString getName() const override { return CString("Pairwise decision strategy based on Voting"); }
|
||||
CString getAuthorName() const override { return CString("Serrière Guillaume"); }
|
||||
CString getAuthorCompanyName() const override { return CString("Inria"); }
|
||||
CString getShortDescription() const override { return CString("."); }
|
||||
CString getDetailedDescription() const override { return CString(""); }
|
||||
CString getCategory() const override { return CString(""); }
|
||||
CString getVersion() const override { return CString("0.1"); }
|
||||
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
|
||||
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_Algorithm_PairwiseDecision_Voting; }
|
||||
IPluginObject* create() override { return new CAlgorithmPairwiseDecisionVoting; }
|
||||
|
||||
bool getAlgorithmPrototype(Kernel::IAlgorithmProto& prototype) const override
|
||||
{
|
||||
CAlgorithmPairwiseDecisionDesc::getAlgorithmPrototype(prototype);
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(CAlgorithmPairwiseDecisionDesc, OVP_ClassId_Algorithm_PairwiseDecision_VotingDesc)
|
||||
};
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,85 @@
|
||||
#define PKPD_DEBUG 0
|
||||
#include "ovpCAlgorithmPairwiseStrategyPKPD.h"
|
||||
#include <xml/IXMLNode.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
|
||||
static const char* const TYPE_NODE_NAME = "PairwiseDecision_PKDP";
|
||||
|
||||
bool CAlgorithmPairwiseStrategyPKPD::parameterize()
|
||||
{
|
||||
Kernel::TParameterHandler<uint64_t> ip_nClass(this->getInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameter_ClassCount));
|
||||
m_nClass = size_t(ip_nClass);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_nClass >= 2, "Pairwise decision PKPD algorithm needs at least 2 classes [" << m_nClass << "] found", Kernel::ErrorType::BadInput);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmPairwiseStrategyPKPD::compute(std::vector<classification_info_t>& classifications, CMatrix* probabilities)
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(m_nClass >= 2, "Pairwise decision PKPD algorithm needs at least 2 classes [" << m_nClass << "] found", Kernel::ErrorType::BadInput);
|
||||
|
||||
std::vector<double> matrix(m_nClass * m_nClass);
|
||||
|
||||
//First we set the diagonal to 0
|
||||
for (size_t i = 0; i < m_nClass; ++i) { matrix[i * m_nClass + i] = 0.; }
|
||||
|
||||
for (size_t i = 0; i < classifications.size(); ++i)
|
||||
{
|
||||
classification_info_t& temp = classifications[i];
|
||||
const size_t firstIdx = size_t(temp.firstClass);
|
||||
const size_t secondIdx = size_t(temp.secondClass);
|
||||
const double* values = temp.classificationValue->getBuffer();
|
||||
matrix[firstIdx * m_nClass + secondIdx] = values[0];
|
||||
matrix[secondIdx * m_nClass + firstIdx] = 1 - values[0];
|
||||
}
|
||||
|
||||
#if PKPD_DEBUG
|
||||
for (size_t i = 0 ; i < m_nClass ; ++i)
|
||||
{
|
||||
for (size_t j = 0 ; j < m_nClass ; ++j) { std::cout << matrix[i * m_nClass + j] << " "; }
|
||||
std::cout << std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::vector<double> probVector(m_nClass);
|
||||
double sum = 0;
|
||||
for (size_t classIdx = 0; classIdx < m_nClass; ++classIdx)
|
||||
{
|
||||
double tmpSum = 0;
|
||||
for (size_t secondClass = 0; secondClass < m_nClass; ++secondClass)
|
||||
{
|
||||
if (secondClass != classIdx) { tmpSum += 1 / matrix[m_nClass * classIdx + secondClass]; }
|
||||
}
|
||||
probVector[classIdx] = 1 / (tmpSum - (m_nClass - 2));
|
||||
sum += probVector[classIdx];
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < m_nClass; ++i) { probVector[i] /= sum; }
|
||||
|
||||
#if PKPD_DEBUG
|
||||
for (size_t i = 0; i < m_nClass ; ++i) { std::cout << probVector[i] << " "; }
|
||||
std::cout << std::endl;
|
||||
#endif
|
||||
|
||||
probabilities->resize(m_nClass);
|
||||
|
||||
for (size_t i = 0; i < m_nClass; ++i) { probabilities->getBuffer()[i] = probVector[i]; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
XML::IXMLNode* CAlgorithmPairwiseStrategyPKPD::saveConfig()
|
||||
{
|
||||
XML::IXMLNode* node = XML::createNode(TYPE_NODE_NAME);
|
||||
return node;
|
||||
}
|
||||
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include "ovpCAlgorithmPairwiseDecision.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
/**
|
||||
* @brief The CAlgorithmPairwiseStrategyPKPD class
|
||||
* This strategy relies on the algorithm describe in the article . Price, S. Knerr, L. Personnaz, and G. Dreyfus.
|
||||
* Pairwise neural network classifiers with probabilistic outputs. In G. Tesauro, D. Touretzky, and T. Leen (eds.)
|
||||
* Advances in Neural Information Processing Systems 7 (NIPS-94), pp. 1109-1116. MIT Press, 1995.
|
||||
*/
|
||||
class CAlgorithmPairwiseStrategyPKPD final : virtual public CAlgorithmPairwiseDecision
|
||||
{
|
||||
public:
|
||||
|
||||
CAlgorithmPairwiseStrategyPKPD() { }
|
||||
void release() override { delete this; }
|
||||
bool initialize() override { return true; }
|
||||
bool uninitialize() override { return true; }
|
||||
bool parameterize() override;
|
||||
bool compute(std::vector<classification_info_t>& classifications, CMatrix* probabilities) override;
|
||||
XML::IXMLNode* saveConfig() override;
|
||||
bool loadConfig(XML::IXMLNode& /*node*/) override { return true; }
|
||||
|
||||
_IsDerivedFromClass_Final_(CAlgorithmPairwiseDecision, OVP_ClassId_Algorithm_PairwiseStrategy_PKPD)
|
||||
|
||||
private:
|
||||
size_t m_nClass = 0;
|
||||
};
|
||||
|
||||
class CAlgorithmPairwiseStrategyPKPDDesc final : virtual public CAlgorithmPairwiseDecisionDesc
|
||||
{
|
||||
public:
|
||||
void release() override { }
|
||||
CString getName() const override { return CString("Pairwise decision strategy based on PKPD"); }
|
||||
CString getAuthorName() const override { return CString("Serrière Guillaume"); }
|
||||
CString getAuthorCompanyName() const override { return CString("Inria"); }
|
||||
CString getShortDescription() const override { return CString("."); }
|
||||
|
||||
CString getDetailedDescription() const override
|
||||
{
|
||||
return CString("Price, S. Knerr, L. Personnaz, and G. Dreyfus."
|
||||
"Pairwise neural network classifiers with probabilistic outputs."
|
||||
" In G. Tesauro, D. Touretzky, and T. Leen (eds.)"
|
||||
"Advances in Neural Information Processing Systems 7 (NIPS-94), pp."
|
||||
" 1109-1116. MIT Press, 1995.");
|
||||
}
|
||||
|
||||
CString getCategory() const override { return CString(""); }
|
||||
CString getVersion() const override { return CString("0.1"); }
|
||||
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
|
||||
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_Algorithm_PairwiseStrategy_PKPD; }
|
||||
IPluginObject* create() override { return new CAlgorithmPairwiseStrategyPKPD; }
|
||||
|
||||
bool getAlgorithmPrototype(Kernel::IAlgorithmProto& prototype) const override
|
||||
{
|
||||
CAlgorithmPairwiseDecisionDesc::getAlgorithmPrototype(prototype);
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(CAlgorithmPairwiseDecisionDesc, OVP_ClassId_Algorithm_PairwiseStrategy_PKPDDesc)
|
||||
};
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,247 @@
|
||||
#include "ovpCBoxAlgorithmClassifierProcessor.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include <xml/IXMLHandler.h>
|
||||
#include <xml/IXMLNode.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
|
||||
bool CBoxAlgorithmClassifierProcessor::loadClassifier(const char* filename)
|
||||
{
|
||||
if (m_classifier)
|
||||
{
|
||||
m_classifier->uninitialize();
|
||||
this->getAlgorithmManager().releaseAlgorithm(*m_classifier);
|
||||
m_classifier = nullptr;
|
||||
}
|
||||
|
||||
XML::IXMLHandler* handler = XML::createXMLHandler();
|
||||
XML::IXMLNode* rootNode = handler->parseFile(filename);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(rootNode, "Unable to get xml root node from file at " << filename, Kernel::ErrorType::BadParsing);
|
||||
|
||||
m_stimulations.clear();
|
||||
|
||||
// Check the version of the file
|
||||
OV_ERROR_UNLESS_KRF(rootNode->hasAttribute(FORMAT_VERSION_ATTRIBUTE_NAME), "Configuration file [" << filename << "] has no version information",
|
||||
Kernel::ErrorType::ResourceNotFound);
|
||||
|
||||
std::stringstream data(rootNode->getAttribute(FORMAT_VERSION_ATTRIBUTE_NAME));
|
||||
size_t version;
|
||||
data >> version;
|
||||
|
||||
OV_WARNING_UNLESS_K(version <= OVP_Classification_BoxTrainerFormatVersion,
|
||||
"Classifier configuration in [" << filename << "] saved using a newer version: saved version = [" << version
|
||||
<< "] vs current version = [" << OVP_Classification_BoxTrainerFormatVersion << "]");
|
||||
|
||||
OV_ERROR_UNLESS_KRF(version >= OVP_Classification_BoxTrainerFormatVersionRequired,
|
||||
"Classifier configuration in [" << filename << "] saved using an obsolete version [" << version << "] (minimum expected version = "
|
||||
<< OVP_Classification_BoxTrainerFormatVersionRequired << ")", Kernel::ErrorType::BadVersion);
|
||||
|
||||
CIdentifier algorithmClassID = CIdentifier::undefined();
|
||||
|
||||
XML::IXMLNode* tmp = rootNode->getChildByName(STRATEGY_NODE_NAME);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(tmp, "Configuration file [" << filename << "] has no node " << STRATEGY_NODE_NAME, Kernel::ErrorType::BadParsing);
|
||||
|
||||
algorithmClassID.fromString(tmp->getAttribute(IDENTIFIER_ATTRIBUTE_NAME));
|
||||
|
||||
//If the Identifier is undefined, that means we need to load a native algorithm
|
||||
if (algorithmClassID == CIdentifier::undefined())
|
||||
{
|
||||
tmp = rootNode->getChildByName(ALGORITHM_NODE_NAME);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(tmp, "Configuration file [" << filename << "] has no node " << ALGORITHM_NODE_NAME, Kernel::ErrorType::BadParsing);
|
||||
|
||||
algorithmClassID.fromString(tmp->getAttribute(IDENTIFIER_ATTRIBUTE_NAME));
|
||||
|
||||
//If the algorithm is still unknown, that means that we face an error
|
||||
OV_ERROR_UNLESS_KRF(algorithmClassID != CIdentifier::undefined(), "No classifier retrieved from configuration file [" << filename << "]",
|
||||
Kernel::ErrorType::BadConfig);
|
||||
}
|
||||
|
||||
//Now loading all stimulations output
|
||||
XML::IXMLNode* stimNode = rootNode->getChildByName(STIMULATIONS_NODE_NAME);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(stimNode, "Configuration file [" << filename << "] has no node " << STIMULATIONS_NODE_NAME, Kernel::ErrorType::BadParsing);
|
||||
|
||||
//Now load every stimulation and store them in the map with the right class id
|
||||
for (size_t i = 0; i < stimNode->getChildCount(); ++i)
|
||||
{
|
||||
tmp = stimNode->getChild(i);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(tmp, "Invalid NULL child node " << i << " for node [" << STIMULATIONS_NODE_NAME << "]", Kernel::ErrorType::BadParsing);
|
||||
|
||||
CString name(tmp->getPCData());
|
||||
|
||||
double classID;
|
||||
const char* att = tmp->getAttribute(IDENTIFIER_ATTRIBUTE_NAME);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(att, "Invalid child node " << i << " for node [" << STIMULATIONS_NODE_NAME << "]: attribute ["
|
||||
<< IDENTIFIER_ATTRIBUTE_NAME << "] not found", Kernel::ErrorType::BadParsing);
|
||||
|
||||
std::stringstream ss(att);
|
||||
ss >> classID;
|
||||
m_stimulations[classID] = this->getTypeManager().getEnumerationEntryValueFromName(OV_TypeId_Stimulation, name);
|
||||
}
|
||||
|
||||
const CIdentifier id = this->getAlgorithmManager().createAlgorithm(algorithmClassID);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(id != CIdentifier::undefined(),
|
||||
"Invalid classifier algorithm with id [" << algorithmClassID.str() << "] in configuration file [" << filename << "]",
|
||||
Kernel::ErrorType::BadConfig);
|
||||
|
||||
m_classifier = &this->getAlgorithmManager().getAlgorithm(id);
|
||||
m_classifier->initialize();
|
||||
|
||||
// Connect the params to the new classifier
|
||||
|
||||
Kernel::TParameterHandler<CMatrix*> ip_sample = m_classifier->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_FeatureVector);
|
||||
ip_sample.setReferenceTarget(m_sampleDecoder.getOutputMatrix());
|
||||
|
||||
m_hyperplanesEncoder.getInputMatrix().
|
||||
setReferenceTarget(m_classifier->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_ClassificationValues));
|
||||
m_probabilitiesEncoder.getInputMatrix().setReferenceTarget(m_classifier->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_ProbabilityValues));
|
||||
// note: labelsencoder cannot be directly bound here as the classifier returns a float, but we need to output a stimulation
|
||||
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> ip_classificationConfig(m_classifier->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_Config));
|
||||
ip_classificationConfig = rootNode->getChildByName(CLASSIFIER_ROOT)->getChild(0);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_classifier->process(OVTK_Algorithm_Classifier_InputTriggerId_LoadConfig),
|
||||
"Loading configuration failed for subclassifier [" << id.str() << "]", Kernel::ErrorType::Internal);
|
||||
|
||||
rootNode->release();
|
||||
handler->release();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmClassifierProcessor::initialize()
|
||||
{
|
||||
m_classifier = nullptr;
|
||||
|
||||
//First of all, let's get the XML file for configuration
|
||||
const CString configFilename = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(configFilename != CString(""), "Invalid empty configuration file name", Kernel::ErrorType::BadConfig);
|
||||
|
||||
m_sampleDecoder.initialize(*this, 0);
|
||||
m_stimDecoder.initialize(*this, 1);
|
||||
|
||||
m_labelsEncoder.initialize(*this, 0);
|
||||
m_hyperplanesEncoder.initialize(*this, 1);
|
||||
m_probabilitiesEncoder.initialize(*this, 2);
|
||||
|
||||
return loadClassifier(configFilename.toASCIIString());
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmClassifierProcessor::uninitialize()
|
||||
{
|
||||
if (m_classifier)
|
||||
{
|
||||
m_classifier->uninitialize();
|
||||
this->getAlgorithmManager().releaseAlgorithm(*m_classifier);
|
||||
m_classifier = nullptr;
|
||||
}
|
||||
|
||||
m_probabilitiesEncoder.uninitialize();
|
||||
m_hyperplanesEncoder.uninitialize();
|
||||
m_labelsEncoder.uninitialize();
|
||||
|
||||
m_stimDecoder.uninitialize();
|
||||
m_sampleDecoder.uninitialize();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmClassifierProcessor::processInput(const size_t /*index*/)
|
||||
{
|
||||
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmClassifierProcessor::process()
|
||||
{
|
||||
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
|
||||
|
||||
// Check if we have a command first
|
||||
for (size_t i = 0; i < boxContext.getInputChunkCount(1); ++i)
|
||||
{
|
||||
m_stimDecoder.decode(i);
|
||||
if (m_stimDecoder.isHeaderReceived()) { }
|
||||
if (m_stimDecoder.isBufferReceived())
|
||||
{
|
||||
for (size_t j = 0; j < m_stimDecoder.getOutputStimulationSet()->getStimulationCount(); ++j)
|
||||
{
|
||||
if (m_stimDecoder.getOutputStimulationSet()->getStimulationIdentifier(j) == OVTK_StimulationId_TrainCompleted)
|
||||
{
|
||||
CString configFilename = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
|
||||
if (!loadClassifier(configFilename.toASCIIString())) { return false; }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_stimDecoder.isEndReceived()) { }
|
||||
}
|
||||
|
||||
// Classify data
|
||||
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
|
||||
{
|
||||
const uint64_t startTime = boxContext.getInputChunkStartTime(0, i);
|
||||
const uint64_t endTime = boxContext.getInputChunkEndTime(0, i);
|
||||
|
||||
m_sampleDecoder.decode(i);
|
||||
if (m_sampleDecoder.isHeaderReceived())
|
||||
{
|
||||
m_labelsEncoder.encodeHeader();
|
||||
m_hyperplanesEncoder.encodeHeader();
|
||||
m_probabilitiesEncoder.encodeHeader();
|
||||
|
||||
boxContext.markOutputAsReadyToSend(0, startTime, endTime);
|
||||
boxContext.markOutputAsReadyToSend(1, startTime, endTime);
|
||||
boxContext.markOutputAsReadyToSend(2, startTime, endTime);
|
||||
}
|
||||
if (m_sampleDecoder.isBufferReceived())
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(m_classifier->process(OVTK_Algorithm_Classifier_InputTriggerId_Classify)
|
||||
&& m_classifier->isOutputTriggerActive(OVTK_Algorithm_Classifier_OutputTriggerId_Success),
|
||||
"Classification failed", Kernel::ErrorType::Internal);
|
||||
|
||||
Kernel::TParameterHandler<double> op_classificationState(m_classifier->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_Class));
|
||||
|
||||
IStimulationSet* set = m_labelsEncoder.getInputStimulationSet();
|
||||
|
||||
set->setStimulationCount(1);
|
||||
set->setStimulationIdentifier(0, m_stimulations[op_classificationState]);
|
||||
set->setStimulationDate(0, endTime);
|
||||
set->setStimulationDuration(0, 0);
|
||||
|
||||
m_labelsEncoder.encodeBuffer();
|
||||
m_hyperplanesEncoder.encodeBuffer();
|
||||
m_probabilitiesEncoder.encodeBuffer();
|
||||
|
||||
boxContext.markOutputAsReadyToSend(0, startTime, endTime);
|
||||
boxContext.markOutputAsReadyToSend(1, startTime, endTime);
|
||||
boxContext.markOutputAsReadyToSend(2, startTime, endTime);
|
||||
}
|
||||
|
||||
if (m_sampleDecoder.isEndReceived())
|
||||
{
|
||||
m_labelsEncoder.encodeEnd();
|
||||
m_hyperplanesEncoder.encodeEnd();
|
||||
m_probabilitiesEncoder.encodeEnd();
|
||||
|
||||
boxContext.markOutputAsReadyToSend(0, startTime, endTime);
|
||||
boxContext.markOutputAsReadyToSend(1, startTime, endTime);
|
||||
boxContext.markOutputAsReadyToSend(2, startTime, endTime);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
class CBoxAlgorithmClassifierProcessor final : virtual public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
|
||||
{
|
||||
public:
|
||||
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_ClassifierProcessor)
|
||||
|
||||
protected:
|
||||
bool loadClassifier(const char* filename);
|
||||
|
||||
private:
|
||||
|
||||
Toolkit::TFeatureVectorDecoder<CBoxAlgorithmClassifierProcessor> m_sampleDecoder;
|
||||
Toolkit::TStimulationDecoder<CBoxAlgorithmClassifierProcessor> m_stimDecoder;
|
||||
Toolkit::TStimulationEncoder<CBoxAlgorithmClassifierProcessor> m_labelsEncoder;
|
||||
Toolkit::TStreamedMatrixEncoder<CBoxAlgorithmClassifierProcessor> m_hyperplanesEncoder;
|
||||
Toolkit::TStreamedMatrixEncoder<CBoxAlgorithmClassifierProcessor> m_probabilitiesEncoder;
|
||||
|
||||
Kernel::IAlgorithmProxy* m_classifier = nullptr;
|
||||
|
||||
std::map<double, uint64_t> m_stimulations;
|
||||
};
|
||||
|
||||
class CBoxAlgorithmClassifierProcessorDesc final : virtual public IBoxAlgorithmDesc
|
||||
{
|
||||
public:
|
||||
void release() override { }
|
||||
CString getName() const override { return CString("Classifier processor"); }
|
||||
CString getAuthorName() const override { return CString("Yann Renard, Guillaume Serriere"); }
|
||||
CString getAuthorCompanyName() const override { return CString("INRIA/IRISA"); }
|
||||
CString getShortDescription() const override { return CString("Generic classification, relying on several box algorithms"); }
|
||||
|
||||
CString getDetailedDescription() const override { return CString("Classifies incoming feature vectors using a previously learned classifier."); }
|
||||
|
||||
CString getCategory() const override { return CString("Classification"); }
|
||||
CString getVersion() const override { return CString("2.1"); }
|
||||
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
|
||||
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CString getUpdatedSoftwareVersion() const override { return CString("0.1.0"); }
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_ClassifierProcessor; }
|
||||
IPluginObject* create() override { return new CBoxAlgorithmClassifierProcessor; }
|
||||
|
||||
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
|
||||
{
|
||||
prototype.addInput("Features", OV_TypeId_FeatureVector);
|
||||
prototype.addInput("Commands", OV_TypeId_Stimulations);
|
||||
prototype.addOutput("Labels", OV_TypeId_Stimulations);
|
||||
prototype.addOutput("Hyperplane distance", OV_TypeId_StreamedMatrix);
|
||||
prototype.addOutput("Probability values", OV_TypeId_StreamedMatrix);
|
||||
|
||||
//We load everything in the save filed
|
||||
prototype.addSetting("Filename to load configuration from", OV_TypeId_Filename, "");
|
||||
return true;
|
||||
}
|
||||
|
||||
// virtual IBoxListener* createBoxListener() const { return new CBoxAlgorithmCommonClassifierListener(5); }
|
||||
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
|
||||
|
||||
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_ClassifierProcessorDesc)
|
||||
};
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,590 @@
|
||||
#include "ovpCBoxAlgorithmClassifierTrainer.h"
|
||||
#include <system/ovCMath.h>
|
||||
|
||||
#include <xml/IXMLHandler.h>
|
||||
#include <xml/IXMLNode.h>
|
||||
|
||||
#include <sstream>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <iomanip> // setw
|
||||
//This needs to reachable from outside
|
||||
const char* const CLASSIFIER_ROOT = "OpenViBE-Classifier";
|
||||
const char* const FORMAT_VERSION_ATTRIBUTE_NAME = "FormatVersion";
|
||||
const char* const CREATOR_ATTRIBUTE_NAME = "Creator";
|
||||
const char* const CREATOR_VERSION_ATTRIBUTE_NAME = "CreatorVersion";
|
||||
const char* const IDENTIFIER_ATTRIBUTE_NAME = "class-id";
|
||||
const char* const STRATEGY_NODE_NAME = "Strategy-Identifier";
|
||||
const char* const ALGORITHM_NODE_NAME = "Algorithm-Identifier";
|
||||
const char* const STIMULATIONS_NODE_NAME = "Stimulations";
|
||||
const char* const REJECTED_CLASS_NODE_NAME = "Rejected-Class";
|
||||
const char* const CLASS_STIMULATION_NODE_NAME = "Class-Stimulation";
|
||||
const char* const CLASSIFICATION_BOX_ROOT = "OpenViBE-Classifier-Box";
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
|
||||
bool CBoxAlgorithmClassifierTrainer::initialize()
|
||||
{
|
||||
m_classifier = nullptr;
|
||||
m_parameter = nullptr;
|
||||
|
||||
const Kernel::IBox& boxContext = this->getStaticBoxContext();
|
||||
//As we add some parameter in the middle of "static" parameters, we cannot rely on settings index.
|
||||
m_parameter = new std::map<CString, CString>();
|
||||
for (size_t i = 0; i < boxContext.getSettingCount(); ++i)
|
||||
{
|
||||
CString name;
|
||||
boxContext.getSettingName(i, name);
|
||||
const CString value = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), i);
|
||||
(*m_parameter)[name] = value;
|
||||
}
|
||||
|
||||
bool isPairing = false;
|
||||
|
||||
const CString configFilename(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2));
|
||||
|
||||
OV_ERROR_UNLESS_KRF(configFilename != CString(""), "Invalid empty configuration filename", Kernel::ErrorType::BadSetting);
|
||||
|
||||
CIdentifier classifierAlgorithmClassID;
|
||||
|
||||
const CIdentifier strategyClassID = this->getTypeManager().getEnumerationEntryValueFromName(
|
||||
OVTK_TypeId_ClassificationStrategy, (*m_parameter)[MULTICLASS_STRATEGY_SETTING_NAME]);
|
||||
classifierAlgorithmClassID = this->getTypeManager().getEnumerationEntryValueFromName(
|
||||
OVTK_TypeId_ClassificationAlgorithm, (*m_parameter)[ALGORITHM_SETTING_NAME]);
|
||||
|
||||
if (strategyClassID == CIdentifier::undefined())
|
||||
{
|
||||
//That means that we want to use a classical algorithm so just let's create it
|
||||
const CIdentifier classifierAlgorithmID = this->getAlgorithmManager().createAlgorithm(classifierAlgorithmClassID);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(classifierAlgorithmID != CIdentifier::undefined(),
|
||||
"Unable to instantiate classifier for class [" << classifierAlgorithmID.str() << "]", Kernel::ErrorType::BadConfig);
|
||||
|
||||
m_classifier = &this->getAlgorithmManager().getAlgorithm(classifierAlgorithmID);
|
||||
m_classifier->initialize();
|
||||
}
|
||||
else
|
||||
{
|
||||
isPairing = true;
|
||||
m_classifier = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(strategyClassID));
|
||||
m_classifier->initialize();
|
||||
}
|
||||
m_trainStimulation = this->getTypeManager().getEnumerationEntryValueFromName(OV_TypeId_Stimulation, (*m_parameter)[TRAIN_TRIGGER_SETTING_NAME]);
|
||||
|
||||
const int64_t nPartition = this->getConfigurationManager().expandAsInteger((*m_parameter)[FOLD_SETTING_NAME]);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(nPartition >= 0, "Invalid partition count [" << nPartition << "] (expected value >= 0)", Kernel::ErrorType::BadSetting);
|
||||
|
||||
m_nPartition = uint64_t(nPartition);
|
||||
|
||||
m_stimDecoder.initialize(*this, 0);
|
||||
for (size_t i = 1; i < boxContext.getInputCount(); ++i)
|
||||
{
|
||||
m_sampleDecoder.push_back(new Toolkit::TFeatureVectorDecoder<CBoxAlgorithmClassifierTrainer>());
|
||||
m_sampleDecoder.back()->initialize(*this, i);
|
||||
}
|
||||
|
||||
//We link the parameters to the extra parameters input parameter to transmit them
|
||||
Kernel::TParameterHandler<std::map<CString, CString>*> ip_parameter(
|
||||
m_classifier->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_ExtraParameter));
|
||||
ip_parameter = m_parameter;
|
||||
|
||||
m_encoder.initialize(*this, 0);
|
||||
|
||||
m_nFeatures.clear();
|
||||
|
||||
OV_ERROR_UNLESS_KRF(boxContext.getInputCount() >= 2, "Invalid input count [" << boxContext.getInputCount() << "] (at least 2 input expected)",
|
||||
Kernel::ErrorType::BadSetting);
|
||||
|
||||
// Provide the number of classes to the classifier
|
||||
const size_t nClass = boxContext.getInputCount() - 1;
|
||||
Kernel::TParameterHandler<uint64_t> ip_nClasses(m_classifier->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_NClasses));
|
||||
ip_nClasses = nClass;
|
||||
|
||||
//If we have to deal with a pairing strategy we have to pass argument
|
||||
if (isPairing)
|
||||
{
|
||||
Kernel::TParameterHandler<CIdentifier*> ip_classId(
|
||||
m_classifier->getInputParameter(OVTK_Algorithm_PairingStrategy_InputParameterId_SubClassifierAlgorithm));
|
||||
ip_classId = &classifierAlgorithmClassID;
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_classifier->process(OVTK_Algorithm_PairingStrategy_InputTriggerId_DesignArchitecture), "Failed to design architecture",
|
||||
Kernel::ErrorType::Internal);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmClassifierTrainer::uninitialize()
|
||||
{
|
||||
m_stimDecoder.uninitialize();
|
||||
m_encoder.uninitialize();
|
||||
|
||||
if (m_classifier)
|
||||
{
|
||||
m_classifier->uninitialize();
|
||||
this->getAlgorithmManager().releaseAlgorithm(*m_classifier);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < m_sampleDecoder.size(); ++i)
|
||||
{
|
||||
m_sampleDecoder[i]->uninitialize();
|
||||
delete m_sampleDecoder[i];
|
||||
}
|
||||
m_sampleDecoder.clear();
|
||||
|
||||
m_encoder.uninitialize();
|
||||
m_stimDecoder.uninitialize();
|
||||
|
||||
for (size_t i = 0; i < m_datasets.size(); ++i)
|
||||
{
|
||||
delete m_datasets[i].sampleMatrix;
|
||||
m_datasets[i].sampleMatrix = nullptr;
|
||||
}
|
||||
m_datasets.clear();
|
||||
|
||||
if (m_parameter)
|
||||
{
|
||||
delete m_parameter;
|
||||
m_parameter = nullptr;
|
||||
}
|
||||
|
||||
// @fixme who frees this? freeing here -> crash
|
||||
/*
|
||||
if(m_pExtraParameter != nullptr)
|
||||
{
|
||||
delete m_pExtraParameter;
|
||||
m_pExtraParameter = NULL;
|
||||
}
|
||||
*/
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmClassifierTrainer::processInput(const size_t /*index*/)
|
||||
{
|
||||
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Find the most likely class and resample the dataset so that each class is as likely
|
||||
bool CBoxAlgorithmClassifierTrainer::balanceDataset()
|
||||
{
|
||||
const Kernel::IBox& boxContext = this->getStaticBoxContext();
|
||||
const size_t nClass = boxContext.getInputCount() - 1;
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Info << "Balancing dataset...\n";
|
||||
|
||||
// Collect index set of feature vectors per class
|
||||
std::vector<std::vector<size_t>> classIndexes;
|
||||
classIndexes.resize(nClass);
|
||||
for (size_t i = 0; i < m_datasets.size(); ++i) { classIndexes[m_datasets[i].inputIdx].push_back(i); }
|
||||
|
||||
// Count how many vectors the largest class has
|
||||
size_t nMax = 0;
|
||||
for (size_t i = 0; i < nClass; ++i) { nMax = std::max<size_t>(nMax, classIndexes[i].size()); }
|
||||
|
||||
m_balancedDatasets.clear();
|
||||
|
||||
// Pad those classes with resampled examples (sampling with replacement) that have fewer examples than the largest class
|
||||
for (size_t i = 0; i < nClass; ++i)
|
||||
{
|
||||
const size_t examplesInClass = classIndexes[i].size();
|
||||
const size_t paddingNeeded = nMax - examplesInClass;
|
||||
if (examplesInClass == 0)
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Debug << "Cannot resample class " << i << ", 0 examples\n";
|
||||
continue;
|
||||
}
|
||||
if (paddingNeeded > 0) { this->getLogManager() << Kernel::LogLevel_Debug << "Padding class " << i << " with " << paddingNeeded << " examples\n"; }
|
||||
|
||||
// Copy all the examples first to a temporary array so we don't mess with the original data.
|
||||
// This is not too bad as instead of data, we copy the pointer. m_datasets owns the data pointer.
|
||||
const std::vector<size_t>& thisClassesIndexes = classIndexes[i];
|
||||
for (size_t j = 0; j < examplesInClass; ++j) { m_balancedDatasets.push_back(m_datasets[thisClassesIndexes[j]]); }
|
||||
|
||||
for (size_t j = 0; j < paddingNeeded; ++j)
|
||||
{
|
||||
const size_t sampledIndex = System::Math::randomWithCeiling(examplesInClass);
|
||||
const sample_t& sourceVector = m_datasets[thisClassesIndexes[sampledIndex]];
|
||||
m_balancedDatasets.push_back(sourceVector);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmClassifierTrainer::process()
|
||||
{
|
||||
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
|
||||
const size_t nInput = this->getStaticBoxContext().getInputCount();
|
||||
|
||||
bool startTrain = false;
|
||||
|
||||
// Parses stimulations
|
||||
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
|
||||
{
|
||||
m_stimDecoder.decode(i);
|
||||
|
||||
if (m_stimDecoder.isHeaderReceived())
|
||||
{
|
||||
m_encoder.encodeHeader();
|
||||
boxContext.markOutputAsReadyToSend(0, 0, 0);
|
||||
}
|
||||
if (m_stimDecoder.isBufferReceived())
|
||||
{
|
||||
const IStimulationSet* iStimulationSet = m_stimDecoder.getOutputStimulationSet();
|
||||
IStimulationSet* oStimulationSet = m_encoder.getInputStimulationSet();
|
||||
oStimulationSet->clear();
|
||||
|
||||
for (size_t j = 0; j < iStimulationSet->getStimulationCount(); ++j)
|
||||
{
|
||||
if (iStimulationSet->getStimulationIdentifier(j) == m_trainStimulation)
|
||||
{
|
||||
startTrain = true;
|
||||
const uint64_t id = this->getTypeManager().getEnumerationEntryValueFromName(OV_TypeId_Stimulation, "OVTK_StimulationId_TrainCompleted");
|
||||
oStimulationSet->appendStimulation(id, iStimulationSet->getStimulationDate(j), 0);
|
||||
}
|
||||
}
|
||||
m_encoder.encodeBuffer();
|
||||
|
||||
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
|
||||
}
|
||||
if (m_stimDecoder.isEndReceived())
|
||||
{
|
||||
m_encoder.encodeEnd();
|
||||
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
|
||||
}
|
||||
}
|
||||
|
||||
// Parses feature vectors
|
||||
for (size_t i = 1; i < nInput; ++i)
|
||||
{
|
||||
for (size_t j = 0; j < boxContext.getInputChunkCount(i); ++j)
|
||||
{
|
||||
m_sampleDecoder[i - 1]->decode(j);
|
||||
|
||||
if (m_sampleDecoder[i - 1]->isHeaderReceived()) { }
|
||||
if (m_sampleDecoder[i - 1]->isBufferReceived())
|
||||
{
|
||||
const CMatrix* sampleMatrix = m_sampleDecoder[i - 1]->getOutputMatrix();
|
||||
|
||||
sample_t sample;
|
||||
sample.sampleMatrix = new CMatrix();
|
||||
sample.startTime = boxContext.getInputChunkStartTime(i, j);
|
||||
sample.endTime = boxContext.getInputChunkEndTime(i, j);
|
||||
sample.inputIdx = i - 1;
|
||||
|
||||
sample.sampleMatrix->copy(*sampleMatrix);
|
||||
m_datasets.push_back(sample);
|
||||
m_nFeatures[i]++;
|
||||
}
|
||||
if (m_sampleDecoder[i - 1]->isEndReceived()) { }
|
||||
}
|
||||
}
|
||||
|
||||
// On train stimulation reception, build up the labelled feature vector set matrix and go on training
|
||||
if (startTrain)
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(m_datasets.size() >= m_nPartition,
|
||||
"Received fewer examples (" << m_datasets.size() << ") than specified partition count (" << m_nPartition << ")",
|
||||
Kernel::ErrorType::BadInput);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(!m_datasets.empty(), "No training example received", Kernel::ErrorType::BadInput);
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Info << "Received train stimulation. Data dim is [" << m_datasets.size() << "x"
|
||||
<< m_datasets[0].sampleMatrix->getBufferElementCount() << "]\n";
|
||||
for (size_t i = 1; i < nInput; ++i)
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Info << "For information, we have " << m_nFeatures[i] << " feature vector(s) for input " << i << "\n";
|
||||
}
|
||||
|
||||
const bool balancedDataset = this->getConfigurationManager().expandAsBoolean((*m_parameter)[BALANCE_SETTING_NAME]);
|
||||
if (balancedDataset) { balanceDataset(); }
|
||||
|
||||
const std::vector<sample_t>& actualDataset = (balancedDataset ? m_balancedDatasets : m_datasets);
|
||||
|
||||
std::vector<double> partitionAccuracies(m_nPartition);
|
||||
|
||||
const bool randomizeVectorOrder = this->getConfigurationManager().expandAsBoolean("${Plugin_Classification_RandomizeKFoldTestData}", false);
|
||||
|
||||
// create a vector used for mapping feature vectors (initialize it as v[i] = i)
|
||||
std::vector<size_t> featurePermutation;
|
||||
for (size_t i = 0; i < actualDataset.size(); ++i) { featurePermutation.push_back(i); }
|
||||
|
||||
// randomize the vector if necessary
|
||||
if (randomizeVectorOrder)
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Info << "Randomizing the feature vector set\n";
|
||||
random_shuffle(featurePermutation.begin(), featurePermutation.end(), System::Math::randomWithCeiling);
|
||||
}
|
||||
|
||||
const size_t nClass = nInput - 1;
|
||||
CMatrix confusion(nClass, nClass);
|
||||
|
||||
if (m_nPartition >= 2)
|
||||
{
|
||||
double partitionAccuracy = 0;
|
||||
double finalAccuracy = 0;
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Info << "k-fold test could take quite a long time, be patient\n";
|
||||
for (size_t i = 0; i < m_nPartition; ++i)
|
||||
{
|
||||
const size_t startIdx = size_t(((i) * actualDataset.size()) / m_nPartition);
|
||||
const size_t stopIdx = size_t(((i + 1) * actualDataset.size()) / m_nPartition);
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Trace << "Training on partition " << i << " (feature vectors " << startIdx << " to " <<
|
||||
stopIdx - 1 << ")...\n";
|
||||
|
||||
OV_ERROR_UNLESS_KRF(this->train(actualDataset, featurePermutation, startIdx, stopIdx), "Training failed: bailing out (from xval)",
|
||||
Kernel::ErrorType::Internal);
|
||||
|
||||
partitionAccuracy = this->getAccuracy(actualDataset, featurePermutation, startIdx, stopIdx, confusion);
|
||||
partitionAccuracies[i] = partitionAccuracy;
|
||||
finalAccuracy += partitionAccuracy;
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Info << "Finished with partition " << i + 1 << " / " << m_nPartition << " (performance : "
|
||||
<< partitionAccuracy << "%)\n";
|
||||
}
|
||||
|
||||
const double mean = finalAccuracy / m_nPartition;
|
||||
double deviation = 0;
|
||||
|
||||
for (size_t i = 0; i < m_nPartition; ++i)
|
||||
{
|
||||
const double diff = partitionAccuracies[i] - mean;
|
||||
deviation += diff * diff;
|
||||
}
|
||||
deviation = sqrt(deviation / m_nPartition);
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Info << "Cross-validation test accuracy is " << mean << "% (sigma = " << deviation << "%)\n";
|
||||
|
||||
printConfusionMatrix(confusion);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Info << "Training without cross-validation.\n";
|
||||
this->getLogManager() << Kernel::LogLevel_Info << "*** Reported training set accuracy will be optimistic ***\n";
|
||||
}
|
||||
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Trace << "Training final classifier on the whole set...\n";
|
||||
|
||||
OV_ERROR_UNLESS_KRF(this->train(actualDataset, featurePermutation, 0, 0),
|
||||
"Training failed: bailing out (from whole set training)", Kernel::ErrorType::Internal);
|
||||
|
||||
confusion.resetBuffer();
|
||||
const double accuracy = this->getAccuracy(actualDataset, featurePermutation, 0, actualDataset.size(), confusion);
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Info << "Training set accuracy is " << accuracy << "% (optimistic)\n";
|
||||
|
||||
printConfusionMatrix(confusion);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(this->saveConfig(), "Failed to save configuration", Kernel::ErrorType::Internal);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmClassifierTrainer::train(const std::vector<sample_t>& dataset, const std::vector<size_t>& permutation, const size_t startIdx,
|
||||
const size_t stopIdx)
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(stopIdx - startIdx != 1, "Invalid indexes: stopIdx - trainIndex = 1", Kernel::ErrorType::BadArgument);
|
||||
|
||||
const size_t nSample = dataset.size() - (stopIdx - startIdx);
|
||||
const size_t nFeature = dataset[0].sampleMatrix->getBufferElementCount();
|
||||
|
||||
Kernel::TParameterHandler<CMatrix*> ip_sample(m_classifier->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_FeatureVectorSet));
|
||||
|
||||
ip_sample->resize(nSample, nFeature + 1);
|
||||
|
||||
double* buffer = ip_sample->getBuffer();
|
||||
for (size_t j = 0; j < dataset.size() - (stopIdx - startIdx); ++j)
|
||||
{
|
||||
const size_t k = permutation[(j < startIdx ? j : j + (stopIdx - startIdx))];
|
||||
const double classId = double(dataset[k].inputIdx);
|
||||
memcpy(buffer, dataset[k].sampleMatrix->getBuffer(), nFeature * sizeof(double));
|
||||
|
||||
buffer[nFeature] = classId;
|
||||
buffer += (nFeature + 1);
|
||||
}
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_classifier->process(OVTK_Algorithm_Classifier_InputTriggerId_Train), "Training failed", Kernel::ErrorType::Internal);
|
||||
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> op_configuration(m_classifier->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_Config));
|
||||
XML::IXMLNode* node = static_cast<XML::IXMLNode*>(op_configuration);
|
||||
|
||||
if (node != nullptr) { node->release(); }
|
||||
op_configuration = nullptr;
|
||||
|
||||
return m_classifier->process(OVTK_Algorithm_Classifier_InputTriggerId_SaveConfig);
|
||||
}
|
||||
|
||||
// Note that this function is incremental for confusionMatrix and can be called many times; so we don't clear the matrix
|
||||
double CBoxAlgorithmClassifierTrainer::getAccuracy(const std::vector<sample_t>& dataset, const std::vector<size_t>& permutation,
|
||||
const size_t startIdx, const size_t stopIdx, CMatrix& confusionMatrix)
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(stopIdx != startIdx, "Invalid indexes: start index equals stop index", Kernel::ErrorType::BadArgument);
|
||||
|
||||
const size_t nFeature = dataset[0].sampleMatrix->getBufferElementCount();
|
||||
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> op_config(m_classifier->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_Config));
|
||||
XML::IXMLNode* node = op_config;//Requested for affectation
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> ip_config(m_classifier->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_Config));
|
||||
ip_config = node;
|
||||
|
||||
m_classifier->process(OVTK_Algorithm_Classifier_InputTriggerId_LoadConfig);
|
||||
|
||||
Kernel::TParameterHandler<CMatrix*> ip_sample(m_classifier->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_FeatureVector));
|
||||
Kernel::TParameterHandler<double> op_classificationState(m_classifier->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_Class));
|
||||
ip_sample->resize(nFeature);
|
||||
|
||||
size_t nSuccess = 0;
|
||||
|
||||
for (size_t j = startIdx; j < stopIdx; ++j)
|
||||
{
|
||||
const size_t k = permutation[j];
|
||||
|
||||
double* buffer = ip_sample->getBuffer();
|
||||
const double correctValue = double(dataset[k].inputIdx);
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Debug << "Try to recognize " << correctValue << "\n";
|
||||
|
||||
memcpy(buffer, dataset[k].sampleMatrix->getBuffer(), nFeature * sizeof(double));
|
||||
|
||||
m_classifier->process(OVTK_Algorithm_Classifier_InputTriggerId_Classify);
|
||||
|
||||
const double predictedValue = op_classificationState;
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Debug << "Recognize " << predictedValue << "\n";
|
||||
|
||||
if (predictedValue == correctValue) { nSuccess++; }
|
||||
|
||||
if (predictedValue < confusionMatrix.getDimensionSize(0) && correctValue < confusionMatrix.getDimensionSize(0))
|
||||
{
|
||||
double* buf = confusionMatrix.getBuffer();
|
||||
buf[size_t(correctValue) * confusionMatrix.getDimensionSize(1) + size_t(predictedValue)] += 1.0;
|
||||
}
|
||||
else { std::cout << "error\n"; }
|
||||
}
|
||||
|
||||
return double((nSuccess * 100.0) / (stopIdx - startIdx));
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmClassifierTrainer::printConfusionMatrix(const CMatrix& oMatrix)
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(oMatrix.getDimensionCount() == 2 && oMatrix.getDimensionSize(0) == oMatrix.getDimensionSize(1),
|
||||
"Invalid confution matrix [dim count = " << oMatrix.getDimensionCount() << ", dim size 0 = "
|
||||
<< oMatrix.getDimensionSize(0) << ", dim size 1 = "<< oMatrix.getDimensionSize(1) << "] (expected 2 dimensions with same size)",
|
||||
Kernel::ErrorType::BadArgument);
|
||||
|
||||
const size_t rows = oMatrix.getDimensionSize(0);
|
||||
|
||||
if (rows > 10 && !this->getConfigurationManager().expandAsBoolean("${Plugin_Classification_ForceConfusionMatrixPrint}"))
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Info <<
|
||||
"Over 10 classes, not printing the confusion matrix. If needed, override with setting Plugin_Classification_ForceConfusionMatrixPrint token to true.\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
CMatrix tmp(oMatrix), rowSum(rows);
|
||||
|
||||
for (size_t i = 0; i < rows; ++i)
|
||||
{
|
||||
const size_t idx = i * rows;
|
||||
for (size_t j = 0; j < rows; ++j) { rowSum[i] += tmp[idx + j]; }
|
||||
for (size_t j = 0; j < rows; ++j) { tmp[idx + j] /= rowSum[i]; }
|
||||
}
|
||||
|
||||
std::stringstream ss;
|
||||
ss << std::fixed;
|
||||
|
||||
ss << " Cls vs cls ";
|
||||
for (size_t i = 0; i < rows; ++i) { ss << std::setw(6) << (i + 1); }
|
||||
this->getLogManager() << Kernel::LogLevel_Info << ss.str() << "\n";
|
||||
|
||||
ss.precision(1);
|
||||
for (size_t i = 0; i < rows; ++i)
|
||||
{
|
||||
ss.str("");
|
||||
ss << " Target " << std::setw(2) << (i + 1) << ": ";
|
||||
for (size_t j = 0; j < rows; ++j) { ss << std::setw(6) << tmp[i * rows + j] * 100; }
|
||||
this->getLogManager() << Kernel::LogLevel_Info << ss.str() << " %, " << size_t(rowSum[i]) << " examples\n";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmClassifierTrainer::saveConfig()
|
||||
{
|
||||
const Kernel::IBox& boxContext = this->getStaticBoxContext();
|
||||
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> op_config(m_classifier->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_Config));
|
||||
XML::IXMLNode* algorithmConfigNode = XML::createNode(CLASSIFIER_ROOT);
|
||||
algorithmConfigNode->addChild(static_cast<XML::IXMLNode*>(op_config));
|
||||
|
||||
XML::IXMLHandler* handler = XML::createXMLHandler();
|
||||
const CString configurationFilename(this->getConfigurationManager().expand((*m_parameter)[FILENAME_SETTING_NAME]));
|
||||
|
||||
XML::IXMLNode* root = XML::createNode(CLASSIFICATION_BOX_ROOT);
|
||||
std::stringstream version;
|
||||
version << OVP_Classification_BoxTrainerFormatVersion;
|
||||
root->addAttribute(FORMAT_VERSION_ATTRIBUTE_NAME, version.str().c_str());
|
||||
|
||||
const auto cleanup = [&]()
|
||||
{
|
||||
handler->release();
|
||||
root->release();
|
||||
op_config = nullptr;
|
||||
};
|
||||
root->addAttribute(CREATOR_ATTRIBUTE_NAME, this->getConfigurationManager().expand("${Application_Name}"));
|
||||
root->addAttribute(CREATOR_VERSION_ATTRIBUTE_NAME, this->getConfigurationManager().expand("${Application_Version}"));
|
||||
|
||||
XML::IXMLNode* tempNode = XML::createNode(STRATEGY_NODE_NAME);
|
||||
const CIdentifier strategyClassId = this->getTypeManager().getEnumerationEntryValueFromName(
|
||||
OVTK_TypeId_ClassificationStrategy, (*m_parameter)[MULTICLASS_STRATEGY_SETTING_NAME]);
|
||||
tempNode->addAttribute(IDENTIFIER_ATTRIBUTE_NAME, strategyClassId.str().c_str());
|
||||
tempNode->setPCData((*m_parameter)[MULTICLASS_STRATEGY_SETTING_NAME].toASCIIString());
|
||||
root->addChild(tempNode);
|
||||
|
||||
tempNode = XML::createNode(ALGORITHM_NODE_NAME);
|
||||
const CIdentifier classifierClassId = this->getTypeManager().getEnumerationEntryValueFromName(
|
||||
OVTK_TypeId_ClassificationAlgorithm, (*m_parameter)[ALGORITHM_SETTING_NAME]);
|
||||
tempNode->addAttribute(IDENTIFIER_ATTRIBUTE_NAME, classifierClassId.str().c_str());
|
||||
tempNode->setPCData((*m_parameter)[ALGORITHM_SETTING_NAME].toASCIIString());
|
||||
root->addChild(tempNode);
|
||||
|
||||
|
||||
XML::IXMLNode* stimulationsNode = XML::createNode(STIMULATIONS_NODE_NAME);
|
||||
|
||||
for (size_t i = 1; i < boxContext.getInputCount(); ++i)
|
||||
{
|
||||
const std::string name = "Class " + std::to_string(i) + " label";
|
||||
const std::string id = std::to_string(i - 1);
|
||||
tempNode = XML::createNode(CLASS_STIMULATION_NODE_NAME);
|
||||
tempNode->addAttribute(IDENTIFIER_ATTRIBUTE_NAME, id.c_str());
|
||||
tempNode->setPCData((*m_parameter)[name.c_str()].toASCIIString());
|
||||
stimulationsNode->addChild(tempNode);
|
||||
}
|
||||
root->addChild(stimulationsNode);
|
||||
|
||||
root->addChild(algorithmConfigNode);
|
||||
|
||||
if (!handler->writeXMLInFile(*root, configurationFilename.toASCIIString()))
|
||||
{
|
||||
cleanup();
|
||||
OV_ERROR_KRF("Failed saving configuration to file [" << configurationFilename << "]", Kernel::ErrorType::BadFileWrite);
|
||||
}
|
||||
|
||||
cleanup();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,131 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include "ovpCBoxAlgorithmCommonClassifierListener.inl"
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
|
||||
const char* const TRAIN_TRIGGER_SETTING_NAME = "Train trigger";
|
||||
const char* const FILENAME_SETTING_NAME = "Filename to save configuration to";
|
||||
const char* const MULTICLASS_STRATEGY_SETTING_NAME = "Multiclass strategy to apply";
|
||||
const char* const ALGORITHM_SETTING_NAME = "Algorithm to use";
|
||||
const char* const FOLD_SETTING_NAME = "Number of partitions for k-fold cross-validation test";
|
||||
const char* const BALANCE_SETTING_NAME = "Balance classes";
|
||||
|
||||
class CBoxAlgorithmClassifierTrainer final : virtual public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
|
||||
{
|
||||
public:
|
||||
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_ClassifierTrainer)
|
||||
|
||||
protected:
|
||||
|
||||
typedef struct
|
||||
{
|
||||
CMatrix* sampleMatrix;
|
||||
uint64_t startTime;
|
||||
uint64_t endTime;
|
||||
size_t inputIdx;
|
||||
} sample_t;
|
||||
|
||||
bool train(const std::vector<sample_t>& dataset, const std::vector<size_t>& permutation, size_t startIdx, size_t stopIdx);
|
||||
double getAccuracy(const std::vector<sample_t>& dataset, const std::vector<size_t>& permutation, size_t startIdx, size_t stopIdx, CMatrix& confusionMatrix);
|
||||
bool printConfusionMatrix(const CMatrix& oMatrix);
|
||||
bool balanceDataset();
|
||||
|
||||
private:
|
||||
bool saveConfig();
|
||||
|
||||
protected:
|
||||
|
||||
std::map<size_t, size_t> m_nFeatures;
|
||||
|
||||
Kernel::IAlgorithmProxy* m_classifier = nullptr;
|
||||
uint64_t m_trainStimulation = 0;
|
||||
size_t m_nPartition = 0;
|
||||
|
||||
Toolkit::TStimulationDecoder<CBoxAlgorithmClassifierTrainer> m_stimDecoder;
|
||||
std::vector<Toolkit::TFeatureVectorDecoder<CBoxAlgorithmClassifierTrainer>*> m_sampleDecoder;
|
||||
|
||||
Toolkit::TStimulationEncoder<CBoxAlgorithmClassifierTrainer> m_encoder;
|
||||
|
||||
std::map<CString, CString>* m_parameter = nullptr;
|
||||
|
||||
std::vector<sample_t> m_datasets;
|
||||
std::vector<sample_t> m_balancedDatasets;
|
||||
};
|
||||
|
||||
class CBoxAlgorithmClassifierTrainerDesc final : virtual public IBoxAlgorithmDesc
|
||||
{
|
||||
public:
|
||||
void release() override { }
|
||||
CString getName() const override { return CString("Classifier trainer"); }
|
||||
CString getAuthorName() const override { return CString("Yann Renard, Guillaume Serriere"); }
|
||||
CString getAuthorCompanyName() const override { return CString("INRIA/IRISA"); }
|
||||
|
||||
CString getShortDescription() const override { return CString("Generic classifier trainer, relying on several box algorithms"); }
|
||||
|
||||
CString getDetailedDescription() const override { return CString("Performs classifier training with cross-validation -based error estimation"); }
|
||||
|
||||
CString getCategory() const override { return CString("Classification"); }
|
||||
CString getVersion() const override { return CString("2.0"); }
|
||||
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
|
||||
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CString getUpdatedSoftwareVersion() const override { return CString("0.1.0"); }
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_ClassifierTrainer; }
|
||||
IPluginObject* create() override { return new CBoxAlgorithmClassifierTrainer; }
|
||||
|
||||
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
|
||||
{
|
||||
prototype.addInput("Stimulations", OV_TypeId_Stimulations);
|
||||
prototype.addInput("Features for class 1", OV_TypeId_FeatureVector);
|
||||
prototype.addInput("Features for class 2", OV_TypeId_FeatureVector);
|
||||
|
||||
prototype.addOutput("Train-completed Flag", OV_TypeId_Stimulations);
|
||||
|
||||
prototype.addSetting(TRAIN_TRIGGER_SETTING_NAME, OV_TypeId_Stimulation, "OVTK_StimulationId_Train");
|
||||
prototype.addSetting(FILENAME_SETTING_NAME, OV_TypeId_Filename, "${Path_UserData}/my-classifier.xml");
|
||||
|
||||
prototype.addSetting(MULTICLASS_STRATEGY_SETTING_NAME, OVTK_TypeId_ClassificationStrategy, "Native");
|
||||
//Pairing startegy argument
|
||||
//Class label
|
||||
|
||||
prototype.addSetting(ALGORITHM_SETTING_NAME, OVTK_TypeId_ClassificationAlgorithm, "Linear Discrimimant Analysis (LDA)");
|
||||
//Argument of algorithm
|
||||
|
||||
prototype.addSetting(FOLD_SETTING_NAME, OV_TypeId_Integer, "10");
|
||||
prototype.addSetting(BALANCE_SETTING_NAME, OV_TypeId_Boolean, "false");
|
||||
|
||||
prototype.addFlag(Kernel::BoxFlag_CanAddInput);
|
||||
|
||||
// prototype.addFlag(Kernel::BoxFlag_ManualUpdate);
|
||||
return true;
|
||||
}
|
||||
|
||||
IBoxListener* createBoxListener() const override
|
||||
{
|
||||
const size_t nCommonSetting = 6;
|
||||
return new CBoxAlgorithmCommonClassifierListener(nCommonSetting);
|
||||
}
|
||||
|
||||
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
|
||||
|
||||
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_ClassifierTrainerDesc)
|
||||
};
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,375 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
//#define OV_DEBUG_CLASSIFIER_LISTENER
|
||||
|
||||
#ifdef OV_DEBUG_CLASSIFIER_LISTENER
|
||||
#define DEBUG_PRINT(x) x
|
||||
#else
|
||||
#define DEBUG_PRINT(x)
|
||||
#endif
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
class CBoxAlgorithmCommonClassifierListener final : public Toolkit::TBoxListener<IBoxListener>
|
||||
{
|
||||
public:
|
||||
|
||||
explicit CBoxAlgorithmCommonClassifierListener(const size_t customSettingBase) : m_customSettingBase(customSettingBase) { }
|
||||
|
||||
bool initialize() override
|
||||
{
|
||||
//Even if everything should have been set in constructor, we still set everything in initialize (in case of)
|
||||
m_classifierClassID = CIdentifier::undefined();
|
||||
m_classifier = nullptr;
|
||||
|
||||
//CIdentifier::undefined() is already use for the native, We initialize to an unused identifier in the strategy list
|
||||
m_strategyClassID = 0x0;
|
||||
m_strategy = nullptr;
|
||||
|
||||
//This value means that we need to calculate it
|
||||
m_strategyAmountSettings = -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool uninitialize() override
|
||||
{
|
||||
if (m_classifier)
|
||||
{
|
||||
m_classifier->uninitialize();
|
||||
this->getAlgorithmManager().releaseAlgorithm(*m_classifier);
|
||||
m_classifier = nullptr;
|
||||
}
|
||||
if (m_strategy)
|
||||
{
|
||||
m_strategy->uninitialize();
|
||||
this->getAlgorithmManager().releaseAlgorithm(*m_strategy);
|
||||
m_strategy = nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool initializedStrategy(Kernel::IBox& box)
|
||||
{
|
||||
CString name;
|
||||
box.getSettingName(getStrategyIndex() + 1, name);
|
||||
if (name == CString(PAIRWISE_STRATEGY_ENUMERATION_NAME)) { m_strategyAmountSettings = 1; }
|
||||
else { m_strategyAmountSettings = 0; }
|
||||
return true;
|
||||
}
|
||||
|
||||
//virtual bool onAlgorithmClassIdentifierChanged(Kernel::IBox &box)
|
||||
//{
|
||||
//this->initializedStrategy(box);
|
||||
//return true;
|
||||
//}
|
||||
|
||||
int getStrategySettingsCount(Kernel::IBox& box)
|
||||
{
|
||||
if (m_strategyAmountSettings < 0) { initializedStrategy(box); } //The value have never been initialized
|
||||
return m_strategyAmountSettings;
|
||||
}
|
||||
|
||||
static bool onInputAddedOrRemoved(Kernel::IBox& box)
|
||||
{
|
||||
box.setInputType(0, OV_TypeId_Stimulations);
|
||||
box.setInputName(0, "Stimulations");
|
||||
for (size_t i = 1; i < box.getInputCount(); ++i)
|
||||
{
|
||||
box.setInputName(i, ("Features for class " + std::to_string(i)).c_str());
|
||||
box.setInputType(i, OV_TypeId_FeatureVector);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool onInputAdded(Kernel::IBox& box, const size_t index) override
|
||||
{
|
||||
//index represent the number of the class (because of rejected offset)
|
||||
const std::string name = "Class " + std::to_string(index) + " label";
|
||||
std::stringstream stim;
|
||||
stim.fill('0');
|
||||
stim << "OVTK_StimulationId_Label_" << std::setw(2) << index;
|
||||
box.addSetting(name.c_str(), OV_TypeId_Stimulation, stim.str().c_str(), 3 - 1 + getStrategySettingsCount(box) + index);
|
||||
|
||||
//Rename input
|
||||
return onInputAddedOrRemoved(box);
|
||||
}
|
||||
|
||||
bool onInputRemoved(Kernel::IBox& box, const size_t index) override
|
||||
{
|
||||
//First remove the removed input from settings
|
||||
box.removeSetting(3 - 1 + getStrategySettingsCount(box) + index);
|
||||
|
||||
//Then rename the remains inputs in settings
|
||||
for (size_t i = 1; i < box.getInputCount(); ++i)
|
||||
{
|
||||
const std::string name = "Class " + std::to_string(i) + " label";
|
||||
box.setSettingName(3 - 1 + getStrategySettingsCount(box) + i, name.c_str());
|
||||
}
|
||||
|
||||
//Then rename input
|
||||
return onInputAddedOrRemoved(box);
|
||||
}
|
||||
|
||||
bool onInitialized(Kernel::IBox& box) override
|
||||
{
|
||||
//We need to know if the box is already initialized (can be called after a restore state)
|
||||
CString strategyName;
|
||||
box.getSettingName(getStrategyIndex() + 2, strategyName);//this one is a class label
|
||||
const std::string settingName(strategyName.toASCIIString());
|
||||
|
||||
if (settingName.find("Class ") == std::string::npos)//We haven't initialized the box so let's do it
|
||||
{
|
||||
//Now added Settings for classes
|
||||
for (size_t i = 1; i < box.getInputCount(); ++i)
|
||||
{
|
||||
const std::string name = "Class " + std::to_string(i) + " label";
|
||||
std::stringstream stim;
|
||||
stim.fill('0');
|
||||
stim << "OVTK_StimulationId_Label_" << std::setw(2) << i;
|
||||
box.addSetting(name.c_str(), OV_TypeId_Stimulation, stim.str().c_str(), 3 - 1 + getStrategySettingsCount(box) + i);
|
||||
DEBUG_PRINT(std::cout << "Add setting (type D) " << buffer << " " << stimulation << "\n";)
|
||||
}
|
||||
return this->onAlgorithmClassifierChanged(box);
|
||||
}
|
||||
return true;
|
||||
//return this->onAlgorithmClassifierChanged(box);
|
||||
}
|
||||
|
||||
//Return the index of the combo box used to select the strategy (native/ OnevsOne...)
|
||||
static size_t getStrategyIndex() { return 2; }
|
||||
|
||||
//Return the index of the combo box used to select the classification algorithm
|
||||
size_t getClassifierIndex(Kernel::IBox& box) { return getStrategySettingsCount(box) + 3 + box.getInputCount() - 1; }
|
||||
|
||||
bool onSettingValueChanged(Kernel::IBox& box, const size_t index) override
|
||||
{
|
||||
if (index == getClassifierIndex(box)) { return this->onAlgorithmClassifierChanged(box); }
|
||||
if (index == getStrategyIndex()) { return this->onStrategyChanged(box); }
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool updateDecision(Kernel::IBox& box)
|
||||
{
|
||||
const size_t i = getStrategyIndex() + 1;
|
||||
if (m_strategyClassID == OVP_ClassId_Algorithm_ClassifierOneVsOne)
|
||||
{
|
||||
CString classifierName = "Unknown";
|
||||
box.getSettingValue(getClassifierIndex(box), classifierName);
|
||||
const CIdentifier typeID = this->getTypeManager().getEnumerationEntryValueFromName(
|
||||
OVP_TypeId_OneVsOne_DecisionAlgorithms, classifierName);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(typeID != CIdentifier::undefined(),
|
||||
"Unable to find Pairwise Decision for the algorithm [" << m_classifierClassID.str() << "] (" << classifierName << ")",
|
||||
Kernel::ErrorType::BadConfig);
|
||||
|
||||
Kernel::IParameter* param = m_strategy->getInputParameter(OVP_Algorithm_OneVsOneStrategy_InputParameterId_DecisionType);
|
||||
Kernel::TParameterHandler<uint64_t> ip_parameter(param);
|
||||
|
||||
const CString entry = this->getTypeManager().getTypeName(typeID);
|
||||
uint64_t value = ip_parameter;
|
||||
uint64_t idx;
|
||||
CString name;
|
||||
|
||||
box.getSettingValue(i, name);
|
||||
|
||||
const uint64_t oldID = this->getTypeManager().getEnumerationEntryValueFromName(typeID, name);
|
||||
//The previous strategy does not exists in the new enum, let's switch to the default value (the first)
|
||||
if (oldID == CIdentifier::undefined().id()) { idx = 0; }
|
||||
else { idx = oldID; }
|
||||
|
||||
this->getTypeManager().getEnumerationEntry(typeID, idx, name, value);
|
||||
ip_parameter = value;
|
||||
|
||||
box.setSettingType(i, typeID);
|
||||
box.setSettingName(i, entry);
|
||||
box.setSettingValue(i, name);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool onStrategyChanged(Kernel::IBox& box)
|
||||
{
|
||||
CString name;
|
||||
|
||||
box.getSettingValue(getStrategyIndex(), name);
|
||||
|
||||
const CIdentifier id = this->getTypeManager().getEnumerationEntryValueFromName(
|
||||
OVTK_TypeId_ClassificationStrategy, name);
|
||||
if (id != m_strategyClassID)
|
||||
{
|
||||
if (m_strategy)
|
||||
{
|
||||
m_strategy->uninitialize();
|
||||
this->getAlgorithmManager().releaseAlgorithm(*m_strategy);
|
||||
m_strategy = nullptr;
|
||||
m_strategyClassID = CIdentifier::undefined();
|
||||
}
|
||||
if (id != CIdentifier::undefined())
|
||||
{
|
||||
m_strategy = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(id));
|
||||
m_strategy->initialize();
|
||||
m_strategyClassID = id;
|
||||
}
|
||||
|
||||
for (size_t i = getStrategyIndex() + getStrategySettingsCount(box); i > getStrategyIndex(); --i)
|
||||
{
|
||||
DEBUG_PRINT(std::cout << "Remove pairing strategy setting at idx " << i-1 << "\n";)
|
||||
box.removeSetting(i);
|
||||
}
|
||||
m_strategyAmountSettings = 0;
|
||||
}
|
||||
else { return true; } //If we don't change the strategy we just have to return
|
||||
|
||||
if (m_strategy)
|
||||
{
|
||||
box.getSettingValue(getClassifierIndex(box), name);
|
||||
const size_t i = getStrategyIndex() + 1;
|
||||
if (m_strategyClassID == OVP_ClassId_Algorithm_ClassifierOneVsOne)
|
||||
{
|
||||
const CIdentifier typeID = this->getTypeManager().getEnumerationEntryValueFromName(
|
||||
OVP_TypeId_OneVsOne_DecisionAlgorithms, name);
|
||||
OV_ERROR_UNLESS_KRF(typeID != CIdentifier::undefined(),
|
||||
"Unable to find Pairwise Decision for the algorithm [" << m_classifierClassID.str() << "]",
|
||||
Kernel::ErrorType::BadConfig);
|
||||
|
||||
//As we just switch to this strategy, we take the default value set in the strategy to initialize the value
|
||||
Kernel::IParameter* param = m_strategy->getInputParameter(OVP_Algorithm_OneVsOneStrategy_InputParameterId_DecisionType);
|
||||
const Kernel::TParameterHandler<uint64_t> ip_param(param);
|
||||
const uint64_t value = ip_param;
|
||||
name = this->getTypeManager().getEnumerationEntryNameFromValue(typeID, value);
|
||||
|
||||
const CString paramName = this->getTypeManager().getTypeName(typeID);
|
||||
|
||||
DEBUG_PRINT(std::cout << "Adding setting (case C) " << paramName << " : '" << name << "' to index " << i << "\n";)
|
||||
box.addSetting(paramName, typeID, name, i);
|
||||
|
||||
m_strategyAmountSettings = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool onAlgorithmClassifierChanged(Kernel::IBox& box)
|
||||
{
|
||||
CString name;
|
||||
box.getSettingValue(getClassifierIndex(box), name);
|
||||
CIdentifier id = this->getTypeManager().getEnumerationEntryValueFromName(OVTK_TypeId_ClassificationAlgorithm, name);
|
||||
if (id != m_classifierClassID)
|
||||
{
|
||||
if (m_classifier)
|
||||
{
|
||||
m_classifier->uninitialize();
|
||||
this->getAlgorithmManager().releaseAlgorithm(*m_classifier);
|
||||
m_classifier = nullptr;
|
||||
m_classifierClassID = CIdentifier::undefined();
|
||||
}
|
||||
if (id != CIdentifier::undefined())
|
||||
{
|
||||
m_classifier = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(id));
|
||||
m_classifier->initialize();
|
||||
m_classifierClassID = id;
|
||||
}
|
||||
|
||||
//Disable the graphical refresh to avoid abusive redraw (not really a problem)
|
||||
while (box.getSettingCount() >= m_customSettingBase + box.getInputCount() + getStrategySettingsCount(box))
|
||||
{
|
||||
box.removeSetting(getClassifierIndex(box) + 1);
|
||||
}
|
||||
}
|
||||
else { return true; }//If we don't change the algorithm we just have to return
|
||||
|
||||
if (m_classifier)
|
||||
{
|
||||
size_t i = getClassifierIndex(box) + 1;
|
||||
while ((id = m_classifier->getNextInputParameterIdentifier(id)) != CIdentifier::undefined())
|
||||
{
|
||||
if ((id != OVTK_Algorithm_Classifier_InputParameterId_FeatureVector)
|
||||
&& (id != OVTK_Algorithm_Classifier_InputParameterId_FeatureVectorSet)
|
||||
&& (id != OVTK_Algorithm_Classifier_InputParameterId_Config)
|
||||
&& (id != OVTK_Algorithm_Classifier_InputParameterId_NClasses)
|
||||
&& (id != OVTK_Algorithm_Classifier_InputParameterId_ExtraParameter))
|
||||
{
|
||||
CIdentifier typeID;
|
||||
CString paramName = m_classifier->getInputParameterName(id);
|
||||
Kernel::IParameter* param = m_classifier->getInputParameter(id);
|
||||
Kernel::TParameterHandler<int64_t> ip_iParameter(param);
|
||||
Kernel::TParameterHandler<uint64_t> ip_uiParameter(param);
|
||||
Kernel::TParameterHandler<double> ip_dParameter(param);
|
||||
Kernel::TParameterHandler<bool> ip_bParameter(param);
|
||||
Kernel::TParameterHandler<CString*> ip_sParameter(param);
|
||||
std::string buffer;
|
||||
bool valid = true;
|
||||
switch (param->getType())
|
||||
{
|
||||
case Kernel::ParameterType_Enumeration:
|
||||
buffer = this->getTypeManager().getEnumerationEntryNameFromValue(param->getSubTypeIdentifier(), ip_uiParameter).toASCIIString();
|
||||
typeID = param->getSubTypeIdentifier();
|
||||
break;
|
||||
|
||||
case Kernel::ParameterType_Integer:
|
||||
case Kernel::ParameterType_UInteger:
|
||||
buffer = std::to_string(int64_t(ip_iParameter));
|
||||
typeID = OV_TypeId_Integer;
|
||||
break;
|
||||
|
||||
case Kernel::ParameterType_Boolean:
|
||||
buffer = (bool(ip_bParameter)) ? "true" : "false";
|
||||
typeID = OV_TypeId_Boolean;
|
||||
break;
|
||||
|
||||
case Kernel::ParameterType_Float:
|
||||
buffer = std::to_string(double(ip_dParameter));
|
||||
typeID = OV_TypeId_Float;
|
||||
break;
|
||||
case Kernel::ParameterType_String:
|
||||
buffer = static_cast<CString*>(ip_sParameter)->toASCIIString();
|
||||
typeID = OV_TypeId_String;
|
||||
break;
|
||||
default:
|
||||
std::cout << "Invalid parameter type " << param->getType() << "\n";
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (valid)
|
||||
{
|
||||
// @FIXME argh, the -2 is a hard coding that the classifier trainer has 2 settings after the classifier setting... ouch
|
||||
DEBUG_PRINT(std::cout << "Adding setting (case A) " << paramName << " : " << buffer << " to slot "
|
||||
<< box.getSettingCount() - 2 << "\n";)
|
||||
box.addSetting(paramName, typeID, buffer.c_str(), box.getSettingCount() - 2);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// This changes the pairwise strategy decision voting type of the box settings allowing
|
||||
// designer to list the correct choices in the combo box.
|
||||
updateDecision(box);
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
|
||||
|
||||
protected:
|
||||
|
||||
CIdentifier m_classifierClassID = CIdentifier::undefined();
|
||||
CIdentifier m_strategyClassID =
|
||||
0x0; // CIdentifier::undefined() is already use, We initialize to an unused identifier in the strategy list
|
||||
Kernel::IAlgorithmProxy* m_classifier = nullptr;
|
||||
Kernel::IAlgorithmProxy* m_strategy = nullptr;
|
||||
const size_t m_customSettingBase = 0;
|
||||
int m_strategyAmountSettings = -1;
|
||||
};
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,198 @@
|
||||
#include "ovpCBoxAlgorithmVotingClassifier.h"
|
||||
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
|
||||
bool CBoxAlgorithmVotingClassifier::initialize()
|
||||
{
|
||||
const Kernel::IBox& boxContext = this->getStaticBoxContext();
|
||||
|
||||
m_classificationChoiceEncoder.initialize(*this, 0);
|
||||
|
||||
CIdentifier typeID;
|
||||
boxContext.getInputType(0, typeID);
|
||||
m_matrixBased = (typeID == OV_TypeId_StreamedMatrix);
|
||||
|
||||
for (size_t i = 0; i < boxContext.getInputCount(); ++i)
|
||||
{
|
||||
input_t& input = m_results[i];
|
||||
if (m_matrixBased)
|
||||
{
|
||||
auto* decoder = new Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmVotingClassifier>();
|
||||
decoder->initialize(*this, i);
|
||||
input.decoder = decoder;
|
||||
input.op_matrix = decoder->getOutputMatrix();
|
||||
input.twoValueInput = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto* decoder = new Toolkit::TStimulationDecoder<CBoxAlgorithmVotingClassifier>();
|
||||
decoder->initialize(*this, i);
|
||||
input.decoder = decoder;
|
||||
input.op_stimSet = decoder->getOutputStimulationSet();
|
||||
input.twoValueInput = false;
|
||||
}
|
||||
}
|
||||
|
||||
m_nRepetitions = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
|
||||
m_targetClassLabel = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1);
|
||||
m_nonTargetClassLabel = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2);
|
||||
m_rejectClassLabel = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 3);
|
||||
m_resultClassLabelBase = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 4);
|
||||
m_chooseOneIfExAequo = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 5);
|
||||
|
||||
m_lastTime = 0;
|
||||
|
||||
m_classificationChoiceEncoder.encodeHeader();
|
||||
this->getDynamicBoxContext().markOutputAsReadyToSend(0, m_lastTime, this->getPlayerContext().getCurrentTime());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmVotingClassifier::uninitialize()
|
||||
{
|
||||
const size_t nInput = this->getStaticBoxContext().getInputCount();
|
||||
|
||||
for (size_t i = 0; i < nInput; ++i)
|
||||
{
|
||||
input_t& input = m_results[i];
|
||||
input.decoder->uninitialize();
|
||||
delete input.decoder;
|
||||
}
|
||||
|
||||
m_classificationChoiceEncoder.uninitialize();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmVotingClassifier::processInput(const size_t /*index*/)
|
||||
{
|
||||
this->getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmVotingClassifier::process()
|
||||
{
|
||||
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
|
||||
const size_t nInput = this->getStaticBoxContext().getInputCount();
|
||||
|
||||
bool canChoose = true;
|
||||
|
||||
for (size_t i = 0; i < nInput; ++i)
|
||||
{
|
||||
input_t& input = m_results[i];
|
||||
for (size_t j = 0; j < boxContext.getInputChunkCount(i); ++j)
|
||||
{
|
||||
input.decoder->decode(j);
|
||||
|
||||
if (input.decoder->isHeaderReceived())
|
||||
{
|
||||
if (m_matrixBased)
|
||||
{
|
||||
if (input.op_matrix->getBufferElementCount() != 1)
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(input.op_matrix->getBufferElementCount() == 2,
|
||||
"Invalid input matrix with [" << input.op_matrix->getBufferElementCount() << "] (expected values must be 1 or 2)",
|
||||
Kernel::ErrorType::BadInput);
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Debug <<
|
||||
"Input got two dimensions, the value use for the vote will be the difference between the two values\n";
|
||||
input.twoValueInput = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (input.decoder->isBufferReceived())
|
||||
{
|
||||
if (m_matrixBased)
|
||||
{
|
||||
double value;
|
||||
if (input.twoValueInput) { value = input.op_matrix->getBuffer()[1] - input.op_matrix->getBuffer()[0]; }
|
||||
else { value = input.op_matrix->getBuffer()[0]; }
|
||||
input.scores.push_back(std::pair<double, uint64_t>(-value, boxContext.getInputChunkEndTime(i, j)));
|
||||
}
|
||||
else
|
||||
{
|
||||
for (size_t k = 0; k < input.op_stimSet->getStimulationCount(); ++k)
|
||||
{
|
||||
const uint64_t id = input.op_stimSet->getStimulationIdentifier(k);
|
||||
if (id == m_targetClassLabel || id == m_nonTargetClassLabel || id == m_rejectClassLabel)
|
||||
{
|
||||
input.scores.push_back(std::pair<double, uint64_t>(id == m_targetClassLabel ? 1 : 0, input.op_stimSet->getStimulationDate(k)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (input.decoder->isEndReceived())
|
||||
{
|
||||
m_classificationChoiceEncoder.encodeEnd();
|
||||
boxContext.markOutputAsReadyToSend(0, m_lastTime, this->getPlayerContext().getCurrentTime());
|
||||
}
|
||||
}
|
||||
|
||||
if (input.scores.size() < m_nRepetitions) { canChoose = false; }
|
||||
}
|
||||
|
||||
if (canChoose)
|
||||
{
|
||||
double score = -1E100;
|
||||
uint64_t classLabel = m_rejectClassLabel;
|
||||
uint64_t time = 0;
|
||||
|
||||
std::map<uint32_t, double> scores;
|
||||
for (size_t i = 0; i < nInput; ++i)
|
||||
{
|
||||
input_t& input = m_results[i];
|
||||
scores[i] = 0;
|
||||
for (size_t j = 0; j < m_nRepetitions; ++j) { scores[i] += input.scores[j].first; }
|
||||
|
||||
if (scores[i] > score)
|
||||
{
|
||||
score = scores[i];
|
||||
classLabel = m_resultClassLabelBase + i;
|
||||
time = input.scores[size_t(m_nRepetitions - 1)].second;
|
||||
}
|
||||
else if (scores[i] == score)
|
||||
{
|
||||
if (!m_chooseOneIfExAequo)
|
||||
{
|
||||
score = scores[i];
|
||||
classLabel = m_rejectClassLabel;
|
||||
time = input.scores[size_t(m_nRepetitions - 1)].second;
|
||||
}
|
||||
}
|
||||
|
||||
input.scores.erase(input.scores.begin(), input.scores.begin() + int(m_nRepetitions));
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Debug << "Input " << i << " got score " << scores[i] << "\n";
|
||||
}
|
||||
|
||||
if (classLabel != m_rejectClassLabel)
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Debug << "Chosen " << this->getTypeManager().getEnumerationEntryNameFromValue(OV_TypeId_Stimulation, classLabel)
|
||||
<< " with score " << score << "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Debug << "Chosen rejection "
|
||||
<< this->getTypeManager().getEnumerationEntryNameFromValue(OV_TypeId_Stimulation, classLabel) << "\n";
|
||||
}
|
||||
m_classificationChoiceEncoder.getInputStimulationSet()->clear();
|
||||
m_classificationChoiceEncoder.getInputStimulationSet()->appendStimulation(classLabel, time, 0);
|
||||
|
||||
m_classificationChoiceEncoder.encodeBuffer();
|
||||
boxContext.markOutputAsReadyToSend(0, m_lastTime, time);
|
||||
m_lastTime = time;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,135 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
class CBoxAlgorithmVotingClassifier final : public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
|
||||
{
|
||||
public:
|
||||
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_VotingClassifier)
|
||||
|
||||
protected:
|
||||
|
||||
size_t m_nRepetitions = 0;
|
||||
size_t m_targetClassLabel = 0;
|
||||
size_t m_nonTargetClassLabel = 0;
|
||||
size_t m_rejectClassLabel = 0;
|
||||
size_t m_resultClassLabelBase = 0;
|
||||
bool m_chooseOneIfExAequo = false;
|
||||
|
||||
private:
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Toolkit::TDecoder<CBoxAlgorithmVotingClassifier>* decoder = nullptr;
|
||||
Kernel::TParameterHandler<IStimulationSet*> op_stimSet;
|
||||
Kernel::TParameterHandler<CMatrix*> op_matrix;
|
||||
bool twoValueInput;
|
||||
std::vector<std::pair<double, uint64_t>> scores;
|
||||
} input_t;
|
||||
|
||||
std::map<uint32_t, input_t> m_results;
|
||||
|
||||
Toolkit::TStimulationEncoder<CBoxAlgorithmVotingClassifier> m_classificationChoiceEncoder;
|
||||
Kernel::TParameterHandler<const IStimulationSet*> ip_classificationChoiceStimSet;
|
||||
|
||||
uint64_t m_lastTime = 0;
|
||||
bool m_matrixBased = false;
|
||||
};
|
||||
|
||||
class CBoxAlgorithmVotingClassifierListener final : public Toolkit::TBoxListener<IBoxListener>
|
||||
{
|
||||
public:
|
||||
|
||||
CBoxAlgorithmVotingClassifierListener() : m_inputTypeID(OV_TypeId_Stimulations) { }
|
||||
|
||||
bool onInputTypeChanged(Kernel::IBox& box, const size_t index) override
|
||||
{
|
||||
CIdentifier id = CIdentifier::undefined();
|
||||
box.getInputType(index, id);
|
||||
if (id == OV_TypeId_Stimulations || id == OV_TypeId_StreamedMatrix)
|
||||
{
|
||||
m_inputTypeID = id;
|
||||
for (size_t i = 0; i < box.getInputCount(); ++i) { box.setInputType(i, m_inputTypeID); }
|
||||
}
|
||||
else { box.setInputType(index, m_inputTypeID); }
|
||||
return true;
|
||||
}
|
||||
|
||||
bool onInputAdded(Kernel::IBox& box, const size_t /*index*/) override
|
||||
{
|
||||
for (size_t i = 0; i < box.getInputCount(); ++i)
|
||||
{
|
||||
box.setInputType(i, m_inputTypeID);
|
||||
box.setInputName(i, ("Classification result " + std::to_string(i)).c_str());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
|
||||
|
||||
protected:
|
||||
|
||||
CIdentifier m_inputTypeID = CIdentifier::undefined();
|
||||
};
|
||||
|
||||
class CBoxAlgorithmVotingClassifierDesc final : public IBoxAlgorithmDesc
|
||||
{
|
||||
public:
|
||||
void release() override { }
|
||||
CString getName() const override { return CString("Voting Classifier"); }
|
||||
CString getAuthorName() const override { return CString("Yann Renard"); }
|
||||
CString getAuthorCompanyName() const override { return CString("INRIA"); }
|
||||
CString getShortDescription() const override { return CString("Majority voting classifier. Returns the chosen class."); }
|
||||
|
||||
CString getDetailedDescription() const override
|
||||
{
|
||||
return CString(
|
||||
"Each classifier used as input is assumed to have its own two-class output stream. Mainly designed for P300 scenario use.");
|
||||
}
|
||||
|
||||
CString getCategory() const override { return CString("Classification"); }
|
||||
CString getVersion() const override { return CString("1.0"); }
|
||||
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
|
||||
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_VotingClassifier; }
|
||||
IPluginObject* create() override { return new CBoxAlgorithmVotingClassifier; }
|
||||
|
||||
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
|
||||
{
|
||||
prototype.addInput("Classification result 1", OV_TypeId_Stimulations);
|
||||
prototype.addInput("Classification result 2", OV_TypeId_Stimulations);
|
||||
prototype.addOutput("Classification choice", OV_TypeId_Stimulations);
|
||||
prototype.addSetting("Number of repetitions", OV_TypeId_Integer, "12");
|
||||
prototype.addSetting("Target class label", OV_TypeId_Stimulation, "OVTK_StimulationId_Target");
|
||||
prototype.addSetting("Non target class label", OV_TypeId_Stimulation, "OVTK_StimulationId_NonTarget");
|
||||
prototype.addSetting("Reject class label", OV_TypeId_Stimulation, "OVTK_StimulationId_Label_00");
|
||||
prototype.addSetting("Result class label base", OV_TypeId_Stimulation, "OVTK_StimulationId_Label_01");
|
||||
prototype.addSetting("Choose one if ex-aequo", OV_TypeId_Boolean, "false");
|
||||
prototype.addFlag(Kernel::BoxFlag_CanAddInput);
|
||||
prototype.addFlag(Kernel::BoxFlag_CanModifyInput);
|
||||
return true;
|
||||
}
|
||||
|
||||
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmVotingClassifierListener; }
|
||||
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
|
||||
|
||||
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_VotingClassifierDesc)
|
||||
};
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#define OVP_Classification_BoxTrainerFormatVersion 4
|
||||
#define OVP_Classification_BoxTrainerFormatVersionRequired 4
|
||||
|
||||
// Global defines
|
||||
//---------------------------------------------------------------------------------------------------
|
||||
#ifdef TARGET_HAS_ThirdPartyOpenViBEPluginsGlobalDefines
|
||||
#include "ovp_global_defines.h"
|
||||
#endif // TARGET_HAS_ThirdPartyOpenViBEPluginsGlobalDefines
|
||||
|
||||
#define OVP_TypeId_ClassificationPairwiseStrategy OpenViBE::CIdentifier(0x0DD51C74, 0x3C4E74C9)
|
||||
#define OVP_TypeId_OneVsOne_DecisionAlgorithms OpenViBE::CIdentifier(0xDEC1510, 0xDEC1510)
|
||||
|
||||
#define OVP_ClassId_Algorithm_ClassifierLDA OpenViBE::CIdentifier(0x2BA17A3C, 0x1BD46D84)
|
||||
#define OVP_ClassId_Algorithm_ClassifierLDA_DecisionAvailable OpenViBE::CIdentifier(0x79146976, 0xD7F01A25)
|
||||
#define OVP_ClassId_Algorithm_ClassifierLDADesc OpenViBE::CIdentifier(0x78FE2929, 0x644945B4)
|
||||
#define OVP_ClassId_Algorithm_ClassifierNULL OpenViBE::CIdentifier(0x043D09AB, 0xCB5E4859)
|
||||
#define OVP_ClassId_Algorithm_ClassifierNULLDesc OpenViBE::CIdentifier(0x3B365233, 0x812C47DD)
|
||||
#define OVP_ClassId_Algorithm_ClassifierOneVsOne OpenViBE::CIdentifier(0x638C2F90, 0xEAE10226)
|
||||
#define OVP_ClassId_Algorithm_ClassifierOneVsOneDesc OpenViBE::CIdentifier(0xE78E7CDB, 0x369AA9EF)
|
||||
#define OVP_ClassId_Algorithm_ClassifierOneVsAll OpenViBE::CIdentifier(0xD7183FC6, 0xBD74F297)
|
||||
#define OVP_ClassId_Algorithm_ClassifierOneVsAllDesc OpenViBE::CIdentifier(0xD42D5449, 0x7A28DDB0)
|
||||
#define OVP_ClassId_Algorithm_ConditionedCovariance OpenViBE::CIdentifier(0x0F3B77A6, 0x0301518A)
|
||||
#define OVP_ClassId_Algorithm_ConditionedCovarianceDesc OpenViBE::CIdentifier(0x18D15C41, 0x70545A66)
|
||||
#define OVP_ClassId_Algorithm_PairwiseDecision OpenViBE::CIdentifier(0x26EF6DDA, 0xF137053C)
|
||||
#define OVP_ClassId_Algorithm_PairwiseDecisionDesc OpenViBE::CIdentifier(0x191EB02A, 0x6866214A)
|
||||
#define OVP_ClassId_Algorithm_PairwiseDecision_HT OpenViBE::CIdentifier(0xD24F7F19, 0xA744FAD2)
|
||||
#define OVP_ClassId_Algorithm_PairwiseDecision_HTDesc OpenViBE::CIdentifier(0xE837F5C0, 0xF65C1341)
|
||||
#define OVP_ClassId_Algorithm_PairwiseDecision_Voting OpenViBE::CIdentifier(0xA111B830, 0x4679BAFD)
|
||||
#define OVP_ClassId_Algorithm_PairwiseDecision_VotingDesc OpenViBE::CIdentifier(0xAC5A39E8, 0x3A57822A)
|
||||
#define OVP_ClassId_Algorithm_PairwiseStrategy_PKPD OpenViBE::CIdentifier(0x26EF6DDA, 0xF137053C)
|
||||
#define OVP_ClassId_Algorithm_PairwiseStrategy_PKPDDesc OpenViBE::CIdentifier(0x191EB02A, 0x6866214A)
|
||||
#define OVP_ClassId_BoxAlgorithm_ClassifierProcessor OpenViBE::CIdentifier(0x5FE23D17, 0x95B0452C)
|
||||
#define OVP_ClassId_BoxAlgorithm_ClassifierProcessorDesc OpenViBE::CIdentifier(0x29B66B00, 0xB4683D49)
|
||||
#define OVP_ClassId_BoxAlgorithm_ClassifierTrainer OpenViBE::CIdentifier(0xF3DAE8A8, 0x3B444154)
|
||||
#define OVP_ClassId_BoxAlgorithm_ClassifierTrainerDesc OpenViBE::CIdentifier(0xFE277C91, 0x1593B824)
|
||||
#define OVP_ClassId_BoxAlgorithm_VotingClassifier OpenViBE::CIdentifier(0xFAF62C2B, 0x0B75D1B3)
|
||||
#define OVP_ClassId_BoxAlgorithm_VotingClassifierDesc OpenViBE::CIdentifier(0x97E3CCC5, 0xAC353ED2)
|
||||
|
||||
#define OVP_Algorithm_ClassifierLDA_InputParameterId_UseShrinkage OpenViBE::CIdentifier(0x01357534, 0x028312A0)
|
||||
#define OVP_Algorithm_ClassifierLDA_InputParameterId_Shrinkage OpenViBE::CIdentifier(0x01357534, 0x028312A1)
|
||||
#define OVP_Algorithm_ClassifierLDA_InputParameterId_DiagonalCov OpenViBE::CIdentifier(0x067E45C5, 0x15285CC7)
|
||||
#define OVP_Algorithm_ClassifierNULL_InputParameterId_Parameter1 OpenViBE::CIdentifier(0x6DA99952, 0x7E72C143)
|
||||
#define OVP_Algorithm_ClassifierNULL_InputParameterId_Parameter2 OpenViBE::CIdentifier(0xEAC5694A, 0x56CFEF02)
|
||||
#define OVP_Algorithm_ClassifierNULL_InputParameterId_Parameter3 OpenViBE::CIdentifier(0x72F6222D, 0x375BAE2C)
|
||||
#define OVP_Algorithm_OneVsOneStrategy_InputParameterId_DecisionType OpenViBE::CIdentifier(0x0C347BBA, 0x180577F9)
|
||||
#define OVP_Algorithm_ConditionedCovariance_InputParameterId_Shrinkage OpenViBE::CIdentifier(0x54B90EA7, 0x600A4ACC)
|
||||
#define OVP_Algorithm_ConditionedCovariance_InputParameterId_FeatureVectorSet OpenViBE::CIdentifier(0x2CF30E42, 0x051F3996)
|
||||
#define OVP_Algorithm_ConditionedCovariance_OutputParameterId_Mean OpenViBE::CIdentifier(0x0C671FB7, 0x550B01B3)
|
||||
#define OVP_Algorithm_ConditionedCovariance_OutputParameterId_CovarianceMatrix OpenViBE::CIdentifier(0x19F07FB4, 0x084E273B)
|
||||
#define OVP_Algorithm_Classifier_InputParameter_ProbabilityMatrix OpenViBE::CIdentifier(0xF48D35AD, 0xB8EFF834)
|
||||
#define OVP_Algorithm_Classifier_Pairwise_InputParameterId_Config OpenViBE::CIdentifier(0x10EBAC09, 0x80926A63)
|
||||
#define OVP_Algorithm_Classifier_Pairwise_InputParameterId_AlgorithmIdentifier OpenViBE::CIdentifier(0xBE71BE18, 0x82A0E017)
|
||||
#define OVP_Algorithm_Classifier_Pairwise_InputParameterId_SetRepartition OpenViBE::CIdentifier(0xBE71BE18, 0x82A0E018)
|
||||
#define OVP_Algorithm_Classifier_Pairwise_InputParameter_ClassificationOutputs OpenViBE::CIdentifier(0xBE71BE18, 0x82A0E019)
|
||||
#define OVP_Algorithm_Classifier_Pairwise_InputParameter_ClassCount OpenViBE::CIdentifier(0xBE71BE18, 0x82A0E01A)
|
||||
#define OVP_Algorithm_Classifier_OutputParameter_ProbabilityVector OpenViBE::CIdentifier(0x883599FE, 0x2FDB32FF)
|
||||
#define OVP_Algorithm_Classifier_Pairwise_OutputParameterId_Config OpenViBE::CIdentifier(0x69F05A61, 0x25C94515)
|
||||
#define OVP_Algorithm_Classifier_Pairwise_InputTriggerId_Train OpenViBE::CIdentifier(0x32219D21, 0xD3BE6105)
|
||||
#define OVP_Algorithm_Classifier_Pairwise_InputTriggerId_Parameterize OpenViBE::CIdentifier(0x32219D21, 0xD3BE6106)
|
||||
#define OVP_Algorithm_Classifier_Pairwise_InputTriggerId_Compute OpenViBE::CIdentifier(0x3637344B, 0x05D03D7E)
|
||||
#define OVP_Algorithm_Classifier_Pairwise_InputTriggerId_SaveConfig OpenViBE::CIdentifier(0xF19574AD, 0x024045A7)
|
||||
#define OVP_Algorithm_Classifier_Pairwise_InputTriggerId_LoadConfig OpenViBE::CIdentifier(0x97AF6C6C, 0x670A12E6)
|
||||
|
||||
extern const char* const FORMAT_VERSION_ATTRIBUTE_NAME;
|
||||
extern const char* const IDENTIFIER_ATTRIBUTE_NAME;
|
||||
|
||||
extern const char* const STRATEGY_NODE_NAME;
|
||||
extern const char* const ALGORITHM_NODE_NAME;
|
||||
extern const char* const STIMULATIONS_NODE_NAME;
|
||||
extern const char* const REJECTED_CLASS_NODE_NAME;
|
||||
extern const char* const CLASS_STIMULATION_NODE_NAME;
|
||||
|
||||
extern const char* const CLASSIFICATION_BOX_ROOT;
|
||||
extern const char* const CLASSIFIER_ROOT;
|
||||
|
||||
extern const char* const PAIRWISE_STRATEGY_ENUMERATION_NAME;
|
||||
|
||||
extern const char* const MLP_EVALUATION_FUNCTION_NAME;
|
||||
extern const char* const MLP_TRANSFERT_FUNCTION_NAME;
|
||||
|
||||
bool OVFloatEqual(double first, double second);
|
||||
@@ -0,0 +1,90 @@
|
||||
#include <vector>
|
||||
|
||||
#include "ovp_defines.h"
|
||||
#include "toolkit/algorithms/classification/ovtkCAlgorithmPairingStrategy.h" //For comparision mecanism
|
||||
|
||||
#include "algorithms/ovpCAlgorithmClassifierNULL.h"
|
||||
#include "algorithms/ovpCAlgorithmClassifierOneVsAll.h"
|
||||
#include "algorithms/ovpCAlgorithmClassifierOneVsOne.h"
|
||||
|
||||
#include "algorithms/ovpCAlgorithmPairwiseDecision.h"
|
||||
#include "algorithms/ovpCAlgorithmPairwiseStrategyPKPD.h"
|
||||
#include "algorithms/ovpCAlgorithmPairwiseDecisionVoting.h"
|
||||
#include "algorithms/ovpCAlgorithmPairwiseDecisionHT.h"
|
||||
|
||||
#include "box-algorithms/ovpCBoxAlgorithmVotingClassifier.h"
|
||||
#include "box-algorithms/ovpCBoxAlgorithmClassifierTrainer.h"
|
||||
#include "box-algorithms/ovpCBoxAlgorithmClassifierProcessor.h"
|
||||
|
||||
#if defined TARGET_HAS_ThirdPartyEIGEN
|
||||
#include "algorithms/ovpCAlgorithmConditionedCovariance.h"
|
||||
#include "algorithms/ovpCAlgorithmClassifierLDA.h"
|
||||
#endif // TARGET_HAS_ThirdPartyEIGEN
|
||||
|
||||
#include<cmath>
|
||||
|
||||
const char* const PAIRWISE_STRATEGY_ENUMERATION_NAME = "Pairwise Decision Strategy";
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
|
||||
|
||||
OVP_Declare_Begin()
|
||||
context.getTypeManager().registerEnumerationEntry(OVTK_TypeId_ClassificationStrategy, "Native", CIdentifier::undefined().id());
|
||||
context.getTypeManager().registerEnumerationEntry(OVTK_TypeId_ClassificationStrategy, "OneVsAll", OVP_ClassId_Algorithm_ClassifierOneVsAll.id());
|
||||
context.getTypeManager().registerEnumerationEntry(OVTK_TypeId_ClassificationStrategy, "OneVsOne", OVP_ClassId_Algorithm_ClassifierOneVsOne.id());
|
||||
|
||||
// context.getTypeManager().registerEnumerationEntry(OVTK_TypeId_ClassificationAlgorithm, "NULL Classifier (does nothing)",OVP_ClassId_Algorithm_ClassifierNULL.id());
|
||||
// OVP_Declare_New(CAlgorithmClassifierNULLDesc);
|
||||
|
||||
|
||||
OVP_Declare_New(CBoxAlgorithmVotingClassifierDesc);
|
||||
OVP_Declare_New(CBoxAlgorithmClassifierTrainerDesc);
|
||||
OVP_Declare_New(CBoxAlgorithmClassifierProcessorDesc);
|
||||
|
||||
OVP_Declare_New(CAlgorithmClassifierOneVsAllDesc);
|
||||
OVP_Declare_New(CAlgorithmClassifierOneVsOneDesc);
|
||||
|
||||
// Functions related to deciding winner in OneVsOne multiclass decision strategy
|
||||
context.getTypeManager().registerEnumerationType(OVP_TypeId_ClassificationPairwiseStrategy, PAIRWISE_STRATEGY_ENUMERATION_NAME);
|
||||
|
||||
OVP_Declare_New(CAlgorithmPairwiseStrategyPKPDDesc);
|
||||
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_ClassificationPairwiseStrategy, "PKPD", OVP_ClassId_Algorithm_PairwiseStrategy_PKPD.id());
|
||||
OVP_Declare_New(CAlgorithmPairwiseDecisionVotingDesc);
|
||||
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_ClassificationPairwiseStrategy, "Voting", OVP_ClassId_Algorithm_PairwiseDecision_Voting.id());
|
||||
OVP_Declare_New(CAlgorithmPairwiseDecisionHTDesc);
|
||||
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_ClassificationPairwiseStrategy, "HT", OVP_ClassId_Algorithm_PairwiseDecision_HT.id());
|
||||
|
||||
#if defined TARGET_HAS_ThirdPartyEIGEN
|
||||
OVP_Declare_New(CAlgorithmConditionedCovarianceDesc);
|
||||
|
||||
context.getTypeManager().registerEnumerationEntry(OVTK_TypeId_ClassificationAlgorithm, "Linear Discrimimant Analysis (LDA)",
|
||||
OVP_ClassId_Algorithm_ClassifierLDA.id());
|
||||
Toolkit::registerClassificationComparisonFunction(OVP_ClassId_Algorithm_ClassifierLDA, LDAClassificationCompare);
|
||||
OVP_Declare_New(CAlgorithmClassifierLDADesc);
|
||||
context.getTypeManager().registerEnumerationType(OVP_ClassId_Algorithm_ClassifierLDA_DecisionAvailable, PAIRWISE_STRATEGY_ENUMERATION_NAME);
|
||||
context.getTypeManager().registerEnumerationEntry(
|
||||
OVP_ClassId_Algorithm_ClassifierLDA_DecisionAvailable, "PKPD", OVP_ClassId_Algorithm_PairwiseStrategy_PKPD.id());
|
||||
context.getTypeManager().registerEnumerationEntry(
|
||||
OVP_ClassId_Algorithm_ClassifierLDA_DecisionAvailable, "Voting", OVP_ClassId_Algorithm_PairwiseDecision_Voting.id());
|
||||
context.getTypeManager().registerEnumerationEntry(
|
||||
OVP_ClassId_Algorithm_ClassifierLDA_DecisionAvailable, "HT", OVP_ClassId_Algorithm_PairwiseDecision_HT.id());
|
||||
|
||||
context.getTypeManager().registerEnumerationType(OVP_TypeId_OneVsOne_DecisionAlgorithms, "One vs One Decision Algorithms");
|
||||
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_OneVsOne_DecisionAlgorithms, "Linear Discrimimant Analysis (LDA)",
|
||||
OVP_ClassId_Algorithm_ClassifierLDA_DecisionAvailable.id());
|
||||
|
||||
#endif // TARGET_HAS_ThirdPartyEIGEN
|
||||
|
||||
OVP_Declare_End()
|
||||
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
|
||||
bool OVFloatEqual(const double first, const double second)
|
||||
{
|
||||
const double epsilon = 0.000001;
|
||||
return epsilon > fabs(first - second);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
PROJECT(openvibe-plugins-sdk-data-generation)
|
||||
|
||||
SET(PROJECT_VERSION_MAJOR ${OV_GLOBAL_VERSION_MAJOR})
|
||||
SET(PROJECT_VERSION_MINOR ${OV_GLOBAL_VERSION_MINOR})
|
||||
SET(PROJECT_VERSION_PATCH ${OV_GLOBAL_VERSION_PATCH})
|
||||
SET(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH})
|
||||
|
||||
FILE(GLOB_RECURSE SRC_FILES src/*.cpp src/*.h src/*.hpp src/*.inl)
|
||||
INCLUDE("FindSourceRCProperties")
|
||||
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("FindOpenViBEModuleXML")
|
||||
|
||||
# ---------------------------------
|
||||
# Target macros
|
||||
# Defines target operating system, architecture and 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/box-tutorials)
|
||||
@@ -0,0 +1,192 @@
|
||||
<OpenViBE-Scenario>
|
||||
<FormatVersion>2</FormatVersion>
|
||||
<Creator>OpenViBE Designer</Creator>
|
||||
<CreatorVersion>2.2.0</CreatorVersion>
|
||||
<Settings></Settings>
|
||||
<Inputs></Inputs>
|
||||
<Outputs></Outputs>
|
||||
<Boxes>
|
||||
<Box>
|
||||
<Identifier>(0x000015f6, 0x00002e14)</Identifier>
|
||||
<Name>Matrix Display</Name>
|
||||
<AlgorithmClassIdentifier>(0x54f0796d, 0x3ede2cc0)</AlgorithmClassIdentifier>
|
||||
<Inputs>
|
||||
<Input>
|
||||
<TypeIdentifier>(0x544a003e, 0x6dcba5f6)</TypeIdentifier>
|
||||
<Name>Matrix</Name>
|
||||
</Input>
|
||||
</Inputs>
|
||||
<Settings>
|
||||
<Setting>
|
||||
<TypeIdentifier>(0x3d3c7c7f, 0xef0e7129)</TypeIdentifier>
|
||||
<Name>Color gradient</Name>
|
||||
<DefaultValue>0:2,36,58; 50:100,100,100; 100:83,17,20</DefaultValue>
|
||||
<Value>0:100,100,100; 100:100,100,100</Value>
|
||||
<Modifiability>false</Modifiability>
|
||||
</Setting>
|
||||
<Setting>
|
||||
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
|
||||
<Name>Steps</Name>
|
||||
<DefaultValue>100</DefaultValue>
|
||||
<Value>100</Value>
|
||||
<Modifiability>false</Modifiability>
|
||||
</Setting>
|
||||
<Setting>
|
||||
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
|
||||
<Name>Symetric min/max</Name>
|
||||
<DefaultValue>false</DefaultValue>
|
||||
<Value>false</Value>
|
||||
<Modifiability>false</Modifiability>
|
||||
</Setting>
|
||||
<Setting>
|
||||
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
|
||||
<Name>Real time min/max</Name>
|
||||
<DefaultValue>false</DefaultValue>
|
||||
<Value>false</Value>
|
||||
<Modifiability>false</Modifiability>
|
||||
</Setting>
|
||||
</Settings>
|
||||
<Attributes>
|
||||
<Attribute>
|
||||
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
|
||||
<Value>0</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
|
||||
<Value>368</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
|
||||
<Value>(0x4ff49bdb, 0x9dcf6788)</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
|
||||
<Value>4</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
|
||||
<Value>1</Value>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Box>
|
||||
<Box>
|
||||
<Identifier>(0x5f3d3b70, 0x223fa5b7)</Identifier>
|
||||
<Name>Time signal</Name>
|
||||
<AlgorithmClassIdentifier>(0x28a5e7ff, 0x530095de)</AlgorithmClassIdentifier>
|
||||
<Outputs>
|
||||
<Output>
|
||||
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
|
||||
<Name>Generated signal</Name>
|
||||
</Output>
|
||||
</Outputs>
|
||||
<Settings>
|
||||
<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>8</Value>
|
||||
<Modifiability>false</Modifiability>
|
||||
</Setting>
|
||||
</Settings>
|
||||
<Attributes>
|
||||
<Attribute>
|
||||
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
|
||||
<Value>-80</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
|
||||
<Value>368</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
|
||||
<Value>(0x9e5ca01e, 0x30a4d8c3)</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0xc80ce8af, 0xf699f813)</Identifier>
|
||||
<Value>1</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
|
||||
<Value>2</Value>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Box>
|
||||
</Boxes>
|
||||
<Links>
|
||||
<Link>
|
||||
<Identifier>(0x00004fef, 0x00006a19)</Identifier>
|
||||
<Source>
|
||||
<BoxIdentifier>(0x5f3d3b70, 0x223fa5b7)</BoxIdentifier>
|
||||
<BoxOutputIndex>0</BoxOutputIndex>
|
||||
</Source>
|
||||
<Target>
|
||||
<BoxIdentifier>(0x000015f6, 0x00002e14)</BoxIdentifier>
|
||||
<BoxInputIndex>0</BoxInputIndex>
|
||||
</Target>
|
||||
</Link>
|
||||
</Links>
|
||||
<Comments>
|
||||
<Comment>
|
||||
<Identifier>(0x0000019a, 0x000047dc)</Identifier>
|
||||
<Text>You can browse each box' documentation by selecting the box and pressing <b>F1</b></Text>
|
||||
<Attributes>
|
||||
<Attribute>
|
||||
<Identifier>(0x473d9a43, 0x97fc0a97)</Identifier>
|
||||
<Value>704</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x7234b86b, 0x2b8651a5)</Identifier>
|
||||
<Value>-80</Value>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Comment>
|
||||
</Comments>
|
||||
<Metadata>
|
||||
<Entry>
|
||||
<Identifier>(0x05377d7e, 0x41751bb3)</Identifier>
|
||||
<Type>(0x3bcce5d2, 0x43f2d968)</Type>
|
||||
<Data>[{"boxIdentifier":"(0x000015f6, 0x00002e14)","childCount":0,"identifier":"(0x000055e3, 0x0000746e)","parentIdentifier":"(0xffffffff, 0xffffffff)","type":3}]</Data>
|
||||
</Entry>
|
||||
</Metadata>
|
||||
<Attributes>
|
||||
<Attribute>
|
||||
<Identifier>(0x4c90d4ad, 0x7a2554ec)</Identifier>
|
||||
<Value>320</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x790d75b8, 0x3bb90c33)</Identifier>
|
||||
<Value>Jussi T. Lindgren</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x7b814cca, 0x271df6dd)</Identifier>
|
||||
<Value>475</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x8c1fc55b, 0x7b433dc2)</Identifier>
|
||||
<Value>1.0</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x9f5c4075, 0x4a0d3666)</Identifier>
|
||||
<Value>Classification training example</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0xf36a1567, 0xd13c53da)</Identifier>
|
||||
<Value></Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0xf6b2e3fa, 0x7bd43926)</Identifier>
|
||||
<Value>box-tutorials</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0xf8034a49, 0x8b3f37cc)</Identifier>
|
||||
<Value>Inria</Value>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</OpenViBE-Scenario>
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* \page BoxAlgorithm_TimeSignal Time signal
|
||||
__________________________________________________________________
|
||||
|
||||
Detailed description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_TimeSignal_Description|
|
||||
* This box generates a linear signal on a single channel.
|
||||
* The box can be used in order to test some processings on a specific
|
||||
* signal when no data is available. Each sample value of the generated
|
||||
* signal exactly equals to time in second.
|
||||
* |OVP_DocEnd_BoxAlgorithm_TimeSignal_Description|
|
||||
__________________________________________________________________
|
||||
|
||||
Outputs description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_TimeSignal_Outputs|
|
||||
* |OVP_DocEnd_BoxAlgorithm_TimeSignal_Outputs|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_TimeSignal_Output1|
|
||||
* This output contains the generated signal.
|
||||
* |OVP_DocEnd_BoxAlgorithm_TimeSignal_Output1|
|
||||
__________________________________________________________________
|
||||
|
||||
Settings description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_TimeSignal_Settings|
|
||||
* |OVP_DocEnd_BoxAlgorithm_TimeSignal_Settings|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_TimeSignal_Setting1|
|
||||
* The first setting indicates the samling frequency of the produced signal in Hz.
|
||||
* |OVP_DocEnd_BoxAlgorithm_TimeSignal_Setting1|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_TimeSignal_Setting2|
|
||||
* The last setting indicates how many sample will be sent for each output chunk.
|
||||
* |OVP_DocEnd_BoxAlgorithm_TimeSignal_Setting2|
|
||||
__________________________________________________________________
|
||||
|
||||
Examples description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_TimeSignal_Examples|
|
||||
* In order to illustrate this case, just drag'n drop a \ref Doc_BoxAlgorithm_SignalDisplay
|
||||
* box on the scenario and connect its input to a sinus oscillator box. You will be able to
|
||||
* see the generated signal.
|
||||
*
|
||||
* For better use of this box, consider using an additional \ref Doc_BoxAlgorithm_SimpleDSP
|
||||
* box. Put whatever function you like in this box configuration and visualize it with the
|
||||
* \ref Doc_BoxAlgorithm_SignalDisplay.
|
||||
* |OVP_DocEnd_BoxAlgorithm_TimeSignal_Examples|
|
||||
__________________________________________________________________
|
||||
|
||||
Miscellaneous description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_TimeSignal_Miscellaneous|
|
||||
* |OVP_DocEnd_BoxAlgorithm_TimeSignal_Miscellaneous|
|
||||
*/
|
||||
@@ -0,0 +1,66 @@
|
||||
.. _Doc_BoxAlgorithm_TimeSignal:
|
||||
|
||||
Time signal
|
||||
===========
|
||||
|
||||
.. container:: attribution
|
||||
|
||||
:Author:
|
||||
Yann Renard
|
||||
:Company:
|
||||
INRIA/IRISA
|
||||
|
||||
.. image:: images/Doc_BoxAlgorithm_TimeSignal.png
|
||||
|
||||
This box generates a linear signal on a single channel.
|
||||
The box can be used in order to test some processings on a specific
|
||||
signal when no data is available. Each sample value of the generated
|
||||
signal exactly equals to time in second.
|
||||
|
||||
Outputs
|
||||
-------
|
||||
|
||||
.. csv-table::
|
||||
:header: "Output Name", "Stream Type"
|
||||
|
||||
"Generated signal", "Signal"
|
||||
|
||||
Generated signal
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
This output contains the generated signal.
|
||||
|
||||
.. _Doc_BoxAlgorithm_TimeSignal_Settings:
|
||||
|
||||
Settings
|
||||
--------
|
||||
|
||||
.. csv-table::
|
||||
:header: "Setting Name", "Type", "Default Value"
|
||||
|
||||
"Sampling frequency", "Integer", "512"
|
||||
"Generated epoch sample count", "Integer", "32"
|
||||
|
||||
Sampling frequency
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The first setting indicates the samling frequency of the produced signal in Hz.
|
||||
|
||||
Generated epoch sample count
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The last setting indicates how many sample will be sent for each output chunk.
|
||||
|
||||
.. _Doc_BoxAlgorithm_TimeSignal_Examples:
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
In order to illustrate this case, just drag'n drop a :ref:`Doc_BoxAlgorithm_ContinuousOscilloscope`
|
||||
box on the scenario and connect its input to a sinus oscillator box. You will be able to
|
||||
see the generated signal.
|
||||
|
||||
For better use of this box, consider using an additional :ref:`Doc_BoxAlgorithm_SimpleDSP`
|
||||
box. Put whatever function you like in this box configuration and visualize it with the
|
||||
:ref:`Doc_BoxAlgorithm_ContinuousOscilloscope`.
|
||||
|
||||
|
After Width: | Height: | Size: 832 B |
@@ -0,0 +1,78 @@
|
||||
#include "ovpCBoxAlgorithmTimeSignalGenerator.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace DataGeneration {
|
||||
|
||||
bool CBoxAlgorithmTimeSignalGenerator::initialize()
|
||||
{
|
||||
m_encoder.initialize(*this, 0);
|
||||
|
||||
// Parses box settings to try connecting to server
|
||||
m_sampling = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
|
||||
m_nGeneratedEpochSample = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1);
|
||||
m_headerSent = false;
|
||||
|
||||
m_nSentSample = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmTimeSignalGenerator::uninitialize()
|
||||
{
|
||||
m_encoder.uninitialize();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmTimeSignalGenerator::processClock(Kernel::CMessageClock& /*msg*/)
|
||||
{
|
||||
this->getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmTimeSignalGenerator::process()
|
||||
{
|
||||
Kernel::IBoxIO* boxContext = getBoxAlgorithmContext()->getDynamicBoxContext();
|
||||
|
||||
if (!m_headerSent)
|
||||
{
|
||||
m_encoder.getInputSamplingRate() = m_sampling;
|
||||
|
||||
CMatrix* matrix = m_encoder.getInputMatrix();
|
||||
|
||||
matrix->resize(1, m_nGeneratedEpochSample);
|
||||
matrix->setDimensionLabel(0, 0, "Time signal");
|
||||
|
||||
m_encoder.encodeHeader();
|
||||
|
||||
m_headerSent = true;
|
||||
|
||||
boxContext->markOutputAsReadyToSend(0, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create sample chunks up until the next step (current time + 1/128) but do not overshoot it
|
||||
// This way we will always create the correct number of samples for frequencies that are above 128Hz
|
||||
const uint64_t nextStepDate = CTime(uint64_t(this->getPlayerContext().getCurrentTime() + (1ULL << 25))).toSampleCount(m_sampling);
|
||||
while (m_nSentSample + m_nGeneratedEpochSample < nextStepDate)
|
||||
{
|
||||
double* buffer = m_encoder.getInputMatrix()->getBuffer();
|
||||
|
||||
for (size_t i = 0; i < m_nGeneratedEpochSample; ++i) { buffer[i] = (i + m_nSentSample) / double(m_sampling); }
|
||||
|
||||
m_encoder.encodeBuffer();
|
||||
|
||||
const uint64_t tStart = CTime(m_sampling, m_nSentSample).time();
|
||||
m_nSentSample += m_nGeneratedEpochSample;
|
||||
const uint64_t tEnd = CTime(m_sampling, m_nSentSample).time();
|
||||
|
||||
boxContext->markOutputAsReadyToSend(0, tStart, tEnd);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace DataGeneration
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace DataGeneration {
|
||||
class CBoxAlgorithmTimeSignalGenerator final : public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
|
||||
{
|
||||
public:
|
||||
|
||||
CBoxAlgorithmTimeSignalGenerator() {}
|
||||
void release() override { delete this; }
|
||||
|
||||
uint64_t getClockFrequency() override { return 128LL << 32; }
|
||||
|
||||
bool initialize() override;
|
||||
bool uninitialize() override;
|
||||
|
||||
bool processClock(Kernel::CMessageClock& msg) override;
|
||||
bool process() override;
|
||||
|
||||
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_TimeSignalGenerator)
|
||||
|
||||
protected:
|
||||
|
||||
Toolkit::TSignalEncoder<CBoxAlgorithmTimeSignalGenerator> m_encoder;
|
||||
|
||||
bool m_headerSent = false;
|
||||
size_t m_sampling = 0;
|
||||
size_t m_nGeneratedEpochSample = 0;
|
||||
size_t m_nSentSample = 0;
|
||||
};
|
||||
|
||||
class CBoxAlgorithmTimeSignalGeneratorDesc final : public IBoxAlgorithmDesc
|
||||
{
|
||||
public:
|
||||
|
||||
void release() override { }
|
||||
CString getName() const override { return CString("Time signal"); }
|
||||
CString getAuthorName() const override { return CString("Yann Renard"); }
|
||||
CString getAuthorCompanyName() const override { return CString("INRIA/IRISA"); }
|
||||
CString getShortDescription() const override { return CString("Simple time signal generator (for use with DSP)"); }
|
||||
CString getDetailedDescription() const override { return CString(""); }
|
||||
CString getCategory() const override { return CString("Data generation"); }
|
||||
CString getVersion() const override { return CString("1.1"); }
|
||||
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
|
||||
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CString getUpdatedSoftwareVersion() const override { return CString("0.4.0"); }
|
||||
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_TimeSignalGenerator; }
|
||||
IPluginObject* create() override { return new CBoxAlgorithmTimeSignalGenerator(); }
|
||||
|
||||
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
|
||||
{
|
||||
prototype.addOutput("Generated signal", OV_TypeId_Signal);
|
||||
|
||||
prototype.addSetting("Sampling frequency", OV_TypeId_Integer, "512");
|
||||
prototype.addSetting("Generated epoch sample count", OV_TypeId_Integer, "32");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_TimeSignalGeneratorDesc)
|
||||
};
|
||||
} // namespace DataGeneration
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
// Global defines
|
||||
//---------------------------------------------------------------------------------------------------
|
||||
#ifdef TARGET_HAS_ThirdPartyOpenViBEPluginsGlobalDefines
|
||||
#include "ovp_global_defines.h"
|
||||
#endif // TARGET_HAS_ThirdPartyOpenViBEPluginsGlobalDefines
|
||||
|
||||
|
||||
#define OVP_ClassId_BoxAlgorithm_TimeSignalGenerator OpenViBE::CIdentifier(0x28A5E7FF, 0x530095DE)
|
||||
#define OVP_ClassId_BoxAlgorithm_TimeSignalGeneratorDesc OpenViBE::CIdentifier(0x57AD8655, 0x1966B4DC)
|
||||
@@ -0,0 +1,7 @@
|
||||
#include "ovp_defines.h"
|
||||
|
||||
#include "box-algorithms/ovpCBoxAlgorithmTimeSignalGenerator.h"
|
||||
|
||||
OVP_Declare_Begin()
|
||||
OVP_Declare_New(OpenViBE::Plugins::DataGeneration::CBoxAlgorithmTimeSignalGeneratorDesc);
|
||||
OVP_Declare_End()
|
||||
@@ -0,0 +1,36 @@
|
||||
PROJECT(openvibe-plugins-sdk-feature-extraction)
|
||||
|
||||
SET(PROJECT_VERSION_MAJOR ${OV_GLOBAL_VERSION_MAJOR})
|
||||
SET(PROJECT_VERSION_MINOR ${OV_GLOBAL_VERSION_MINOR})
|
||||
SET(PROJECT_VERSION_PATCH ${OV_GLOBAL_VERSION_PATCH})
|
||||
SET(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH})
|
||||
|
||||
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")
|
||||
|
||||
# ---------------------------------
|
||||
# Target macros
|
||||
# Defines target operating system, architecture and 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/box-tutorials)
|
||||
@@ -0,0 +1,360 @@
|
||||
<OpenViBE-Scenario>
|
||||
<FormatVersion>2</FormatVersion>
|
||||
<Creator>OpenViBE Designer</Creator>
|
||||
<CreatorVersion>2.2.0</CreatorVersion>
|
||||
<Settings></Settings>
|
||||
<Inputs></Inputs>
|
||||
<Outputs></Outputs>
|
||||
<Boxes>
|
||||
<Box>
|
||||
<Identifier>(0x000001b3, 0x00001dd9)</Identifier>
|
||||
<Name>Feature aggregator</Name>
|
||||
<AlgorithmClassIdentifier>(0x00682417, 0x453635f9)</AlgorithmClassIdentifier>
|
||||
<Inputs>
|
||||
<Input>
|
||||
<TypeIdentifier>(0x544a003e, 0x6dcba5f6)</TypeIdentifier>
|
||||
<Name>Input stream 1</Name>
|
||||
</Input>
|
||||
<Input>
|
||||
<Identifier>(0x004294b2, 0xfe7e8a08)</Identifier>
|
||||
<TypeIdentifier>(0x544a003e, 0x6dcba5f6)</TypeIdentifier>
|
||||
<Name>Input stream 2</Name>
|
||||
</Input>
|
||||
</Inputs>
|
||||
<Outputs>
|
||||
<Output>
|
||||
<TypeIdentifier>(0x17341935, 0x152ff448)</TypeIdentifier>
|
||||
<Name>Feature vector stream</Name>
|
||||
</Output>
|
||||
</Outputs>
|
||||
<Attributes>
|
||||
<Attribute>
|
||||
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
|
||||
<Value>384</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
|
||||
<Value>736</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
|
||||
<Value>(0xb5d15cc9, 0x6c8c28fb)</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0xc46b3d00, 0x3e0454e1)</Identifier>
|
||||
<Value>(0x00000000, 0x004b1b63)</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0xc73e83ec, 0xf855c5bc)</Identifier>
|
||||
<Value>false</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0xc80ce8af, 0xf699f813)</Identifier>
|
||||
<Value>1</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
|
||||
<Value>1</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0xfba64161, 0x65304e21)</Identifier>
|
||||
<Value></Value>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Box>
|
||||
<Box>
|
||||
<Identifier>(0x00003622, 0x0000376c)</Identifier>
|
||||
<Name>Matrix Display</Name>
|
||||
<AlgorithmClassIdentifier>(0x54f0796d, 0x3ede2cc0)</AlgorithmClassIdentifier>
|
||||
<Inputs>
|
||||
<Input>
|
||||
<TypeIdentifier>(0x544a003e, 0x6dcba5f6)</TypeIdentifier>
|
||||
<Name>Matrix</Name>
|
||||
</Input>
|
||||
</Inputs>
|
||||
<Settings>
|
||||
<Setting>
|
||||
<TypeIdentifier>(0x3d3c7c7f, 0xef0e7129)</TypeIdentifier>
|
||||
<Name>Color gradient</Name>
|
||||
<DefaultValue>0:2,36,58; 50:100,100,100; 100:83,17,20</DefaultValue>
|
||||
<Value>0:2,36,58; 50:100,100,100; 100:83,17,20</Value>
|
||||
<Modifiability>false</Modifiability>
|
||||
</Setting>
|
||||
<Setting>
|
||||
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
|
||||
<Name>Steps</Name>
|
||||
<DefaultValue>100</DefaultValue>
|
||||
<Value>100</Value>
|
||||
<Modifiability>false</Modifiability>
|
||||
</Setting>
|
||||
<Setting>
|
||||
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
|
||||
<Name>Symetric min/max</Name>
|
||||
<DefaultValue>false</DefaultValue>
|
||||
<Value>false</Value>
|
||||
<Modifiability>false</Modifiability>
|
||||
</Setting>
|
||||
<Setting>
|
||||
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
|
||||
<Name>Real time min/max</Name>
|
||||
<DefaultValue>false</DefaultValue>
|
||||
<Value>false</Value>
|
||||
<Modifiability>false</Modifiability>
|
||||
</Setting>
|
||||
</Settings>
|
||||
<Attributes>
|
||||
<Attribute>
|
||||
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
|
||||
<Value>432</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
|
||||
<Value>736</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
|
||||
<Value>(0x4ff49bdb, 0x9dcf6788)</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
|
||||
<Value>4</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
|
||||
<Value>1</Value>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Box>
|
||||
<Box>
|
||||
<Identifier>(0x00003ad0, 0x00003967)</Identifier>
|
||||
<Name>Simple DSP</Name>
|
||||
<AlgorithmClassIdentifier>(0x00e26fa1, 0x1dbab1b2)</AlgorithmClassIdentifier>
|
||||
<Inputs>
|
||||
<Input>
|
||||
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
|
||||
<Name>Input - A</Name>
|
||||
</Input>
|
||||
</Inputs>
|
||||
<Outputs>
|
||||
<Output>
|
||||
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
|
||||
<Name>Output</Name>
|
||||
</Output>
|
||||
</Outputs>
|
||||
<Settings>
|
||||
<Setting>
|
||||
<TypeIdentifier>(0x79a9edeb, 0x245d83fc)</TypeIdentifier>
|
||||
<Name>Equation</Name>
|
||||
<DefaultValue>x</DefaultValue>
|
||||
<Value>x+10</Value>
|
||||
<Modifiability>false</Modifiability>
|
||||
</Setting>
|
||||
</Settings>
|
||||
<Attributes>
|
||||
<Attribute>
|
||||
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
|
||||
<Value>320</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
|
||||
<Value>784</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x30a4e5c9, 0x83502953)</Identifier>
|
||||
<Value></Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
|
||||
<Value>(0x21889dc4, 0x1126497e)</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x527ad68d, 0x16d746a0)</Identifier>
|
||||
<Value></Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0xc80ce8af, 0xf699f813)</Identifier>
|
||||
<Value>1</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
|
||||
<Value>1</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
|
||||
<Value>1</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0xfba64161, 0x65304e21)</Identifier>
|
||||
<Value></Value>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Box>
|
||||
<Box>
|
||||
<Identifier>(0x00003dc7, 0x0000468d)</Identifier>
|
||||
<Name>Time signal</Name>
|
||||
<AlgorithmClassIdentifier>(0x28a5e7ff, 0x530095de)</AlgorithmClassIdentifier>
|
||||
<Outputs>
|
||||
<Output>
|
||||
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
|
||||
<Name>Generated signal</Name>
|
||||
</Output>
|
||||
</Outputs>
|
||||
<Settings>
|
||||
<Setting>
|
||||
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
|
||||
<Name>Sampling frequency</Name>
|
||||
<DefaultValue>512</DefaultValue>
|
||||
<Value>256</Value>
|
||||
<Modifiability>false</Modifiability>
|
||||
</Setting>
|
||||
<Setting>
|
||||
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
|
||||
<Name>Generated epoch sample count</Name>
|
||||
<DefaultValue>32</DefaultValue>
|
||||
<Value>4</Value>
|
||||
<Modifiability>false</Modifiability>
|
||||
</Setting>
|
||||
</Settings>
|
||||
<Attributes>
|
||||
<Attribute>
|
||||
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
|
||||
<Value>272</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
|
||||
<Value>688</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
|
||||
<Value>(0x9e5ca01e, 0x30a4d8c3)</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0xc80ce8af, 0xf699f813)</Identifier>
|
||||
<Value>1</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
|
||||
<Value>2</Value>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Box>
|
||||
<Box>
|
||||
<Identifier>(0x00003dc7, 0x0000468e)</Identifier>
|
||||
<Name>Time signal</Name>
|
||||
<AlgorithmClassIdentifier>(0x28a5e7ff, 0x530095de)</AlgorithmClassIdentifier>
|
||||
<Outputs>
|
||||
<Output>
|
||||
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
|
||||
<Name>Generated signal</Name>
|
||||
</Output>
|
||||
</Outputs>
|
||||
<Settings>
|
||||
<Setting>
|
||||
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
|
||||
<Name>Sampling frequency</Name>
|
||||
<DefaultValue>512</DefaultValue>
|
||||
<Value>256</Value>
|
||||
<Modifiability>false</Modifiability>
|
||||
</Setting>
|
||||
<Setting>
|
||||
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
|
||||
<Name>Generated epoch sample count</Name>
|
||||
<DefaultValue>32</DefaultValue>
|
||||
<Value>4</Value>
|
||||
<Modifiability>false</Modifiability>
|
||||
</Setting>
|
||||
</Settings>
|
||||
<Attributes>
|
||||
<Attribute>
|
||||
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
|
||||
<Value>272</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
|
||||
<Value>784</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
|
||||
<Value>(0x9e5ca01e, 0x30a4d8c3)</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0xc80ce8af, 0xf699f813)</Identifier>
|
||||
<Value>1</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
|
||||
<Value>2</Value>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Box>
|
||||
</Boxes>
|
||||
<Links>
|
||||
<Link>
|
||||
<Identifier>(0x00000587, 0x00007793)</Identifier>
|
||||
<Source>
|
||||
<BoxIdentifier>(0x000001b3, 0x00001dd9)</BoxIdentifier>
|
||||
<BoxOutputIndex>0</BoxOutputIndex>
|
||||
</Source>
|
||||
<Target>
|
||||
<BoxIdentifier>(0x00003622, 0x0000376c)</BoxIdentifier>
|
||||
<BoxInputIndex>0</BoxInputIndex>
|
||||
</Target>
|
||||
</Link>
|
||||
<Link>
|
||||
<Identifier>(0x000016f7, 0x00005cd1)</Identifier>
|
||||
<Source>
|
||||
<BoxIdentifier>(0x00003ad0, 0x00003967)</BoxIdentifier>
|
||||
<BoxOutputIndex>0</BoxOutputIndex>
|
||||
</Source>
|
||||
<Target>
|
||||
<BoxIdentifier>(0x000001b3, 0x00001dd9)</BoxIdentifier>
|
||||
<BoxInputIdentifier>(0x004294b2, 0xfe7e8a08)</BoxInputIdentifier>
|
||||
</Target>
|
||||
</Link>
|
||||
<Link>
|
||||
<Identifier>(0x00004b52, 0x00001eef)</Identifier>
|
||||
<Source>
|
||||
<BoxIdentifier>(0x00003dc7, 0x0000468d)</BoxIdentifier>
|
||||
<BoxOutputIndex>0</BoxOutputIndex>
|
||||
</Source>
|
||||
<Target>
|
||||
<BoxIdentifier>(0x000001b3, 0x00001dd9)</BoxIdentifier>
|
||||
<BoxInputIndex>0</BoxInputIndex>
|
||||
</Target>
|
||||
</Link>
|
||||
<Link>
|
||||
<Identifier>(0x0000778e, 0x00002620)</Identifier>
|
||||
<Source>
|
||||
<BoxIdentifier>(0x00003dc7, 0x0000468e)</BoxIdentifier>
|
||||
<BoxOutputIndex>0</BoxOutputIndex>
|
||||
</Source>
|
||||
<Target>
|
||||
<BoxIdentifier>(0x00003ad0, 0x00003967)</BoxIdentifier>
|
||||
<BoxInputIndex>0</BoxInputIndex>
|
||||
</Target>
|
||||
</Link>
|
||||
</Links>
|
||||
<Comments>
|
||||
<Comment>
|
||||
<Identifier>(0x0000019a, 0x000047dc)</Identifier>
|
||||
<Text>You can browse each box' documentation by selecting the box and pressing <b>F1</b></Text>
|
||||
<Attributes>
|
||||
<Attribute>
|
||||
<Identifier>(0x473d9a43, 0x97fc0a97)</Identifier>
|
||||
<Value>736</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x7234b86b, 0x2b8651a5)</Identifier>
|
||||
<Value>160</Value>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Comment>
|
||||
</Comments>
|
||||
<Metadata>
|
||||
<Entry>
|
||||
<Identifier>(0x00004cc5, 0x0000007c)</Identifier>
|
||||
<Type>(0x3bcce5d2, 0x43f2d968)</Type>
|
||||
<Data>[{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":1,"height":320,"identifier":"(0x000050a5, 0x00005401)","name":"Default window","parentIdentifier":"(0xffffffff, 0xffffffff)","type":1,"width":480},{"boxIdentifier":"(0x00003622, 0x0000376c)","childCount":0,"identifier":"(0x00006730, 0x000037b5)","parentIdentifier":"(0xffffffff, 0xffffffff)","type":3},{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":1,"identifier":"(0x00006ebd, 0x00002449)","index":0,"name":"Default tab","parentIdentifier":"(0x000050a5, 0x00005401)","type":2},{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":0,"identifier":"(0x000055d0, 0x0000606d)","index":0,"name":"Empty","parentIdentifier":"(0x00006ebd, 0x00002449)","type":0}]</Data>
|
||||
</Entry>
|
||||
</Metadata>
|
||||
</OpenViBE-Scenario>
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* \page BoxAlgorithm_FeatureAggregator Feature aggregator
|
||||
__________________________________________________________________
|
||||
|
||||
Detailed description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_FeatureAggregator_Description|
|
||||
* This plugins aggregates the features it receives on its inputs
|
||||
* into a feature vector that can be used for classification.
|
||||
* |OVP_DocEnd_BoxAlgorithm_FeatureAggregator_Description|
|
||||
__________________________________________________________________
|
||||
|
||||
Inputs description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_FeatureAggregator_Inputs|
|
||||
* |OVP_DocEnd_BoxAlgorithm_FeatureAggregator_Inputs|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_FeatureAggregator_Input1|
|
||||
* A stream of matrices containing features.
|
||||
* |OVP_DocEnd_BoxAlgorithm_FeatureAggregator_Input1|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_FeatureAggregator_Input2|
|
||||
* A stream of matrices containing features.
|
||||
* |OVP_DocEnd_BoxAlgorithm_FeatureAggregator_Input2|
|
||||
__________________________________________________________________
|
||||
|
||||
Outputs description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_FeatureAggregator_Outputs|
|
||||
* |OVP_DocEnd_BoxAlgorithm_FeatureAggregator_Outputs|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_FeatureAggregator_Output1|
|
||||
* A stream of feature vectors made by aggregating incoming features.
|
||||
* |OVP_DocEnd_BoxAlgorithm_FeatureAggregator_Output1|
|
||||
__________________________________________________________________
|
||||
|
||||
Examples description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_FeatureAggregator_Examples|
|
||||
* |OVP_DocEnd_BoxAlgorithm_FeatureAggregator_Examples|
|
||||
__________________________________________________________________
|
||||
|
||||
Miscellaneous description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_FeatureAggregator_Miscellaneous|
|
||||
* |OVP_DocEnd_BoxAlgorithm_FeatureAggregator_Miscellaneous|
|
||||
*/
|
||||
@@ -0,0 +1,45 @@
|
||||
.. _Doc_BoxAlgorithm_FeatureAggregator:
|
||||
|
||||
Feature aggregator
|
||||
==================
|
||||
|
||||
.. container:: attribution
|
||||
|
||||
:Author:
|
||||
Bruno Renier
|
||||
:Company:
|
||||
INRIA/IRISA
|
||||
|
||||
.. image:: images/Doc_BoxAlgorithm_FeatureAggregator.png
|
||||
|
||||
Each chunk of input will be catenated into one feature vector.
|
||||
|
||||
This plugins aggregates the features it receives on its inputs
|
||||
into a feature vector that can be used for classification.
|
||||
|
||||
Inputs
|
||||
------
|
||||
|
||||
.. csv-table::
|
||||
:header: "Input Name", "Stream Type"
|
||||
|
||||
"Input stream 1", "Streamed matrix"
|
||||
|
||||
Input stream 1
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
A stream of matrices containing features.
|
||||
|
||||
Outputs
|
||||
-------
|
||||
|
||||
.. csv-table::
|
||||
:header: "Output Name", "Stream Type"
|
||||
|
||||
"Feature vector stream", "Feature vector"
|
||||
|
||||
Feature vector stream
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A stream of feature vectors made by aggregating incoming features.
|
||||
|
||||
|
After Width: | Height: | Size: 1017 B |
@@ -0,0 +1,154 @@
|
||||
#include "ovpCBoxAlgorithmFeatureAggregator.h"
|
||||
#include <iostream>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace FeatureExtraction {
|
||||
bool CBoxAlgorithmFeatureAggregator::initialize()
|
||||
{
|
||||
m_nInput = getBoxAlgorithmContext()->getStaticBoxContext()->getInputCount();
|
||||
|
||||
// Prepares decoders
|
||||
for (size_t i = 0; i < m_nInput; ++i)
|
||||
{
|
||||
Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmFeatureAggregator>* streamedMatrixDecoder = new Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmFeatureAggregator>();
|
||||
m_decoder.push_back(streamedMatrixDecoder);
|
||||
m_decoder.back()->initialize(*this, i);
|
||||
}
|
||||
m_encoder = new Toolkit::TFeatureVectorEncoder<CBoxAlgorithmFeatureAggregator>;
|
||||
m_encoder->initialize(*this, 0);
|
||||
|
||||
//resizes everything as needed
|
||||
m_iBufferSizes.resize(m_nInput);
|
||||
m_dimSize.resize(m_nInput);
|
||||
m_featureNames.resize(m_nInput);
|
||||
|
||||
m_headerSent = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmFeatureAggregator::uninitialize()
|
||||
{
|
||||
for (size_t i = 0; i < m_nInput; ++i)
|
||||
{
|
||||
if (m_decoder.back())
|
||||
{
|
||||
m_decoder.back()->uninitialize();
|
||||
delete m_decoder.back();
|
||||
m_decoder.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
if (m_encoder)
|
||||
{
|
||||
m_encoder->uninitialize();
|
||||
delete m_encoder;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmFeatureAggregator::processInput(const size_t index)
|
||||
{
|
||||
Kernel::IBoxIO* boxIO = getBoxAlgorithmContext()->getDynamicBoxContext();
|
||||
|
||||
size_t lastBufferChunkSize;
|
||||
const uint8_t* lastBuffer;
|
||||
|
||||
size_t bufferChunkSize;
|
||||
const uint8_t* buffer;
|
||||
|
||||
//gets the first buffer from the concerned input
|
||||
boxIO->getInputChunk(index, 0, m_lastChunkStartTime, m_lastChunkEndTime, lastBufferChunkSize, lastBuffer);
|
||||
|
||||
uint64_t tStart = 0, tEnd = 0;
|
||||
|
||||
bool readyToProcess = true;
|
||||
|
||||
//checks every input's first chunk's dates
|
||||
for (size_t i = 0; i < m_nInput && readyToProcess; ++i)
|
||||
{
|
||||
if (boxIO->getInputChunkCount(i) != 0)
|
||||
{
|
||||
boxIO->getInputChunk(i, 0, tStart, tEnd, bufferChunkSize, buffer);
|
||||
//if the first buffers don't have the same starting/ending dates, stop
|
||||
if (tStart != m_lastChunkStartTime || tEnd != m_lastChunkEndTime) { readyToProcess = false; }
|
||||
|
||||
//checks for problems, buffer lengths differents...
|
||||
if (tEnd - tStart != m_lastChunkEndTime - m_lastChunkStartTime)
|
||||
{
|
||||
//marks everything as deprecated and sends a error
|
||||
for (size_t input = 0; input < m_nInput; ++input)
|
||||
{
|
||||
for (size_t chunk = 0; chunk < boxIO->getInputChunkCount(input); ++chunk) { boxIO->markInputAsDeprecated(input, chunk); }
|
||||
}
|
||||
|
||||
//readyToProcess = false;
|
||||
OV_ERROR_KRF("Invalid incoming input chunks: duration differs between chunks", Kernel::ErrorType::BadInput);
|
||||
}
|
||||
}
|
||||
else { readyToProcess = false; }
|
||||
}
|
||||
|
||||
//If there is one buffer of the same time period per input, process
|
||||
if (readyToProcess) { getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess(); }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmFeatureAggregator::process()
|
||||
{
|
||||
const Kernel::IBox* boxContext = getBoxAlgorithmContext()->getStaticBoxContext();
|
||||
Kernel::IBoxIO* boxIO = getBoxAlgorithmContext()->getDynamicBoxContext();
|
||||
|
||||
CMatrix* oMatrix = m_encoder->getInputMatrix();
|
||||
std::vector<double> bufferElements;
|
||||
size_t totalBufferSize = 0;
|
||||
bool bufferReceived = false;
|
||||
|
||||
for (size_t input = 0; input < boxContext->getInputCount(); ++input)
|
||||
{
|
||||
m_decoder[input]->decode(0);
|
||||
//*
|
||||
if ((m_decoder[input]->isHeaderReceived()) && !m_headerSent)
|
||||
{
|
||||
//getBoxAlgorithmContext()->getPlayerContext()->getLogManager() << Kernel::LogLevel_Warning << "header " << input << "\n";
|
||||
CMatrix* iMatrix = m_decoder[input]->getOutputMatrix();
|
||||
totalBufferSize += iMatrix->getBufferElementCount();
|
||||
if (input == boxContext->getInputCount() - 1)
|
||||
{
|
||||
oMatrix->resize(totalBufferSize);
|
||||
|
||||
for (size_t i = 0; i < totalBufferSize; ++i) { oMatrix->setDimensionLabel(0, i, ("Feature " + std::to_string(i + 1)).c_str()); }
|
||||
|
||||
m_encoder->encodeHeader();
|
||||
boxIO->markOutputAsReadyToSend(0, m_lastChunkStartTime, m_lastChunkEndTime);
|
||||
m_headerSent = true;
|
||||
}
|
||||
}
|
||||
//*/
|
||||
if (m_decoder[input]->isBufferReceived())
|
||||
{
|
||||
bufferReceived = true;
|
||||
CMatrix* iMatrix = m_decoder[input]->getOutputMatrix();
|
||||
const size_t size = iMatrix->getBufferElementCount();
|
||||
|
||||
double* buffer = iMatrix->getBuffer();
|
||||
for (size_t i = 0; i < size; ++i) { bufferElements.push_back(buffer[i]); }
|
||||
}
|
||||
}
|
||||
|
||||
if (m_headerSent && bufferReceived)
|
||||
{
|
||||
double* oBuffer = oMatrix->getBuffer();
|
||||
for (size_t i = 0; i < bufferElements.size(); ++i) { oBuffer[i] = bufferElements[i]; }
|
||||
m_encoder->encodeBuffer();
|
||||
boxIO->markOutputAsReadyToSend(0, m_lastChunkStartTime, m_lastChunkEndTime);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace FeatureExtraction
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,115 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace FeatureExtraction {
|
||||
/**
|
||||
* Main plugin class of the feature aggregator plugins.
|
||||
* Aggregates the features received in a feature vector then outputs it.
|
||||
* */
|
||||
class CBoxAlgorithmFeatureAggregator final : public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
|
||||
{
|
||||
public:
|
||||
|
||||
CBoxAlgorithmFeatureAggregator() { }
|
||||
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_FeatureAggregator)
|
||||
|
||||
protected:
|
||||
//codecs
|
||||
Toolkit::TFeatureVectorEncoder<CBoxAlgorithmFeatureAggregator>* m_encoder = nullptr;
|
||||
std::vector<Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmFeatureAggregator>*> m_decoder;
|
||||
|
||||
// contains the labels for each dimension for each input
|
||||
std::vector<std::vector<std::vector<std::string>>> m_featureNames;
|
||||
|
||||
// contains the dimension size for each dimension of each input
|
||||
std::vector<std::vector<size_t>> m_dimSize;
|
||||
|
||||
// contains the input buffer's total size for each input
|
||||
std::vector<size_t> m_iBufferSizes;
|
||||
|
||||
//start time and end time of the last arrived chunk
|
||||
uint64_t m_lastChunkStartTime = 0;
|
||||
uint64_t m_lastChunkEndTime = 0;
|
||||
|
||||
// number of inputs
|
||||
size_t m_nInput = 0;
|
||||
|
||||
bool m_headerSent = false;
|
||||
};
|
||||
|
||||
class CBoxAlgorithmFeatureAggregatorListener final : public Toolkit::TBoxListener<IBoxListener>
|
||||
{
|
||||
public:
|
||||
|
||||
bool check(Kernel::IBox& box) const
|
||||
{
|
||||
for (size_t i = 0; i < box.getInputCount(); ++i)
|
||||
{
|
||||
box.setInputName(i, ("Input stream " + std::to_string(i + 1)).c_str());
|
||||
box.setInputType(i, OV_TypeId_StreamedMatrix);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool onInputRemoved(Kernel::IBox& box, const size_t /*index*/) override { return this->check(box); }
|
||||
bool onInputAdded(Kernel::IBox& box, const size_t /*index*/) override { return this->check(box); }
|
||||
|
||||
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
|
||||
};
|
||||
|
||||
/**
|
||||
* Plugin's description
|
||||
*/
|
||||
class CBoxAlgorithmFeatureAggregatorDesc final : public IBoxAlgorithmDesc
|
||||
{
|
||||
public:
|
||||
CString getName() const override { return CString("Feature aggregator"); }
|
||||
CString getAuthorName() const override { return CString("Bruno Renier"); }
|
||||
CString getAuthorCompanyName() const override { return CString("INRIA/IRISA"); }
|
||||
CString getShortDescription() const override { return CString("Aggregates input to feature vectors"); }
|
||||
|
||||
CString getDetailedDescription() const override { return CString("Each chunk of input will be catenated into one feature vector."); }
|
||||
|
||||
CString getCategory() const override { return CString("Feature extraction"); }
|
||||
CString getVersion() const override { return CString("1.0"); }
|
||||
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
|
||||
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
void release() override { }
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_FeatureAggregator; }
|
||||
IPluginObject* create() override { return new CBoxAlgorithmFeatureAggregator(); }
|
||||
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmFeatureAggregatorListener; }
|
||||
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
|
||||
|
||||
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
|
||||
{
|
||||
prototype.addInput("Input stream 1", OV_TypeId_StreamedMatrix);
|
||||
// prototype.addInput("Input stream 2", OV_TypeId_StreamedMatrix);
|
||||
prototype.addOutput("Feature vector stream", OV_TypeId_FeatureVector);
|
||||
prototype.addFlag(Kernel::BoxFlag_CanAddInput);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_FeatureAggregatorDesc)
|
||||
};
|
||||
} // namespace FeatureExtraction
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
// Boxes
|
||||
//---------------------------------------------------------------------------------------------------
|
||||
#define OVP_ClassId_BoxAlgorithm_FeatureAggregator OpenViBE::CIdentifier(0x00682417, 0x453635F9)
|
||||
#define OVP_ClassId_BoxAlgorithm_FeatureAggregatorDesc OpenViBE::CIdentifier(0x00B5B638, 0x25821BAF)
|
||||
@@ -0,0 +1,15 @@
|
||||
#include "ovp_defines.h"
|
||||
|
||||
#include "box-algorithms/ovpCBoxAlgorithmFeatureAggregator.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace FeatureExtraction {
|
||||
|
||||
OVP_Declare_Begin()
|
||||
OVP_Declare_New(CBoxAlgorithmFeatureAggregatorDesc);
|
||||
OVP_Declare_End()
|
||||
|
||||
} // namespace FeatureExtraction
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,51 @@
|
||||
PROJECT(openvibe-plugins-sdk-file-io)
|
||||
|
||||
SET(PROJECT_VERSION_MAJOR ${OV_GLOBAL_VERSION_MAJOR})
|
||||
SET(PROJECT_VERSION_MINOR ${OV_GLOBAL_VERSION_MINOR})
|
||||
SET(PROJECT_VERSION_PATCH ${OV_GLOBAL_VERSION_PATCH})
|
||||
SET(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH})
|
||||
|
||||
FILE(GLOB_RECURSE SRC_FILES src/*.cpp src/*.c src/*.h src/*.hpp src/*.inl)
|
||||
LIST(REMOVE_ITEM SRC_FILES
|
||||
src/box-algorithms/csv/ovpCBoxAlgorithmCSVFileReader.cpp
|
||||
src/box-algorithms/csv/ovpCBoxAlgorithmCSVFileWriter.cpp
|
||||
src/box-algorithms/csv/ovpCBoxAlgorithmCSVFileReader.h
|
||||
src/box-algorithms/csv/ovpCBoxAlgorithmCSVFileWriter.h)
|
||||
INCLUDE("FindSourceRCProperties")
|
||||
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("FindOpenViBEModuleCSV")
|
||||
INCLUDE("FindOpenViBEModuleEBML")
|
||||
INCLUDE("FindOpenViBEModuleXML")
|
||||
INCLUDE("FindOpenViBEModuleFS")
|
||||
INCLUDE("FindThirdPartyBoost")
|
||||
INCLUDE("FindThirdPartyXerces")
|
||||
|
||||
# ---------------------------------
|
||||
# Target macros
|
||||
# Defines target operating system, architecture and 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 signals DESTINATION ${DIST_DATADIR}/openvibe/scenarios/)
|
||||
INSTALL(DIRECTORY electrode_sets DESTINATION ${DIST_DATADIR}/openvibe/)
|
||||
|
||||
@@ -0,0 +1,367 @@
|
||||
<OpenViBE-Scenario>
|
||||
<FormatVersion>2</FormatVersion>
|
||||
<Creator>OpenViBE Designer</Creator>
|
||||
<CreatorVersion>2.2.0</CreatorVersion>
|
||||
<Settings></Settings>
|
||||
<Inputs></Inputs>
|
||||
<Outputs></Outputs>
|
||||
<Boxes>
|
||||
<Box>
|
||||
<Identifier>(0x00004a40, 0x0000585c)</Identifier>
|
||||
<Name>Continuous Oscilloscope</Name>
|
||||
<AlgorithmClassIdentifier>(0x0842bcd1, 0xd53c1c89)</AlgorithmClassIdentifier>
|
||||
<Inputs>
|
||||
<Input>
|
||||
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
|
||||
<Name>Matrix</Name>
|
||||
</Input>
|
||||
<Input>
|
||||
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
|
||||
<Name>Markers</Name>
|
||||
</Input>
|
||||
</Inputs>
|
||||
<Settings>
|
||||
<Setting>
|
||||
<TypeIdentifier>(0x330306dd, 0x74a95f98)</TypeIdentifier>
|
||||
<Name>Channel Localisation</Name>
|
||||
<DefaultValue>${AdvancedViz_ChannelLocalisation}</DefaultValue>
|
||||
<Value>${AdvancedViz_ChannelLocalisation}</Value>
|
||||
<Modifiability>false</Modifiability>
|
||||
</Setting>
|
||||
<Setting>
|
||||
<TypeIdentifier>(0x8f02e3f6, 0xffb00f4b)</TypeIdentifier>
|
||||
<Name>Temporal Coherence</Name>
|
||||
<DefaultValue>Time Locked</DefaultValue>
|
||||
<Value>Time Locked</Value>
|
||||
<Modifiability>false</Modifiability>
|
||||
</Setting>
|
||||
<Setting>
|
||||
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
|
||||
<Name>Time Scale</Name>
|
||||
<DefaultValue>20</DefaultValue>
|
||||
<Value>20</Value>
|
||||
<Modifiability>false</Modifiability>
|
||||
</Setting>
|
||||
<Setting>
|
||||
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
|
||||
<Name>Matrix Count</Name>
|
||||
<DefaultValue>50</DefaultValue>
|
||||
<Value>50</Value>
|
||||
<Modifiability>false</Modifiability>
|
||||
</Setting>
|
||||
<Setting>
|
||||
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
|
||||
<Name>Positive Data Only ?</Name>
|
||||
<DefaultValue>false</DefaultValue>
|
||||
<Value>false</Value>
|
||||
<Modifiability>false</Modifiability>
|
||||
</Setting>
|
||||
<Setting>
|
||||
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
|
||||
<Name>Gain</Name>
|
||||
<DefaultValue>1</DefaultValue>
|
||||
<Value>0.01</Value>
|
||||
<Modifiability>false</Modifiability>
|
||||
</Setting>
|
||||
<Setting>
|
||||
<TypeIdentifier>(0x79a9edeb, 0x245d83fc)</TypeIdentifier>
|
||||
<Name>Caption</Name>
|
||||
<DefaultValue></DefaultValue>
|
||||
<Value></Value>
|
||||
<Modifiability>false</Modifiability>
|
||||
</Setting>
|
||||
<Setting>
|
||||
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
|
||||
<Name>Translucency</Name>
|
||||
<DefaultValue>1</DefaultValue>
|
||||
<Value>1</Value>
|
||||
<Modifiability>false</Modifiability>
|
||||
</Setting>
|
||||
<Setting>
|
||||
<TypeIdentifier>(0x7f45a2a9, 0x7db12219)</TypeIdentifier>
|
||||
<Name>Color</Name>
|
||||
<DefaultValue>${AdvancedViz_DefaultColor}</DefaultValue>
|
||||
<Value>${AdvancedViz_DefaultColor}</Value>
|
||||
<Modifiability>false</Modifiability>
|
||||
</Setting>
|
||||
</Settings>
|
||||
<Attributes>
|
||||
<Attribute>
|
||||
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
|
||||
<Value>288</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
|
||||
<Value>368</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
|
||||
<Value>(0x35390ab5, 0x7b926078)</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x527ad68d, 0x16d746a0)</Identifier>
|
||||
<Value></Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
|
||||
<Value>9</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
|
||||
<Value>2</Value>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Box>
|
||||
<Box>
|
||||
<Identifier>(0x00006ffa, 0x000071a0)</Identifier>
|
||||
<Name>Temporal Filter</Name>
|
||||
<AlgorithmClassIdentifier>(0xb4f9d042, 0x9d79f2e5)</AlgorithmClassIdentifier>
|
||||
<Inputs>
|
||||
<Input>
|
||||
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
|
||||
<Name>Input signal</Name>
|
||||
</Input>
|
||||
</Inputs>
|
||||
<Outputs>
|
||||
<Output>
|
||||
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
|
||||
<Name>Output signal</Name>
|
||||
</Output>
|
||||
</Outputs>
|
||||
<Settings>
|
||||
<Setting>
|
||||
<TypeIdentifier>(0x2f2c606c, 0x8512ed68)</TypeIdentifier>
|
||||
<Name>Filter Method</Name>
|
||||
<DefaultValue>Butterworth</DefaultValue>
|
||||
<Value>Butterworth</Value>
|
||||
<Modifiability>false</Modifiability>
|
||||
</Setting>
|
||||
<Setting>
|
||||
<TypeIdentifier>(0xfa20178e, 0x4cba62e9)</TypeIdentifier>
|
||||
<Name>Filter Type</Name>
|
||||
<DefaultValue>Band Pass</DefaultValue>
|
||||
<Value>Band Pass</Value>
|
||||
<Modifiability>false</Modifiability>
|
||||
</Setting>
|
||||
<Setting>
|
||||
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
|
||||
<Name>Filter Order</Name>
|
||||
<DefaultValue>4</DefaultValue>
|
||||
<Value>4</Value>
|
||||
<Modifiability>false</Modifiability>
|
||||
</Setting>
|
||||
<Setting>
|
||||
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
|
||||
<Name>Low Cut-off Frequency (Hz)</Name>
|
||||
<DefaultValue>1</DefaultValue>
|
||||
<Value>1</Value>
|
||||
<Modifiability>false</Modifiability>
|
||||
</Setting>
|
||||
<Setting>
|
||||
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
|
||||
<Name>High Cut-off Frequency (Hz)</Name>
|
||||
<DefaultValue>40</DefaultValue>
|
||||
<Value>100</Value>
|
||||
<Modifiability>false</Modifiability>
|
||||
</Setting>
|
||||
<Setting>
|
||||
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
|
||||
<Name>Band Pass Ripple (dB)</Name>
|
||||
<DefaultValue>0.5</DefaultValue>
|
||||
<Value>0.5</Value>
|
||||
<Modifiability>false</Modifiability>
|
||||
</Setting>
|
||||
</Settings>
|
||||
<Attributes>
|
||||
<Attribute>
|
||||
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
|
||||
<Value>128</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
|
||||
<Value>320</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
|
||||
<Value>(0x27a4ceec, 0x876d6384)</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>1</Value>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Box>
|
||||
<Box>
|
||||
<Identifier>(0x557489da, 0x7f18ab2b)</Identifier>
|
||||
<Name>Generic stream reader</Name>
|
||||
<AlgorithmClassIdentifier>(0x6468099f, 0x0370095a)</AlgorithmClassIdentifier>
|
||||
<Outputs>
|
||||
<Output>
|
||||
<TypeIdentifier>(0x403488e7, 0x565d70b6)</TypeIdentifier>
|
||||
<Name>Output stream 1</Name>
|
||||
</Output>
|
||||
<Output>
|
||||
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
|
||||
<Name>Output stream 2</Name>
|
||||
</Output>
|
||||
<Output>
|
||||
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
|
||||
<Name>Output stream 3</Name>
|
||||
</Output>
|
||||
</Outputs>
|
||||
<Settings>
|
||||
<Setting>
|
||||
<TypeIdentifier>(0x330306dd, 0x74a95f98)</TypeIdentifier>
|
||||
<Name>Filename</Name>
|
||||
<DefaultValue></DefaultValue>
|
||||
<Value>${Path_Data}/scenarios/signals/bci-motor-imagery.ov</Value>
|
||||
<Modifiability>false</Modifiability>
|
||||
</Setting>
|
||||
</Settings>
|
||||
<Attributes>
|
||||
<Attribute>
|
||||
<Identifier>(0x17ee7c08, 0x94c14893)</Identifier>
|
||||
<Value></Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
|
||||
<Value>32</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
|
||||
<Value>352</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x30a4e5c9, 0x83502953)</Identifier>
|
||||
<Value></Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
|
||||
<Value>(0xf37b8e7a, 0x1bc33e4e)</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0xc46b3d00, 0x3e0454e1)</Identifier>
|
||||
<Value>(0x00000000, 0x00b124ce)</Value>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Box>
|
||||
</Boxes>
|
||||
<Links>
|
||||
<Link>
|
||||
<Identifier>(0x0000003a, 0x0000396d)</Identifier>
|
||||
<Source>
|
||||
<BoxIdentifier>(0x557489da, 0x7f18ab2b)</BoxIdentifier>
|
||||
<BoxOutputIndex>2</BoxOutputIndex>
|
||||
</Source>
|
||||
<Target>
|
||||
<BoxIdentifier>(0x00004a40, 0x0000585c)</BoxIdentifier>
|
||||
<BoxInputIndex>1</BoxInputIndex>
|
||||
</Target>
|
||||
</Link>
|
||||
<Link>
|
||||
<Identifier>(0x00001785, 0x00002d8f)</Identifier>
|
||||
<Source>
|
||||
<BoxIdentifier>(0x00006ffa, 0x000071a0)</BoxIdentifier>
|
||||
<BoxOutputIndex>0</BoxOutputIndex>
|
||||
</Source>
|
||||
<Target>
|
||||
<BoxIdentifier>(0x00004a40, 0x0000585c)</BoxIdentifier>
|
||||
<BoxInputIndex>0</BoxInputIndex>
|
||||
</Target>
|
||||
</Link>
|
||||
<Link>
|
||||
<Identifier>(0x000045ee, 0x000074b2)</Identifier>
|
||||
<Source>
|
||||
<BoxIdentifier>(0x557489da, 0x7f18ab2b)</BoxIdentifier>
|
||||
<BoxOutputIndex>1</BoxOutputIndex>
|
||||
</Source>
|
||||
<Target>
|
||||
<BoxIdentifier>(0x00006ffa, 0x000071a0)</BoxIdentifier>
|
||||
<BoxInputIndex>0</BoxInputIndex>
|
||||
</Target>
|
||||
</Link>
|
||||
</Links>
|
||||
<Comments>
|
||||
<Comment>
|
||||
<Identifier>(0x0000761e, 0x00005eae)</Identifier>
|
||||
<Text>The <i>Temporal Filter</i> enables
|
||||
us to center data around 0 by removing
|
||||
low frequencies.</Text>
|
||||
<Attributes>
|
||||
<Attribute>
|
||||
<Identifier>(0x473d9a43, 0x97fc0a97)</Identifier>
|
||||
<Value>592</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x7234b86b, 0x2b8651a5)</Identifier>
|
||||
<Value>144</Value>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Comment>
|
||||
<Comment>
|
||||
<Identifier>(0x0afa52e5, 0x7e02009b)</Identifier>
|
||||
<Text>The <i>Continuous Oscilloscope</i> box displays
|
||||
the content of the file.
|
||||
|
||||
You might need to <b>right click and drag up or down</b>
|
||||
in order to zoom to the signal.</Text>
|
||||
<Attributes>
|
||||
<Attribute>
|
||||
<Identifier>(0x473d9a43, 0x97fc0a97)</Identifier>
|
||||
<Value>624</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x7234b86b, 0x2b8651a5)</Identifier>
|
||||
<Value>256</Value>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Comment>
|
||||
<Comment>
|
||||
<Identifier>(0x3198ee95, 0x3397262b)</Identifier>
|
||||
<Text>You can browse each box' documentation by selecting the box and pressing <b>F1</b></Text>
|
||||
<Attributes>
|
||||
<Attribute>
|
||||
<Identifier>(0x473d9a43, 0x97fc0a97)</Identifier>
|
||||
<Value>480</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x7234b86b, 0x2b8651a5)</Identifier>
|
||||
<Value>384</Value>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Comment>
|
||||
<Comment>
|
||||
<Identifier>(0x52401569, 0x4c3a5e29)</Identifier>
|
||||
<Text>The <i><b>Generic Stream Reader</b></i> box
|
||||
reads an <i>OpenViBE</i> file from disk and sends
|
||||
its content to the following box.
|
||||
The <i>OpenViBE</i> file format can store any
|
||||
stream of the NeuroRT platform.</Text>
|
||||
<Attributes>
|
||||
<Attribute>
|
||||
<Identifier>(0x473d9a43, 0x97fc0a97)</Identifier>
|
||||
<Value>608</Value>
|
||||
</Attribute>
|
||||
<Attribute>
|
||||
<Identifier>(0x7234b86b, 0x2b8651a5)</Identifier>
|
||||
<Value>48</Value>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Comment>
|
||||
</Comments>
|
||||
<Metadata>
|
||||
<Entry>
|
||||
<Identifier>(0x0000775c, 0x000078ff)</Identifier>
|
||||
<Type>(0x3bcce5d2, 0x43f2d968)</Type>
|
||||
<Data>[{"boxIdentifier":"(0x00004a40, 0x0000585c)","childCount":0,"identifier":"(0x00005da3, 0x00007534)","parentIdentifier":"(0xffffffff, 0xffffffff)","type":3},{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":1,"height":1,"identifier":"(0x0509f2db, 0x6d6aaba8)","name":"Default window","parentIdentifier":"(0xffffffff, 0xffffffff)","type":1,"width":1},{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":1,"identifier":"(0x748521de, 0x4b35cf59)","index":0,"name":"Default tab","parentIdentifier":"(0x0509f2db, 0x6d6aaba8)","type":2},{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":0,"identifier":"(0x06d9aa6d, 0x7f11c5f6)","index":0,"name":"Empty","parentIdentifier":"(0x748521de, 0x4b35cf59)","type":0}]</Data>
|
||||
</Entry>
|
||||
</Metadata>
|
||||
</OpenViBE-Scenario>
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* \page BoxAlgorithm_CSVFileReader CSV File Reader
|
||||
__________________________________________________________________
|
||||
|
||||
Detailed description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_CSVFileReader_Description|
|
||||
This box reads a Comma Separated Values (CSV) text file. The file must be written using a specific
|
||||
OpenViBE convention. The format is described in detail in \ref Doc_BoxAlgorithm_CSVFileWriter
|
||||
|
||||
The file can contain two streams, one for matrix-type data and one for stimulations.
|
||||
* |OVP_DocEnd_BoxAlgorithm_CSVFileReader_Description|
|
||||
__________________________________________________________________
|
||||
|
||||
Outputs description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_CSVFileReader_Outputs|
|
||||
* |OVP_DocEnd_BoxAlgorithm_CSVFileReader_Outputs|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_CSVFileReader_Output1|
|
||||
This output can carry Signal, Streamed Matrix, Covariance, Spectrum and Feature Vector data.
|
||||
* |OVP_DocEnd_BoxAlgorithm_CSVFileReader_Output1|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_CSVFileReader_Output2|
|
||||
This output will contain stimulations read from the file.
|
||||
* |OVP_DocEnd_BoxAlgorithm_CSVFileReader_Output2|
|
||||
__________________________________________________________________
|
||||
|
||||
Settings description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_CSVFileReader_Settings|
|
||||
* |OVP_DocEnd_BoxAlgorithm_CSVFileReader_Settings|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_CSVFileReader_Setting1|
|
||||
Path of the CSV file to read.
|
||||
* |OVP_DocEnd_BoxAlgorithm_CSVFileReader_Setting1|
|
||||
__________________________________________________________________
|
||||
|
||||
Examples description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_CSVFileReader_Examples|
|
||||
|
||||
Example of a scenario using the CSV Reader box to read a signal file and display it using an Oscilloscope.
|
||||
|
||||
\image html csv-file-reader-example.png "Reading a CSV file"
|
||||
\image latex csv-file-reader-example.png "Reading a CSV file" width=8cm
|
||||
|
||||
* |OVP_DocEnd_BoxAlgorithm_CSVFileReader_Examples|
|
||||
__________________________________________________________________
|
||||
|
||||
Miscellaneous description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_CSVFileReader_Miscellaneous|
|
||||
* |OVP_DocEnd_BoxAlgorithm_CSVFileReader_Miscellaneous|
|
||||
*/
|
||||
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* \page BoxAlgorithm_CSVFileWriter CSV File Writer
|
||||
__________________________________________________________________
|
||||
|
||||
Detailed description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_CSVFileWriter_Description|
|
||||
This box writes incoming time series along with a stimulation stream into a text file using the
|
||||
Comma Separated Values format conforming to the RFC 4180 format.
|
||||
|
||||
The header of the CSV file contains some additional information that enables additional features
|
||||
during subsequent reading. This format is described in the \ref Doc_BoxAlgorithm_CSVFileWriter_Miscellaneous
|
||||
section.
|
||||
* |OVP_DocEnd_BoxAlgorithm_CSVFileWriter_Description|
|
||||
__________________________________________________________________
|
||||
|
||||
Inputs description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_CSVFileWriter_Inputs|
|
||||
* |OVP_DocEnd_BoxAlgorithm_CSVFileWriter_Inputs|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_CSVFileWriter_Input1|
|
||||
Time series input, this can be either a Signal, Streamed Matrix, Spectrum, Covariance Matrix or Feature Vector stream.
|
||||
* |OVP_DocEnd_BoxAlgorithm_CSVFileWriter_Input1|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_CSVFileWriter_Input2|
|
||||
Stimulations to be written alongside the signal.
|
||||
* |OVP_DocEnd_BoxAlgorithm_CSVFileWriter_Input2|
|
||||
__________________________________________________________________
|
||||
|
||||
Settings description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_CSVFileWriter_Settings|
|
||||
* |OVP_DocEnd_BoxAlgorithm_CSVFileWriter_Settings|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_CSVFileWriter_Setting1|
|
||||
Path of the CSV file to be written.
|
||||
* |OVP_DocEnd_BoxAlgorithm_CSVFileWriter_Setting1|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_CSVFileWriter_Setting2|
|
||||
Precision, in number of decimal digits, of the resulting data. For longer data the precision can significantly impact the file size.
|
||||
* |OVP_DocEnd_BoxAlgorithm_CSVFileWriter_Setting2|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_CSVFileWriter_Setting3|
|
||||
By default this box will overwrite data in the file if it exists. If this setting is set to true, the box will append data to the file instead. If the file is empty a header will be added as well. It is up to the user to ensure herself that the data written is of the same type and dimensions.
|
||||
* |OVP_DocEnd_BoxAlgorithm_CSVFileWriter_Setting3|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_CSVFileWriter_Setting4|
|
||||
If this setting is activated, only the last received matrix will be written to the file. This can be used, for example, with cumulative average box.
|
||||
* |OVP_DocEnd_BoxAlgorithm_CSVFileWriter_Setting4|
|
||||
__________________________________________________________________
|
||||
|
||||
Examples description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_CSVFileWriter_Examples|
|
||||
|
||||
Example of writing a spectrum into a CSV file:
|
||||
|
||||
\image html csv-file-writer-example.png "Writing a CSV file"
|
||||
\image latex csv-file-writer-example.png "Writing a CSV file" width=8cm
|
||||
|
||||
|
||||
* |OVP_DocEnd_BoxAlgorithm_CSVFileWriter_Examples|
|
||||
__________________________________________________________________
|
||||
|
||||
Miscellaneous description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_CSVFileWriter_Miscellaneous|
|
||||
|
||||
The CSV Format
|
||||
|
||||
## Example file containing Signal data and Stimulations
|
||||
|
||||
Signal file sampled at 8Hz, with epochs of 0.5s length. Note that the timestamps do not have an end time which is implicit.
|
||||
|
||||
In this example each x represents an arbitrary floating point value. The header contains the following rows:
|
||||
|
||||
- Time:8Hz - contains the timestamp of each sample, this label also encodes the original sampling rate
|
||||
- Epoch - contains the number of the epoch in which each sample is contained, this allows encoding even overlapping signal
|
||||
- O1, O2, Pz, P3, P4 - channels containing EEG data
|
||||
- Event Id - contains a colon separated list of StimulationIds
|
||||
- Event Date - contains the same number of timestamps
|
||||
- Event Duration - is constructed in the same manner as Event Date
|
||||
|
||||
\code{.unparsed}
|
||||
Time:8Hz,Epoch,O1,O2,Pz,P3,P4,Event Id,Event Date,Event Duration
|
||||
0.00000,0,x,x,x,x,x,,,
|
||||
0.12500,0,x,x,x,x,x,,,
|
||||
0.25000,0,x,x,x,x,x,32000:32010,0.25000:0.25000,0:0
|
||||
0.37500,0,x,x,x,x,x,,,
|
||||
0.50000,1,x,x,x,x,x,,,
|
||||
0.62500,1,x,x,x,x,x,,,
|
||||
0.75000,1,x,x,x,x,x,35000,0.75250,0
|
||||
0.87500,1,x,x,x,x,x,,,
|
||||
\endcode
|
||||
|
||||
## Example file containing a three dimensional matrix
|
||||
|
||||
This example file contains 2x2x2 matrices produced every 0.125 seconds and spanning one second.
|
||||
|
||||
The labels for this matrix are:
|
||||
|
||||
- for first dimension "LA", "LB"
|
||||
- for second dimension "1", "2"
|
||||
- for third dimension "X", "Y"
|
||||
|
||||
\code{.unparsed}
|
||||
Time:2x2x2,End Time,LA:1:X,LA:1:Y,LA:2:X,LA:2:Y,LB:1:X,...,LB:2:Y,Event Id,Event Date,Event Duration
|
||||
0.00000,1.00000,x,x,x,x,x,...,x,,,
|
||||
0.12500,1.12500,x,x,x,x,x,...,x,,,
|
||||
0.25000,1.25000,x,x,x,x,x,...,x,,,
|
||||
0.37500,1.37500,x,x,x,x,x,...,x,,,
|
||||
0.50000,1.50000,x,x,x,x,x,...,x,,,
|
||||
0.62500,1.62500,x,x,x,x,x,...,x,,,
|
||||
0.75000,1.75000,x,x,x,x,x,...,x,,,
|
||||
0.87500,1.62500,x,x,x,x,x,...,x,,,
|
||||
1.00000,2.00000,x,x,x,x,x,...,x,,,
|
||||
1.12500,2.12500,x,x,x,x,x,...,x,,,
|
||||
\endcode
|
||||
|
||||
\note
|
||||
A label can be an empty string. If second dimension had an empty label then the first column would have label "LA::X", if it were the third dimension the column would have label "LA:1:"
|
||||
|
||||
|
||||
## Example file containing a spectrum
|
||||
|
||||
Spectrum file with 2 channels, 128Hz signal and spectra calculated on periods of 1 second every 0.125 seconds. The last element in the Time column (128) represents the original sampling rate.
|
||||
|
||||
\code{.unparsed}
|
||||
Time:2x64:128,End Time,O1:0,O1:1.015873,...,O1:64,O2:0,...,O2:64,Event Id,Event Date,Event Duration
|
||||
0.00000,1.00000,x,x,...,x,x,...,x,,,
|
||||
0.12500,1.12500,x,x,...,x,x,...,x,,,
|
||||
0.25000,1.25000,x,x,...,x,x,...,x,,,
|
||||
0.37500,1.37500,x,x,...,x,x,...,x,,,
|
||||
0.50000,1.50000,x,x,...,x,x,...,x,,,
|
||||
0.62500,1.62500,x,x,...,x,x,...,x,,,
|
||||
0.75000,1.75000,x,x,...,x,x,...,x,,,
|
||||
0.87500,1.87500,x,x,...,x,x,...,x,,,
|
||||
1.00000,2.00000,x,x,...,x,x,...,x,,,
|
||||
1.12500,2.12500,x,x,...,x,x,...,x,,,
|
||||
\endcode
|
||||
|
||||
|
||||
|
||||
* |OVP_DocEnd_BoxAlgorithm_CSVFileWriter_Miscellaneous|
|
||||
*/
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* \page BoxAlgorithm_ElectrodeLocalisationFileReader Electrode localisation file reader
|
||||
__________________________________________________________________
|
||||
|
||||
Detailed description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_ElectrodeLocalisationFileReader_Description|
|
||||
* This box loads files holding the normalized coordinates of an electrode set.
|
||||
* |OVP_DocEnd_BoxAlgorithm_ElectrodeLocalisationFileReader_Description|
|
||||
__________________________________________________________________
|
||||
|
||||
Outputs description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_ElectrodeLocalisationFileReader_Outputs|
|
||||
* |OVP_DocEnd_BoxAlgorithm_ElectrodeLocalisationFileReader_Outputs|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_ElectrodeLocalisationFileReader_Output1|
|
||||
* The output channel localization information.
|
||||
* |OVP_DocEnd_BoxAlgorithm_ElectrodeLocalisationFileReader_Output1|
|
||||
__________________________________________________________________
|
||||
|
||||
Settings description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_ElectrodeLocalisationFileReader_Settings|
|
||||
* |OVP_DocEnd_BoxAlgorithm_ElectrodeLocalisationFileReader_Settings|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_ElectrodeLocalisationFileReader_Setting1|
|
||||
* The normalized coordinates file.
|
||||
* |OVP_DocEnd_BoxAlgorithm_ElectrodeLocalisationFileReader_Setting1|
|
||||
__________________________________________________________________
|
||||
|
||||
Examples description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_ElectrodeLocalisationFileReader_Examples|
|
||||
* A default electrode set should be included in your BRAND_NAME distribution. Look for
|
||||
* it in the <tt>share/electrode_sets</tt> directory of the source tree of this plugin.
|
||||
* It comes as a \ref Doc_MatrixFileFormat "text file" which is quite self explanatory,
|
||||
* open it in a text editor to edit it. Electrode names are specified in the header
|
||||
* section of the matrix, while actual coordinates are stored in the buffer section.
|
||||
* |OVP_DocEnd_BoxAlgorithm_ElectrodeLocalisationFileReader_Examples|
|
||||
__________________________________________________________________
|
||||
|
||||
Miscellaneous description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_ElectrodeLocalisationFileReader_Miscellaneous|
|
||||
* The coordinates loaded by this box are sent once only (static coordinates, e.g. EEG), as
|
||||
* opposed to regularly (dynamic coordinates, e.g. MEG).
|
||||
* The supported file format is the \ref Doc_MatrixFileFormat "BRAND_NAME matrix" file format.
|
||||
* Electrode coordinates must be normalized cartesian coordinates in the following frame
|
||||
* of reference : X right, Y front and Z up.
|
||||
* |OVP_DocEnd_BoxAlgorithm_ElectrodeLocalisationFileReader_Miscellaneous|
|
||||
*/
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* \page BoxAlgorithm_GenericStreamReader Generic stream reader
|
||||
__________________________________________________________________
|
||||
|
||||
Detailed description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_GenericStreamReader_Description|
|
||||
This box is able to read any file saved with the \ref Doc_BoxAlgorithm_GenericStreamWriter box.
|
||||
It is interesting to notice that such file can contain a variable number of streams. Therefore,
|
||||
the user is able to add and modify any output he wants on the box. The box does not supposes
|
||||
anything on the streams contained in the file during authoring in the designer. The streams of
|
||||
the file are mapped to created output at runtime in an "intelligent way" depending on there types.
|
||||
* |OVP_DocEnd_BoxAlgorithm_GenericStreamReader_Description|
|
||||
__________________________________________________________________
|
||||
|
||||
Outputs description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_GenericStreamReader_Outputs|
|
||||
This box can have as many output you want depending on the content of the file.
|
||||
* |OVP_DocEnd_BoxAlgorithm_GenericStreamReader_Outputs|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_GenericStreamReader_Output1|
|
||||
This is the default output.
|
||||
* |OVP_DocEnd_BoxAlgorithm_GenericStreamReader_Output1|
|
||||
__________________________________________________________________
|
||||
|
||||
Settings description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_GenericStreamReader_Settings|
|
||||
* |OVP_DocEnd_BoxAlgorithm_GenericStreamReader_Settings|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_GenericStreamReader_Setting1|
|
||||
This setting points to the file to read. This file may contain a variable number of multiplexed
|
||||
streams. Those streams will be mapped to corresponding outputs at runtime. The mapping is done in
|
||||
an "intelligent way" because it reorders the streams contained in the file to match the types of
|
||||
the outputs. If a stream from the file does not find a matching output, a warning is launched.
|
||||
If an output does not find a matching stream from the file, a warning is also launched.
|
||||
* |OVP_DocEnd_BoxAlgorithm_GenericStreamReader_Setting1|
|
||||
__________________________________________________________________
|
||||
|
||||
Examples description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_GenericStreamReader_Examples|
|
||||
* |OVP_DocEnd_BoxAlgorithm_GenericStreamReader_Examples|
|
||||
__________________________________________________________________
|
||||
|
||||
Miscellaneous description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_GenericStreamReader_Miscellaneous|
|
||||
* |OVP_DocEnd_BoxAlgorithm_GenericStreamReader_Miscellaneous|
|
||||
*/
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* \page BoxAlgorithm_GenericStreamWriter Generic stream writer
|
||||
__________________________________________________________________
|
||||
|
||||
Detailed description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_GenericStreamWriter_Description|
|
||||
This box is able to dump any OpenViBE stream into a binary file. In the cacse where this box
|
||||
would have multiple inputs, the streams would be multiplexed in the file. Such file can
|
||||
be read back with the \ref Doc_BoxAlgorithm_GenericStreamReader
|
||||
* |OVP_DocEnd_BoxAlgorithm_GenericStreamWriter_Description|
|
||||
__________________________________________________________________
|
||||
|
||||
Inputs description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_GenericStreamWriter_Inputs|
|
||||
You can add any input you want to this box depending on the number of streams you want to dump.
|
||||
In the cacse where this box would have multiple inputs, the streams would be multiplexed in the file.
|
||||
* |OVP_DocEnd_BoxAlgorithm_GenericStreamWriter_Inputs|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_GenericStreamWriter_Input1|
|
||||
The default input.
|
||||
|
||||
<b>Note: it important to correctly configure the type of the inputs</b>. That information will be
|
||||
used by the \ref Doc_BoxAlgorithm_GenericStreamReader to map the contained streams to its outputs.
|
||||
* |OVP_DocEnd_BoxAlgorithm_GenericStreamWriter_Input1|
|
||||
__________________________________________________________________
|
||||
|
||||
Settings description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_GenericStreamWriter_Settings|
|
||||
* |OVP_DocEnd_BoxAlgorithm_GenericStreamWriter_Settings|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_GenericStreamWriter_Setting1|
|
||||
This setting points to the file to write the streams to.
|
||||
* |OVP_DocEnd_BoxAlgorithm_GenericStreamWriter_Setting1|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_GenericStreamWriter_Setting2|
|
||||
Thanks to this setting, you can use compression on each input stream. This means that the basic
|
||||
structure of the file remains uncompressed but that each stream inside this structure is compressed.
|
||||
<b>Note: this is not implemented at the moment</b>.
|
||||
* |OVP_DocEnd_BoxAlgorithm_GenericStreamWriter_Setting2|
|
||||
__________________________________________________________________
|
||||
|
||||
Examples description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_GenericStreamWriter_Examples|
|
||||
* |OVP_DocEnd_BoxAlgorithm_GenericStreamWriter_Examples|
|
||||
__________________________________________________________________
|
||||
|
||||
Miscellaneous description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_GenericStreamWriter_Miscellaneous|
|
||||
* |OVP_DocEnd_BoxAlgorithm_GenericStreamWriter_Miscellaneous|
|
||||
*/
|
||||
@@ -0,0 +1,66 @@
|
||||
.. _Doc_BoxAlgorithm_CSVFileReader:
|
||||
|
||||
CSV File Reader
|
||||
===============
|
||||
|
||||
.. container:: attribution
|
||||
|
||||
:Author:
|
||||
Victor Herlin
|
||||
:Company:
|
||||
Mensia Technologies SA
|
||||
|
||||
.. image:: images/Doc_BoxAlgorithm_CSVFileReader.png
|
||||
|
||||
This box reads a Comma Separated Values (CSV) text file. The file must be written using a specific
|
||||
OpenViBE convention. The format is described in detail in :ref:`Doc_BoxAlgorithm_CSVFileWriter`
|
||||
|
||||
The file can contain two streams, one for matrix-type data and one for stimulations.
|
||||
|
||||
Outputs
|
||||
-------
|
||||
|
||||
.. csv-table::
|
||||
:header: "Output Name", "Stream Type"
|
||||
|
||||
"Output stream", "Signal"
|
||||
"Output stimulation", "Stimulations"
|
||||
|
||||
Output stream
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
This output can carry Signal, Streamed Matrix, Covariance, Spectrum and Feature Vector data.
|
||||
|
||||
Output stimulation
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This output will contain stimulations read from the file.
|
||||
|
||||
.. _Doc_BoxAlgorithm_CSVFileReader_Settings:
|
||||
|
||||
Settings
|
||||
--------
|
||||
|
||||
.. csv-table::
|
||||
:header: "Setting Name", "Type", "Default Value"
|
||||
|
||||
"Filename", "Filename", ""
|
||||
|
||||
Filename
|
||||
~~~~~~~~
|
||||
|
||||
Path of the CSV file to read.
|
||||
|
||||
.. _Doc_BoxAlgorithm_CSVFileReader_Examples:
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Example of a scenario using the CSV Reader box to read a signal file and display it using an Oscilloscope.
|
||||
|
||||
.. figure:: images/csv-file-reader-example.png
|
||||
:alt: Reading a CSV file
|
||||
:align: center
|
||||
|
||||
Reading a CSV file
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
.. _Doc_BoxAlgorithm_CSVFileWriter:
|
||||
|
||||
CSV File Writer
|
||||
===============
|
||||
|
||||
.. container:: attribution
|
||||
|
||||
:Author:
|
||||
Victor Herlin
|
||||
:Company:
|
||||
Mensia Technologies SA
|
||||
|
||||
|
||||
.. image:: images/Doc_BoxAlgorithm_CSVFileWriter.png
|
||||
|
||||
This box writes incoming time series along with a stimulation stream into a text file using the
|
||||
Comma Separated Values format conforming to the RFC 4180 format.
|
||||
|
||||
The header of the CSV file contains some additional information that enables additional features
|
||||
during subsequent reading. This format is described in the :ref:`Doc_BoxAlgorithm_CSVFileWriter_Miscellaneous`
|
||||
section.
|
||||
|
||||
Inputs
|
||||
------
|
||||
|
||||
.. csv-table::
|
||||
:header: "Input Name", "Stream Type"
|
||||
|
||||
"Input stream", "Signal"
|
||||
"Stimulations stream", "Stimulations"
|
||||
|
||||
Input stream
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Time series input, this can be either a Signal, Streamed Matrix, Spectrum, Covariance Matrix or Feature Vector stream.
|
||||
|
||||
Stimulations stream
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Stimulations to be written alongside the signal.
|
||||
|
||||
.. _Doc_BoxAlgorithm_CSVFileWriter_Settings:
|
||||
|
||||
Settings
|
||||
--------
|
||||
|
||||
.. csv-table::
|
||||
:header: "Setting Name", "Type", "Default Value"
|
||||
|
||||
"Filename", "Filename", ""
|
||||
"Precision", "Integer", "10"
|
||||
"Append data", "Boolean", "false"
|
||||
"Only last matrix", "Boolean", "false"
|
||||
|
||||
Filename
|
||||
~~~~~~~~
|
||||
|
||||
Path of the CSV file to be written.
|
||||
|
||||
|
||||
- Default value : [ *record-[$core{date}-$core{time}].csv* ]
|
||||
|
||||
|
||||
Precision
|
||||
~~~~~~~~~
|
||||
|
||||
Precision, in number of decimal digits, of the resulting data. For longer data the precision can significantly impact the file size.
|
||||
|
||||
Append data
|
||||
~~~~~~~~~~~
|
||||
|
||||
By default this box will overwrite data in the file if it exists. If this setting is set to true, the box will append data to the file instead. If the file is empty a header will be added as well. It is up to the user to ensure herself that the data written is of the same type and dimensions.
|
||||
|
||||
Only last matrix
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
If this setting is activated, only the last received matrix will be written to the file. This can be used, for example, with cumulative average box.
|
||||
|
||||
.. _Doc_BoxAlgorithm_CSVFileWriter_Examples:
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Example of writing a spectrum into a CSV file:
|
||||
|
||||
.. figure:: images/csv-file-writer-example.png
|
||||
:alt: Writing a CSV file
|
||||
:align: center
|
||||
|
||||
Writing a CSV file
|
||||
|
||||
.. _Doc_BoxAlgorithm_CSVFileWriter_Miscellaneous:
|
||||
|
||||
Miscellaneous
|
||||
-------------
|
||||
|
||||
The CSV Format
|
||||
|
||||
## Example file containing Signal data and Stimulations
|
||||
|
||||
Signal file sampled at 8Hz, with epochs of 0.5s length. Note that the timestamps do not have an end time which is implicit.
|
||||
|
||||
In this example each x represents an arbitrary floating point value. The header contains the following rows:
|
||||
|
||||
|
||||
- Time:8Hz - contains the timestamp of each sample, this label also encodes the original sampling rate
|
||||
- Epoch - contains the number of the epoch in which each sample is contained, this allows encoding even overlapping signal
|
||||
- O1, O2, Pz, P3, P4 - channels containing EEG data
|
||||
- Event Id - contains a colon separated list of StimulationIds
|
||||
- Event Date - contains the same number of timestamps
|
||||
- Event Duration - is constructed in the same manner as Event Date
|
||||
|
||||
|
||||
|
||||
.. code::
|
||||
|
||||
Time:8Hz,Epoch,O1,O2,Pz,P3,P4,Event Id,Event Date,Event Duration
|
||||
0.00000,0,x,x,x,x,x,,,
|
||||
0.12500,0,x,x,x,x,x,,,
|
||||
0.25000,0,x,x,x,x,x,32000:32010,0.25000:0.25000,0:0
|
||||
0.37500,0,x,x,x,x,x,,,
|
||||
0.50000,1,x,x,x,x,x,,,
|
||||
0.62500,1,x,x,x,x,x,,,
|
||||
0.75000,1,x,x,x,x,x,35000,0.75250,0
|
||||
0.87500,1,x,x,x,x,x,,,
|
||||
|
||||
## Example file containing a three dimensional matrix
|
||||
|
||||
This example file contains 2x2x2 matrices produced every 0.125 seconds and spanning one second.
|
||||
|
||||
The labels for this matrix are:
|
||||
|
||||
|
||||
- for first dimension "LA", "LB"
|
||||
- for second dimension "1", "2"
|
||||
- for third dimension "X", "Y"
|
||||
|
||||
|
||||
|
||||
.. code::
|
||||
|
||||
Time:2x2x2,End Time,LA:1:X,LA:1:Y,LA:2:X,LA:2:Y,LB:1:X,...,LB:2:Y,Event Id,Event Date,Event Duration
|
||||
0.00000,1.00000,x,x,x,x,x,...,x,,,
|
||||
0.12500,1.12500,x,x,x,x,x,...,x,,,
|
||||
0.25000,1.25000,x,x,x,x,x,...,x,,,
|
||||
0.37500,1.37500,x,x,x,x,x,...,x,,,
|
||||
0.50000,1.50000,x,x,x,x,x,...,x,,,
|
||||
0.62500,1.62500,x,x,x,x,x,...,x,,,
|
||||
0.75000,1.75000,x,x,x,x,x,...,x,,,
|
||||
0.87500,1.62500,x,x,x,x,x,...,x,,,
|
||||
1.00000,2.00000,x,x,x,x,x,...,x,,,
|
||||
1.12500,2.12500,x,x,x,x,x,...,x,,,
|
||||
|
||||
\note
|
||||
A label can be an empty string. If second dimension had an empty label then the first column would have label "LA::X", if it were the third dimension the column would have label "LA:1:"
|
||||
|
||||
## Example file containing a spectrum
|
||||
|
||||
Spectrum file with 2 channels, 128Hz signal and spectra calculated on periods of 1 second every 0.125 seconds. The last element in the Time column (128) represents the original sampling rate.
|
||||
|
||||
|
||||
.. code::
|
||||
|
||||
Time:2x64:128,End Time,O1:0,O1:1.015873,...,O1:64,O2:0,...,O2:64,Event Id,Event Date,Event Duration
|
||||
0.00000,1.00000,x,x,...,x,x,...,x,,,
|
||||
0.12500,1.12500,x,x,...,x,x,...,x,,,
|
||||
0.25000,1.25000,x,x,...,x,x,...,x,,,
|
||||
0.37500,1.37500,x,x,...,x,x,...,x,,,
|
||||
0.50000,1.50000,x,x,...,x,x,...,x,,,
|
||||
0.62500,1.62500,x,x,...,x,x,...,x,,,
|
||||
0.75000,1.75000,x,x,...,x,x,...,x,,,
|
||||
0.87500,1.87500,x,x,...,x,x,...,x,,,
|
||||
1.00000,2.00000,x,x,...,x,x,...,x,,,
|
||||
1.12500,2.12500,x,x,...,x,x,...,x,,,
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
.. _Doc_BoxAlgorithm_ElectrodeLocalisationFileReader:
|
||||
|
||||
Electrode localisation file reader
|
||||
==================================
|
||||
|
||||
.. container:: attribution
|
||||
|
||||
:Author:
|
||||
Vincent Delannoy
|
||||
:Company:
|
||||
INRIA/IRISA
|
||||
|
||||
.. image:: images/Doc_BoxAlgorithm_ElectrodeLocalisationFileReader.png
|
||||
|
||||
This box loads files holding the normalized coordinates of an electrode set.
|
||||
|
||||
Outputs
|
||||
-------
|
||||
|
||||
.. csv-table::
|
||||
:header: "Output Name", "Stream Type"
|
||||
|
||||
"Channel localisation", "Channel localisation"
|
||||
|
||||
Channel localisation
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The output channel localization information.
|
||||
|
||||
.. _Doc_BoxAlgorithm_ElectrodeLocalisationFileReader_Settings:
|
||||
|
||||
Settings
|
||||
--------
|
||||
|
||||
.. csv-table::
|
||||
:header: "Setting Name", "Type", "Default Value"
|
||||
|
||||
"Filename", "Filename", ""
|
||||
|
||||
Filename
|
||||
~~~~~~~~
|
||||
|
||||
The normalized coordinates file.
|
||||
|
||||
.. _Doc_BoxAlgorithm_ElectrodeLocalisationFileReader_Examples:
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
A default electrode set should be included in your NeuroRT distribution. Look for
|
||||
it in the ``share/electrode_sets`` directory of the source tree of this plugin.
|
||||
It comes as a :ref:`Doc_MatrixFileFormat` "text file" which is quite self explanatory,
|
||||
open it in a text editor to edit it. Electrode names are specified in the header
|
||||
section of the matrix, while actual coordinates are stored in the buffer section.
|
||||
|
||||
.. _Doc_BoxAlgorithm_ElectrodeLocalisationFileReader_Miscellaneous:
|
||||
|
||||
Miscellaneous
|
||||
-------------
|
||||
|
||||
The coordinates loaded by this box are sent once only (static coordinates, e.g. EEG), as
|
||||
opposed to regularly (dynamic coordinates, e.g. MEG).
|
||||
The supported file format is the :ref:`Doc_MatrixFileFormat` "NeuroRT matrix" file format.
|
||||
Electrode coordinates must be normalized cartesian coordinates in the following frame
|
||||
of reference : X right, Y front and Z up.
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
.. _Doc_BoxAlgorithm_GenericStreamReader:
|
||||
|
||||
Generic stream reader
|
||||
=====================
|
||||
|
||||
.. container:: attribution
|
||||
|
||||
:Author:
|
||||
Yann Renard
|
||||
:Company:
|
||||
INRIA
|
||||
|
||||
.. image:: images/Doc_BoxAlgorithm_GenericStreamReader.png
|
||||
|
||||
Generic Stream Writer box can be used to store data in the format read by this box
|
||||
|
||||
This box is able to read any file saved with the :ref:`Doc_BoxAlgorithm_GenericStreamWriter` box.
|
||||
It is interesting to notice that such file can contain a variable number of streams. Therefore,
|
||||
the user is able to add and modify any output he wants on the box. The box does not supposes
|
||||
anything on the streams contained in the file during authoring in the designer. The streams of
|
||||
the file are mapped to created output at runtime in an "intelligent way" depending on there types.
|
||||
|
||||
Outputs
|
||||
-------
|
||||
|
||||
.. csv-table::
|
||||
:header: "Output Name", "Stream Type"
|
||||
|
||||
"Output Signal", "Signal"
|
||||
"Output Stimulations", "Stimulations"
|
||||
|
||||
This box can have as many output you want depending on the content of the file.
|
||||
|
||||
Output Signal
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
This is the default output.
|
||||
|
||||
.. _Doc_BoxAlgorithm_GenericStreamReader_Settings:
|
||||
|
||||
Settings
|
||||
--------
|
||||
|
||||
.. csv-table::
|
||||
:header: "Setting Name", "Type", "Default Value"
|
||||
|
||||
"Filename", "Filename", ""
|
||||
|
||||
Filename
|
||||
~~~~~~~~
|
||||
|
||||
This setting points to the file to read. This file may contain a variable number of multiplexed
|
||||
streams. Those streams will be mapped to corresponding outputs at runtime. The mapping is done in
|
||||
an "intelligent way" because it reorders the streams contained in the file to match the types of
|
||||
the outputs. If a stream from the file does not find a matching output, a warning is launched.
|
||||
If an output does not find a matching stream from the file, a warning is also launched.
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
.. _Doc_BoxAlgorithm_GenericStreamWriter:
|
||||
|
||||
Generic stream writer
|
||||
=====================
|
||||
|
||||
.. container:: attribution
|
||||
|
||||
:Author:
|
||||
Yann Renard
|
||||
:Company:
|
||||
INRIA
|
||||
|
||||
.. image:: images/Doc_BoxAlgorithm_GenericStreamWriter.png
|
||||
|
||||
This box is able to dump any OpenViBE stream into a binary file. In the cacse where this box
|
||||
would have multiple inputs, the streams would be multiplexed in the file. Such file can
|
||||
be read back with the :ref:`Doc_BoxAlgorithm_GenericStreamReader`
|
||||
|
||||
Inputs
|
||||
------
|
||||
|
||||
.. csv-table::
|
||||
:header: "Input Name", "Stream Type"
|
||||
|
||||
"Input Signal", "Signal"
|
||||
"Input Stimulations", "Stimulations"
|
||||
|
||||
You can add any input you want to this box depending on the number of streams you want to dump.
|
||||
In the cacse where this box would have multiple inputs, the streams would be multiplexed in the file.
|
||||
|
||||
Input Signal
|
||||
~~~~~~~~~~~~
|
||||
|
||||
The default input.
|
||||
|
||||
**Note: it important to correctly configure the type of the inputs**. That information will be
|
||||
used by the :ref:`Doc_BoxAlgorithm_GenericStreamReader` to map the contained streams to its outputs.
|
||||
|
||||
.. _Doc_BoxAlgorithm_GenericStreamWriter_Settings:
|
||||
|
||||
Settings
|
||||
--------
|
||||
|
||||
.. csv-table::
|
||||
:header: "Setting Name", "Type", "Default Value"
|
||||
|
||||
"Filename", "Filename", ""
|
||||
"Use compression", "Boolean", "false"
|
||||
|
||||
Filename
|
||||
~~~~~~~~
|
||||
|
||||
This setting points to the file to write the streams to.
|
||||
|
||||
|
||||
- Default value : [ *record-[$core{date}-$core{time}].ov* ]
|
||||
|
||||
|
||||
Use compression
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Thanks to this setting, you can use compression on each input stream. This means that the basic
|
||||
structure of the file remains uncompressed but that each stream inside this structure is compressed.
|
||||
**Note: this is not implemented at the moment**.
|
||||
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 31 KiB |
@@ -0,0 +1,232 @@
|
||||
[
|
||||
[ "O1" "O2" "T5" "P7" "P3" "Pz" "P4" "T6" "P8" "T3" "T7" "C3" "Cz" "C4" "T4" "T8" "F7" "F3" "Fz" "F4" "F8" "Fp1" "Fp2" "P5" "P1" "P2" "P6" "C5" "C1" "C2" "C6" "F5" "F1" "F2" "F6" "M1" "M2" "Oz" "POz" "CPz" "FCz" "FFz" "Fpz" "FC1" "FC2" "CP1" "CP2" "FC3" "FC4" "CP3" "CP4" "FT3" "FT4" "TP3" "TP4" "P'1" "P'2" "Nz" "I1" "Iz" "I2" "FF3" "TP5" "OT5" "IT1" "IT2" "IP1" "IPz" "IP2" "M'1" "M'2" "TP7" "TP8" "Nez" "F3z" "F4z" ]
|
||||
["theta" "phi" ]
|
||||
]
|
||||
[
|
||||
[90 252 ]
|
||||
]
|
||||
[
|
||||
[90 288 ]
|
||||
]
|
||||
[
|
||||
[90 216 ]
|
||||
]
|
||||
[
|
||||
[63.1 223.1 ]
|
||||
]
|
||||
[
|
||||
[63.1 223.1 ]
|
||||
]
|
||||
[
|
||||
[45 270 ]
|
||||
]
|
||||
[
|
||||
[63.1 316.9 ]
|
||||
]
|
||||
[
|
||||
[90 324 ]
|
||||
]
|
||||
[
|
||||
[90 324 ]
|
||||
]
|
||||
[
|
||||
[90 180 ]
|
||||
]
|
||||
[
|
||||
[90 180 ]
|
||||
]
|
||||
[
|
||||
[45 180 ]
|
||||
]
|
||||
[
|
||||
[0 0 ]
|
||||
]
|
||||
[
|
||||
[45 0 ]
|
||||
]
|
||||
[
|
||||
[90 0 ]
|
||||
]
|
||||
[
|
||||
[90 0 ]
|
||||
]
|
||||
[
|
||||
[90 144 ]
|
||||
]
|
||||
[
|
||||
[63.1 136.9 ]
|
||||
]
|
||||
[
|
||||
[45 90 ]
|
||||
]
|
||||
[
|
||||
[63.1 43.1 ]
|
||||
]
|
||||
[
|
||||
[90 36 ]
|
||||
]
|
||||
[
|
||||
[90 108 ]
|
||||
]
|
||||
[
|
||||
[90 72 ]
|
||||
]
|
||||
[
|
||||
[76.5 219.3 ]
|
||||
]
|
||||
[
|
||||
[51.7 243.7 ]
|
||||
]
|
||||
[
|
||||
[51.7 296.3 ]
|
||||
]
|
||||
[
|
||||
[76.5 320.7 ]
|
||||
]
|
||||
[
|
||||
[67.5 180 ]
|
||||
]
|
||||
[
|
||||
[22.5 180 ]
|
||||
]
|
||||
[
|
||||
[22.5 0 ]
|
||||
]
|
||||
[
|
||||
[67.5 0 ]
|
||||
]
|
||||
[
|
||||
[76.5 140.7 ]
|
||||
]
|
||||
[
|
||||
[51.7 116.3 ]
|
||||
]
|
||||
[
|
||||
[51.7 63.7 ]
|
||||
]
|
||||
[
|
||||
[76.5 39.3 ]
|
||||
]
|
||||
[
|
||||
[120 215.6 ]
|
||||
]
|
||||
[
|
||||
[120 324.4 ]
|
||||
]
|
||||
[
|
||||
[90 270 ]
|
||||
]
|
||||
[
|
||||
[67.5 270 ]
|
||||
]
|
||||
[
|
||||
[22.5 270 ]
|
||||
]
|
||||
[
|
||||
[22.5 90 ]
|
||||
]
|
||||
[
|
||||
[67.5 90 ]
|
||||
]
|
||||
[
|
||||
[90 90 ]
|
||||
]
|
||||
[
|
||||
[31.5 136.9 ]
|
||||
]
|
||||
[
|
||||
[31.5 43.1 ]
|
||||
]
|
||||
[
|
||||
[31.5 223.1 ]
|
||||
]
|
||||
[
|
||||
[31.5 316.9 ]
|
||||
]
|
||||
[
|
||||
[52.1 155.8 ]
|
||||
]
|
||||
[
|
||||
[52.1 24.2 ]
|
||||
]
|
||||
[
|
||||
[52.1 204.2 ]
|
||||
]
|
||||
[
|
||||
[52.1 335.8 ]
|
||||
]
|
||||
[
|
||||
[75.6 159.7 ]
|
||||
]
|
||||
[
|
||||
[75.6 20.3 ]
|
||||
]
|
||||
[
|
||||
[75.6 200.3 ]
|
||||
]
|
||||
[
|
||||
[75.6 339.7 ]
|
||||
]
|
||||
[
|
||||
[52 241.3 ]
|
||||
]
|
||||
[
|
||||
[52 298.7 ]
|
||||
]
|
||||
[
|
||||
[112.5 90 ]
|
||||
]
|
||||
[
|
||||
[120 234.4 ]
|
||||
]
|
||||
[
|
||||
[112.5 270 ]
|
||||
]
|
||||
[
|
||||
[120 305.6 ]
|
||||
]
|
||||
[
|
||||
[33.2 135.6 ]
|
||||
]
|
||||
[
|
||||
[66.5 201.2 ]
|
||||
]
|
||||
[
|
||||
[77.4 241.8 ]
|
||||
]
|
||||
[
|
||||
[102.6 241.8 ]
|
||||
]
|
||||
[
|
||||
[102.6 298.2 ]
|
||||
]
|
||||
[
|
||||
[87.6 247 ]
|
||||
]
|
||||
[
|
||||
[78.8 270 ]
|
||||
]
|
||||
[
|
||||
[87.6 293 ]
|
||||
]
|
||||
[
|
||||
[105.4 203 ]
|
||||
]
|
||||
[
|
||||
[105.4 337 ]
|
||||
]
|
||||
[
|
||||
[90 198 ]
|
||||
]
|
||||
[
|
||||
[90 342 ]
|
||||
]
|
||||
[
|
||||
[118.1 90 ]
|
||||
]
|
||||
[
|
||||
[63.4 113 ]
|
||||
]
|
||||
[
|
||||
[63.4 67 ]
|
||||
]
|
||||
@@ -0,0 +1,31 @@
|
||||
#include "ovpCAlgorithmOVMatrixFileReader.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace FileIO {
|
||||
|
||||
bool CAlgorithmOVMatrixFileReader::initialize()
|
||||
{
|
||||
ip_sFilename.initialize(getInputParameter(OVP_Algorithm_OVMatrixFileReader_InputParameterId_Filename));
|
||||
op_pMatrix.initialize(getOutputParameter(OVP_Algorithm_OVMatrixFileReader_OutputParameterId_Matrix));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmOVMatrixFileReader::uninitialize()
|
||||
{
|
||||
op_pMatrix.uninitialize();
|
||||
ip_sFilename.uninitialize();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmOVMatrixFileReader::process()
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(Toolkit::Matrix::loadFromTextFile(*op_pMatrix, ip_sFilename->toASCIIString()),
|
||||
"Reading matrix file " << *ip_sFilename << " failed", Kernel::ErrorType::BadFileRead);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace FileIO
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace FileIO {
|
||||
class CAlgorithmOVMatrixFileReader final : public Toolkit::TAlgorithm<IAlgorithm>
|
||||
{
|
||||
public:
|
||||
void release() override { delete this; }
|
||||
bool initialize() override;
|
||||
bool uninitialize() override;
|
||||
bool process() override;
|
||||
|
||||
_IsDerivedFromClass_Final_(Toolkit::TAlgorithm<IAlgorithm>, OVP_ClassId_Algorithm_OVMatrixFileReader)
|
||||
|
||||
protected:
|
||||
|
||||
Kernel::TParameterHandler<CString*> ip_sFilename;
|
||||
Kernel::TParameterHandler<CMatrix*> op_pMatrix;
|
||||
};
|
||||
|
||||
class CAlgorithmOVMatrixFileReaderDesc final : public IAlgorithmDesc
|
||||
{
|
||||
public:
|
||||
void release() override { }
|
||||
CString getName() const override { return CString("OpenViBE Matrix file reader"); }
|
||||
CString getAuthorName() const override { return CString("Vincent Delannoy"); }
|
||||
CString getAuthorCompanyName() const override { return CString("INRIA/IRISA"); }
|
||||
CString getShortDescription() const override { return CString(""); }
|
||||
CString getDetailedDescription() const override { return CString(""); }
|
||||
CString getCategory() const override { return CString("File reading and writing"); }
|
||||
CString getVersion() const override { return CString("1.1"); }
|
||||
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
|
||||
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_Algorithm_OVMatrixFileReader; }
|
||||
IPluginObject* create() override { return new CAlgorithmOVMatrixFileReader; }
|
||||
|
||||
bool getAlgorithmPrototype(Kernel::IAlgorithmProto& prototype) const override
|
||||
{
|
||||
prototype.addInputParameter(OVP_Algorithm_OVMatrixFileReader_InputParameterId_Filename, "Filename", Kernel::ParameterType_String);
|
||||
prototype.addOutputParameter(OVP_Algorithm_OVMatrixFileReader_OutputParameterId_Matrix, "Matrix", Kernel::ParameterType_Matrix);
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(IAlgorithmDesc, OVP_ClassId_Algorithm_OVMatrixFileReaderDesc)
|
||||
};
|
||||
} // namespace FileIO
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,31 @@
|
||||
#include "ovpCAlgorithmOVMatrixFileWriter.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace FileIO {
|
||||
|
||||
bool CAlgorithmOVMatrixFileWriter::initialize()
|
||||
{
|
||||
ip_sFilename.initialize(getInputParameter(OVP_Algorithm_OVMatrixFileWriter_InputParameterId_Filename));
|
||||
ip_pMatrix.initialize(getInputParameter(OVP_Algorithm_OVMatrixFileWriter_InputParameterId_Matrix));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmOVMatrixFileWriter::uninitialize()
|
||||
{
|
||||
ip_sFilename.uninitialize();
|
||||
ip_pMatrix.uninitialize();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmOVMatrixFileWriter::process()
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(Toolkit::Matrix::saveToTextFile(*ip_pMatrix, ip_sFilename->toASCIIString()),
|
||||
"Writing matrix file " << *ip_sFilename << " failed", Kernel::ErrorType::BadFileWrite);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace FileIO
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace FileIO {
|
||||
class CAlgorithmOVMatrixFileWriter final : public Toolkit::TAlgorithm<IAlgorithm>
|
||||
{
|
||||
public:
|
||||
void release() override { delete this; }
|
||||
bool initialize() override;
|
||||
bool uninitialize() override;
|
||||
bool process() override;
|
||||
|
||||
_IsDerivedFromClass_Final_(Toolkit::TAlgorithm<IAlgorithm>, OVP_ClassId_Algorithm_OVMatrixFileWriter)
|
||||
|
||||
protected:
|
||||
|
||||
Kernel::TParameterHandler<CString*> ip_sFilename;
|
||||
Kernel::TParameterHandler<CMatrix*> ip_pMatrix;
|
||||
};
|
||||
|
||||
class CAlgorithmOVMatrixFileWriterDesc final : public IAlgorithmDesc
|
||||
{
|
||||
public:
|
||||
void release() override { }
|
||||
CString getName() const override { return CString("OpenViBE Matrix file writer"); }
|
||||
CString getAuthorName() const override { return CString("Vincent Delannoy"); }
|
||||
CString getAuthorCompanyName() const override { return CString("INRIA/IRISA"); }
|
||||
CString getShortDescription() const override { return CString(""); }
|
||||
CString getDetailedDescription() const override { return CString(""); }
|
||||
CString getCategory() const override { return CString("File reading and writing"); }
|
||||
CString getVersion() const override { return CString("1.1"); }
|
||||
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
|
||||
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_Algorithm_OVMatrixFileWriter; }
|
||||
IPluginObject* create() override { return new CAlgorithmOVMatrixFileWriter; }
|
||||
|
||||
bool getAlgorithmPrototype(Kernel::IAlgorithmProto& prototype) const override
|
||||
{
|
||||
prototype.addInputParameter(OVP_Algorithm_OVMatrixFileWriter_InputParameterId_Filename, "Filename", Kernel::ParameterType_String);
|
||||
prototype.addInputParameter(OVP_Algorithm_OVMatrixFileWriter_InputParameterId_Matrix, "Matrix", Kernel::ParameterType_Matrix);
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(IAlgorithmDesc, OVP_ClassId_Algorithm_OVMatrixFileWriterDesc)
|
||||
};
|
||||
} // namespace FileIO
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,161 @@
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include "ovpCAlgorithmXMLScenarioExporter.h"
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace FileIO {
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
CAlgorithmXMLScenarioExporter::CAlgorithmXMLScenarioExporter() { m_writer = createWriter(*this); }
|
||||
CAlgorithmXMLScenarioExporter::~CAlgorithmXMLScenarioExporter() { m_writer->release(); }
|
||||
|
||||
void CAlgorithmXMLScenarioExporter::write(const char* str) { m_pMemoryBuffer->append(reinterpret_cast<const uint8_t*>(str), strlen(str)); }
|
||||
|
||||
bool CAlgorithmXMLScenarioExporter::exportStart(IMemoryBuffer& memoryBuffer, const CIdentifier& id)
|
||||
{
|
||||
m_pMemoryBuffer = &memoryBuffer;
|
||||
|
||||
CString name;
|
||||
|
||||
if (id == OVTK_Algorithm_ScenarioExporter_NodeId_OpenViBEScenario) { name = "OpenViBE-Scenario"; }
|
||||
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Settings) { name = "Settings"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Setting) { name = "Setting"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Setting_ID) { name = "Identifier"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Setting_TypeID) { name = "TypeIdentifier"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Setting_Name) { name = "Name"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Setting_DefaultValue) { name = "DefaultValue"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Setting_Value) { name = "Value"; }
|
||||
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Inputs) { name = "Inputs"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Input) { name = "Input"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Input_ID) { name = "Identifier"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Input_TypeID) { name = "TypeIdentifier"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Input_Name) { name = "Name"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Input_LinkedBoxID) { name = "LinkedBoxIdentifier"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Input_LinkedBoxInputIdx) { name = "LinkedBoxInputIndex"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Input_LinkedBoxInputID) { name = "LinkedBoxInputIdentifier"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Outputs) { name = "Outputs"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Output) { name = "Output"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Output_ID) { name = "Identifier"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Output_TypeID) { name = "TypeIdentifier"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Output_Name) { name = "Name"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Output_LinkedBoxID) { name = "LinkedBoxIdentifier"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Output_LinkedBoxOutputIdx) { name = "LinkedBoxOutputIndex"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Output_LinkedBoxOutputID) { name = "LinkedBoxOutputIdentifier"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_FormatVersion) { name = "FormatVersion"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Creator) { name = "Creator"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_CreatorVersion) { name = "CreatorVersion"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Boxes) { name = "Boxes"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Box) { name = "Box"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Box_ID) { name = "Identifier"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Box_Name) { name = "Name"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Box_AlgorithmClassIdD) { name = "AlgorithmClassIdentifier"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Box_Inputs) { name = "Inputs"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Box_Input) { name = "Input"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Box_Input_ID) { name = "Identifier"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Box_Input_TypeID) { name = "TypeIdentifier"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Box_Input_Name) { name = "Name"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Box_Outputs) { name = "Outputs"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Box_Output) { name = "Output"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Box_Output_ID) { name = "Identifier"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Box_Output_TypeID) { name = "TypeIdentifier"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Box_Output_Name) { name = "Name"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Box_Settings) { name = "Settings"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Box_Setting) { name = "Setting"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Box_Setting_ID) { name = "Identifier"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Box_Setting_TypeID) { name = "TypeIdentifier"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Box_Setting_Name) { name = "Name"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Box_Setting_DefaultValue) { name = "DefaultValue"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Box_Setting_Value) { name = "Value"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Box_Setting_Modifiability) { name = "Modifiability"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Box_Attributes) { name = "Attributes"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Box_Attribute) { name = "Attribute"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Box_Attribute_ID) { name = "Identifier"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Box_Attribute_Value) { name = "Value"; }
|
||||
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Comments) { name = "Comments"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Comment) { name = "Comment"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Comment_ID) { name = "Identifier"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Comment_Text) { name = "Text"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Comment_Attributes) { name = "Attributes"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Comment_Attribute) { name = "Attribute"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Comment_Attribute_ID) { name = "Identifier"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Comment_Attribute_Value) { name = "Value"; }
|
||||
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Metadata) { name = "Metadata"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_MetadataEntry) { name = "Entry"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_MetadataEntry_ID) { name = "Identifier"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_MetadataEntry_Type) { name = "Type"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_MetadataEntry_Data) { name = "Data"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Links) { name = "Links"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Link) { name = "Link"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Link_ID) { name = "Identifier"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Link_Source) { name = "Source"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Link_Source_BoxID) { name = "BoxIdentifier"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Link_Source_BoxOutputIdx) { name = "BoxOutputIndex"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Link_Source_BoxOutputID) { name = "BoxOutputIdentifier"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Link_Target) { name = "Target"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Link_Target_BoxID) { name = "BoxIdentifier"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Link_Target_BoxInputIdx) { name = "BoxInputIndex"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Link_Target_BoxInputID) { name = "BoxInputIdentifier"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Link_Attributes) { name = "Attributes"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Link_Attribute) { name = "Attribute"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Link_Attribute_ID) { name = "Identifier"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Link_Attribute_Value) { name = "Value"; }
|
||||
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Attributes) { name = "Attributes"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Attribute) { name = "Attribute"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Attribute_ID) { name = "Identifier"; }
|
||||
else if (id == OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Attribute_Value) { name = "Value"; }
|
||||
//
|
||||
else { OV_ERROR_KRF("(start) Unexpected node identifier " << id.str(), Kernel::ErrorType::BadArgument); }
|
||||
|
||||
m_writer->openChild(name.toASCIIString());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmXMLScenarioExporter::exportIdentifier(IMemoryBuffer& memoryBuffer, const CIdentifier& id, const CIdentifier& value)
|
||||
{
|
||||
m_pMemoryBuffer = &memoryBuffer;
|
||||
OV_ERROR_UNLESS_KRF(this->exportStart(memoryBuffer, id), "Exporting identifier failed", Kernel::ErrorType::Internal);
|
||||
m_writer->setChildData(value.str().c_str());
|
||||
this->exportStop(memoryBuffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmXMLScenarioExporter::exportString(IMemoryBuffer& memoryBuffer, const CIdentifier& id, const CString& value)
|
||||
{
|
||||
m_pMemoryBuffer = &memoryBuffer;
|
||||
OV_ERROR_UNLESS_KRF(this->exportStart(memoryBuffer, id), "Exporting string failed", Kernel::ErrorType::Internal);
|
||||
m_writer->setChildData(value.toASCIIString());
|
||||
this->exportStop(memoryBuffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmXMLScenarioExporter::exportUInteger(IMemoryBuffer& memoryBuffer, const CIdentifier& id, const uint64_t value)
|
||||
{
|
||||
m_pMemoryBuffer = &memoryBuffer;
|
||||
OV_ERROR_UNLESS_KRF(this->exportStart(memoryBuffer, id), "Exporting uint failed", Kernel::ErrorType::Internal);
|
||||
m_writer->setChildData(std::to_string(value).c_str());
|
||||
this->exportStop(memoryBuffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmXMLScenarioExporter::exportStop(IMemoryBuffer& memoryBuffer)
|
||||
{
|
||||
m_pMemoryBuffer = &memoryBuffer;
|
||||
m_writer->closeChild();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace FileIO
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../ovp_defines.h"
|
||||
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
#include <xml/IWriter.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace FileIO {
|
||||
class CAlgorithmXMLScenarioExporter final : public Toolkit::CAlgorithmScenarioExporter, public XML::IWriterCallback
|
||||
{
|
||||
public:
|
||||
|
||||
CAlgorithmXMLScenarioExporter();
|
||||
~CAlgorithmXMLScenarioExporter() override;
|
||||
bool exportStart(IMemoryBuffer& memoryBuffer, const CIdentifier& id) override;
|
||||
bool exportIdentifier(IMemoryBuffer& memoryBuffer, const CIdentifier& id, const CIdentifier& value) override;
|
||||
bool exportString(IMemoryBuffer& memoryBuffer, const CIdentifier& id, const CString& value) override;
|
||||
bool exportUInteger(IMemoryBuffer& memoryBuffer, const CIdentifier& id, uint64_t value) override;
|
||||
bool exportStop(IMemoryBuffer& memoryBuffer) override;
|
||||
|
||||
_IsDerivedFromClass_Final_(Toolkit::CAlgorithmScenarioExporter, OVP_ClassId_Algorithm_XMLScenarioExporter)
|
||||
|
||||
protected:
|
||||
void write(const char* str) override; // XML::IWriterCallback
|
||||
|
||||
XML::IWriter* m_writer = nullptr;
|
||||
IMemoryBuffer* m_pMemoryBuffer = nullptr;
|
||||
};
|
||||
|
||||
class CAlgorithmXMLScenarioExporterDesc final : public Toolkit::CAlgorithmScenarioExporterDesc
|
||||
{
|
||||
public:
|
||||
void release() override { }
|
||||
CString getName() const override { return CString("XML Scenario exporter"); }
|
||||
CString getAuthorName() const override { return CString("Yann Renard"); }
|
||||
CString getAuthorCompanyName() const override { return CString("INRIA/IRISA"); }
|
||||
CString getShortDescription() const override { return CString("A sample XML scenario exporter"); }
|
||||
|
||||
CString getDetailedDescription() const override { return CString("This scenario exporter uses simple XML format to output the scenario"); }
|
||||
|
||||
CString getCategory() const override { return CString("File reading and writing/XML Scenario"); }
|
||||
CString getVersion() const override { return CString("1.0"); }
|
||||
// virtual CString getFileExtension() const { return CString("xml;XML"); }
|
||||
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
|
||||
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_Algorithm_XMLScenarioExporter; }
|
||||
IPluginObject* create() override { return new CAlgorithmXMLScenarioExporter(); }
|
||||
|
||||
_IsDerivedFromClass_Final_(Toolkit::CAlgorithmScenarioExporterDesc, OVP_ClassId_Algorithm_XMLScenarioExporterDesc)
|
||||
};
|
||||
} // namespace FileIO
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,469 @@
|
||||
#include "ovpCAlgorithmXMLScenarioImporter.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
#include <xercesc/parsers/XercesDOMParser.hpp>
|
||||
#include <xercesc/dom/DOM.hpp>
|
||||
#include <xercesc/sax/HandlerBase.hpp>
|
||||
#include <xercesc/util/PlatformUtils.hpp>
|
||||
#include <xercesc/framework/MemBufInputSource.hpp>
|
||||
#include <xercesc/validators/common/Grammar.hpp>
|
||||
|
||||
XERCES_CPP_NAMESPACE_USE
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace FileIO {
|
||||
|
||||
namespace {
|
||||
class _AutoBind_
|
||||
{
|
||||
public:
|
||||
explicit _AutoBind_(const std::string& value) : m_value(value) { }
|
||||
operator CString() const { return CString(m_value.c_str()); }
|
||||
|
||||
operator CIdentifier() const
|
||||
{
|
||||
CIdentifier res;
|
||||
res.fromString(m_value.c_str());
|
||||
return res;
|
||||
}
|
||||
|
||||
operator size_t() { return atoi(m_value.c_str()); }
|
||||
protected:
|
||||
const std::string& m_value;
|
||||
};
|
||||
|
||||
std::string xercesToString(const XMLCh* xercesString)
|
||||
{
|
||||
const std::unique_ptr<char[]> charArray(XMLString::transcode(xercesString));
|
||||
return std::string(charArray.get());
|
||||
}
|
||||
|
||||
class CErrorHandler final : public HandlerBase
|
||||
{
|
||||
public:
|
||||
|
||||
explicit CErrorHandler(Kernel::IAlgorithmContext& algorithmCtx)
|
||||
: m_algorithmContext(algorithmCtx) { }
|
||||
|
||||
void fatalError(const SAXParseException& exception) override { this->error(exception); }
|
||||
|
||||
void error(const SAXParseException& exception) override
|
||||
{
|
||||
// we just issue a trace here because the calling method
|
||||
// implements a fallback mechanism and we don't want to populate
|
||||
// the error manager if the importer returns gracefully.
|
||||
m_algorithmContext.getLogManager() << Kernel::LogLevel_Trace << "Failed to validate xml: error [" << xercesToString(exception.getMessage())
|
||||
<< "], line number [" << size_t(exception.getLineNumber()) << "]" << "\n";
|
||||
}
|
||||
|
||||
void warning(const SAXParseException& exception) override
|
||||
{
|
||||
OV_WARNING("Warning while validating xml: warning [" << xercesToString(exception.getMessage()) << "], line number ["
|
||||
<< size_t(exception.getLineNumber()) << "]", m_algorithmContext.getLogManager());
|
||||
}
|
||||
|
||||
private:
|
||||
Kernel::IAlgorithmContext& m_algorithmContext;
|
||||
};
|
||||
} //namespace
|
||||
|
||||
CAlgorithmXMLScenarioImporter::CAlgorithmXMLScenarioImporter() { m_reader = createReader(*this); }
|
||||
CAlgorithmXMLScenarioImporter::~CAlgorithmXMLScenarioImporter() { m_reader->release(); }
|
||||
|
||||
void CAlgorithmXMLScenarioImporter::openChild(const char* name, const char** /*attributeName*/, const char** /*attributeValue*/, const size_t /*nAttribute*/)
|
||||
{
|
||||
m_nodes.push(name);
|
||||
|
||||
std::string& top = m_nodes.top();
|
||||
|
||||
if (top == "OpenViBE-Scenario" && m_status == EParsingStatus::Nothing)
|
||||
{
|
||||
m_status = EParsingStatus::Scenario;
|
||||
m_ctx->processStart(OVTK_Algorithm_ScenarioExporter_NodeId_OpenViBEScenario);
|
||||
}
|
||||
else if (top == "Attribute" && m_status == EParsingStatus::Scenario)
|
||||
{
|
||||
m_status = EParsingStatus::ScenarioAttribute;
|
||||
m_ctx->processStart(OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Attribute);
|
||||
}
|
||||
else if (top == "Setting" && m_status == EParsingStatus::Scenario)
|
||||
{
|
||||
m_status = EParsingStatus::ScenarioSetting;
|
||||
m_ctx->processStart(OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Setting);
|
||||
}
|
||||
else if (top == "Input" && m_status == EParsingStatus::Scenario)
|
||||
{
|
||||
m_status = EParsingStatus::ScenarioInput;
|
||||
m_ctx->processStart(OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Input);
|
||||
}
|
||||
else if (top == "Output" && m_status == EParsingStatus::Scenario)
|
||||
{
|
||||
m_status = EParsingStatus::ScenarioOutput;
|
||||
m_ctx->processStart(OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Output);
|
||||
}
|
||||
|
||||
else if (top == "Box" && m_status == EParsingStatus::Scenario)
|
||||
{
|
||||
m_status = EParsingStatus::Box;
|
||||
m_ctx->processStart(OVTK_Algorithm_ScenarioExporter_NodeId_Box);
|
||||
}
|
||||
else if (top == "Input" && m_status == EParsingStatus::Box)
|
||||
{
|
||||
m_status = EParsingStatus::BoxInput;
|
||||
m_ctx->processStart(OVTK_Algorithm_ScenarioExporter_NodeId_Box_Input);
|
||||
}
|
||||
else if (top == "Output" && m_status == EParsingStatus::Box)
|
||||
{
|
||||
m_status = EParsingStatus::BoxOutput;
|
||||
m_ctx->processStart(OVTK_Algorithm_ScenarioExporter_NodeId_Box_Output);
|
||||
}
|
||||
else if (top == "Setting" && m_status == EParsingStatus::Box)
|
||||
{
|
||||
m_status = EParsingStatus::BoxSetting;
|
||||
m_ctx->processStart(OVTK_Algorithm_ScenarioExporter_NodeId_Box_Setting);
|
||||
}
|
||||
else if (top == "Attribute" && m_status == EParsingStatus::Box)
|
||||
{
|
||||
m_status = EParsingStatus::BoxAttribute;
|
||||
m_ctx->processStart(OVTK_Algorithm_ScenarioExporter_NodeId_Box_Attribute);
|
||||
}
|
||||
|
||||
else if (top == "Comment" && m_status == EParsingStatus::Scenario)
|
||||
{
|
||||
m_status = EParsingStatus::Comment;
|
||||
m_ctx->processStart(OVTK_Algorithm_ScenarioExporter_NodeId_Comment);
|
||||
}
|
||||
else if (top == "Attribute" && m_status == EParsingStatus::Comment)
|
||||
{
|
||||
m_status = EParsingStatus::CommentAttribute;
|
||||
m_ctx->processStart(OVTK_Algorithm_ScenarioExporter_NodeId_Comment_Attribute);
|
||||
}
|
||||
|
||||
else if (top == "Entry" && m_status == EParsingStatus::Scenario)
|
||||
{
|
||||
m_status = EParsingStatus::MetadataEntry;
|
||||
m_ctx->processStart(OVTK_Algorithm_ScenarioExporter_NodeId_MetadataEntry);
|
||||
}
|
||||
|
||||
else if (top == "Link" && m_status == EParsingStatus::Scenario)
|
||||
{
|
||||
m_status = EParsingStatus::Link;
|
||||
m_ctx->processStart(OVTK_Algorithm_ScenarioExporter_NodeId_Link);
|
||||
}
|
||||
else if (top == "Source" && m_status == EParsingStatus::Link)
|
||||
{
|
||||
m_status = EParsingStatus::LinkSource;
|
||||
m_ctx->processStart(OVTK_Algorithm_ScenarioExporter_NodeId_Link_Source);
|
||||
}
|
||||
else if (top == "Target" && m_status == EParsingStatus::Link)
|
||||
{
|
||||
m_status = EParsingStatus::LinkTarget;
|
||||
m_ctx->processStart(OVTK_Algorithm_ScenarioExporter_NodeId_Link_Target);
|
||||
}
|
||||
else if (top == "Attribute" && m_status == EParsingStatus::Link)
|
||||
{
|
||||
m_status = EParsingStatus::LinkAttribute;
|
||||
m_ctx->processStart(OVTK_Algorithm_ScenarioExporter_NodeId_Link_Attribute);
|
||||
}
|
||||
}
|
||||
|
||||
void CAlgorithmXMLScenarioImporter::processChildData(const char* data)
|
||||
{
|
||||
std::string& top = m_nodes.top();
|
||||
|
||||
switch (m_status)
|
||||
{
|
||||
case EParsingStatus::Box:
|
||||
if (top == "Identifier") { m_ctx->processIdentifier(OVTK_Algorithm_ScenarioExporter_NodeId_Box_ID, _AutoBind_(data)); }
|
||||
if (top == "AlgorithmClassIdentifier") { m_ctx->processIdentifier(OVTK_Algorithm_ScenarioExporter_NodeId_Box_AlgorithmClassIdD, _AutoBind_(data)); }
|
||||
if (top == "Name") { m_ctx->processString(OVTK_Algorithm_ScenarioExporter_NodeId_Box_Name, _AutoBind_(data)); }
|
||||
break;
|
||||
case EParsingStatus::BoxInput:
|
||||
if (top == "Identifier") { m_ctx->processIdentifier(OVTK_Algorithm_ScenarioExporter_NodeId_Box_Input_ID, _AutoBind_(data)); }
|
||||
if (top == "TypeIdentifier") { m_ctx->processIdentifier(OVTK_Algorithm_ScenarioExporter_NodeId_Box_Input_TypeID, _AutoBind_(data)); }
|
||||
if (top == "Name") { m_ctx->processString(OVTK_Algorithm_ScenarioExporter_NodeId_Box_Input_Name, _AutoBind_(data)); }
|
||||
break;
|
||||
case EParsingStatus::BoxOutput:
|
||||
if (top == "Identifier") { m_ctx->processIdentifier(OVTK_Algorithm_ScenarioExporter_NodeId_Box_Output_ID, _AutoBind_(data)); }
|
||||
if (top == "TypeIdentifier") { m_ctx->processIdentifier(OVTK_Algorithm_ScenarioExporter_NodeId_Box_Output_TypeID, _AutoBind_(data)); }
|
||||
if (top == "Name") { m_ctx->processString(OVTK_Algorithm_ScenarioExporter_NodeId_Box_Output_Name, _AutoBind_(data)); }
|
||||
break;
|
||||
case EParsingStatus::BoxSetting:
|
||||
if (top == "Identifier") { m_ctx->processIdentifier(OVTK_Algorithm_ScenarioExporter_NodeId_Box_Setting_ID, _AutoBind_(data)); }
|
||||
if (top == "TypeIdentifier") { m_ctx->processIdentifier(OVTK_Algorithm_ScenarioExporter_NodeId_Box_Setting_TypeID, _AutoBind_(data)); }
|
||||
if (top == "Name") { m_ctx->processString(OVTK_Algorithm_ScenarioExporter_NodeId_Box_Setting_Name, _AutoBind_(data)); }
|
||||
if (top == "DefaultValue") { m_ctx->processString(OVTK_Algorithm_ScenarioExporter_NodeId_Box_Setting_DefaultValue, _AutoBind_(data)); }
|
||||
if (top == "Value") { m_ctx->processString(OVTK_Algorithm_ScenarioExporter_NodeId_Box_Setting_Value, _AutoBind_(data)); }
|
||||
if (top == "Modifiability") { m_ctx->processString(OVTK_Algorithm_ScenarioExporter_NodeId_Box_Setting_Modifiability, _AutoBind_(data)); }
|
||||
break;
|
||||
case EParsingStatus::BoxAttribute:
|
||||
if (top == "Identifier") { m_ctx->processIdentifier(OVTK_Algorithm_ScenarioExporter_NodeId_Box_Attribute_ID, _AutoBind_(data)); }
|
||||
if (top == "Value") { m_ctx->processString(OVTK_Algorithm_ScenarioExporter_NodeId_Box_Attribute_Value, _AutoBind_(data)); }
|
||||
break;
|
||||
|
||||
case EParsingStatus::Comment:
|
||||
if (top == "Identifier") { m_ctx->processIdentifier(OVTK_Algorithm_ScenarioExporter_NodeId_Comment_ID, _AutoBind_(data)); }
|
||||
if (top == "Text") { m_ctx->processString(OVTK_Algorithm_ScenarioExporter_NodeId_Comment_Text, _AutoBind_(data)); }
|
||||
break;
|
||||
|
||||
case EParsingStatus::MetadataEntry:
|
||||
if (top == "Identifier") { m_ctx->processIdentifier(OVTK_Algorithm_ScenarioExporter_NodeId_MetadataEntry_ID, _AutoBind_(data)); }
|
||||
if (top == "Type") { m_ctx->processIdentifier(OVTK_Algorithm_ScenarioExporter_NodeId_MetadataEntry_Type, _AutoBind_(data)); }
|
||||
if (top == "Data") { m_ctx->processString(OVTK_Algorithm_ScenarioExporter_NodeId_MetadataEntry_Data, _AutoBind_(data)); }
|
||||
break;
|
||||
|
||||
case EParsingStatus::CommentAttribute:
|
||||
if (top == "Identifier") { m_ctx->processIdentifier(OVTK_Algorithm_ScenarioExporter_NodeId_Comment_Attribute_ID, _AutoBind_(data)); }
|
||||
if (top == "Value") { m_ctx->processString(OVTK_Algorithm_ScenarioExporter_NodeId_Comment_Attribute_Value, _AutoBind_(data)); }
|
||||
break;
|
||||
|
||||
case EParsingStatus::Link:
|
||||
if (top == "Identifier") { m_ctx->processIdentifier(OVTK_Algorithm_ScenarioExporter_NodeId_Link_ID, _AutoBind_(data)); }
|
||||
break;
|
||||
case EParsingStatus::LinkSource:
|
||||
if (top == "BoxIdentifier") { m_ctx->processIdentifier(OVTK_Algorithm_ScenarioExporter_NodeId_Link_Source_BoxID, _AutoBind_(data)); }
|
||||
if (top == "BoxOutputIndex") { m_ctx->processUInteger(OVTK_Algorithm_ScenarioExporter_NodeId_Link_Source_BoxOutputIdx, _AutoBind_(data)); }
|
||||
if (top == "BoxOutputIdentifier") { m_ctx->processIdentifier(OVTK_Algorithm_ScenarioExporter_NodeId_Link_Source_BoxOutputID, _AutoBind_(data)); }
|
||||
break;
|
||||
case EParsingStatus::LinkTarget:
|
||||
if (top == "BoxIdentifier") { m_ctx->processIdentifier(OVTK_Algorithm_ScenarioExporter_NodeId_Link_Target_BoxID, _AutoBind_(data)); }
|
||||
if (top == "BoxInputIndex") { m_ctx->processUInteger(OVTK_Algorithm_ScenarioExporter_NodeId_Link_Target_BoxInputIdx, _AutoBind_(data)); }
|
||||
if (top == "BoxInputIdentifier") { m_ctx->processIdentifier(OVTK_Algorithm_ScenarioExporter_NodeId_Link_Target_BoxInputID, _AutoBind_(data)); }
|
||||
break;
|
||||
case EParsingStatus::LinkAttribute:
|
||||
if (top == "Identifier") { m_ctx->processIdentifier(OVTK_Algorithm_ScenarioExporter_NodeId_Link_Attribute_ID, _AutoBind_(data)); }
|
||||
if (top == "Value") { m_ctx->processString(OVTK_Algorithm_ScenarioExporter_NodeId_Link_Attribute_Value, _AutoBind_(data)); }
|
||||
break;
|
||||
|
||||
case EParsingStatus::ScenarioSetting:
|
||||
if (top == "Identifier") { m_ctx->processIdentifier(OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Setting_ID, _AutoBind_(data)); }
|
||||
if (top == "TypeIdentifier") { m_ctx->processIdentifier(OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Setting_TypeID, _AutoBind_(data)); }
|
||||
if (top == "Name") { m_ctx->processString(OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Setting_Name, _AutoBind_(data)); }
|
||||
if (top == "DefaultValue") { m_ctx->processString(OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Setting_DefaultValue, _AutoBind_(data)); }
|
||||
if (top == "Value") { m_ctx->processString(OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Setting_Value, _AutoBind_(data)); }
|
||||
break;
|
||||
|
||||
case EParsingStatus::ScenarioInput:
|
||||
if (top == "Identifier") { m_ctx->processIdentifier(OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Input_ID, _AutoBind_(data)); }
|
||||
if (top == "TypeIdentifier") { m_ctx->processIdentifier(OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Input_TypeID, _AutoBind_(data)); }
|
||||
if (top == "Name") { m_ctx->processString(OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Input_Name, _AutoBind_(data)); }
|
||||
if (top == "LinkedBoxIdentifier") { m_ctx->processIdentifier(OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Input_LinkedBoxID, _AutoBind_(data)); }
|
||||
if (top == "LinkedBoxInputIndex")
|
||||
{
|
||||
m_ctx->processUInteger(OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Input_LinkedBoxInputIdx, _AutoBind_(data));
|
||||
}
|
||||
if (top == "LinkedBoxInputIdentifier")
|
||||
{
|
||||
m_ctx->processIdentifier(OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Input_LinkedBoxInputID, _AutoBind_(data));
|
||||
}
|
||||
break;
|
||||
|
||||
case EParsingStatus::ScenarioOutput:
|
||||
if (top == "Identifier") { m_ctx->processIdentifier(OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Output_ID, _AutoBind_(data)); }
|
||||
if (top == "TypeIdentifier") { m_ctx->processIdentifier(OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Output_TypeID, _AutoBind_(data)); }
|
||||
if (top == "Name") { m_ctx->processString(OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Output_Name, _AutoBind_(data)); }
|
||||
if (top == "LinkedBoxIdentifier")
|
||||
{
|
||||
m_ctx->processIdentifier(OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Output_LinkedBoxID, _AutoBind_(data));
|
||||
}
|
||||
if (top == "LinkedBoxOutputIndex")
|
||||
{
|
||||
m_ctx->processUInteger(OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Output_LinkedBoxOutputIdx, _AutoBind_(data));
|
||||
}
|
||||
if (top == "LinkedBoxOutputIdentifier")
|
||||
{
|
||||
m_ctx->processIdentifier(OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Output_LinkedBoxOutputID, _AutoBind_(data));
|
||||
}
|
||||
break;
|
||||
|
||||
case EParsingStatus::ScenarioAttribute:
|
||||
if (top == "Identifier") { m_ctx->processIdentifier(OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Attribute_ID, _AutoBind_(data)); }
|
||||
if (top == "Value") { m_ctx->processString(OVTK_Algorithm_ScenarioExporter_NodeId_Scenario_Attribute_Value, _AutoBind_(data)); }
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
void CAlgorithmXMLScenarioImporter::closeChild()
|
||||
{
|
||||
std::string& top = m_nodes.top();
|
||||
|
||||
if (top == "OpenViBE-Scenario" && m_status == EParsingStatus::Scenario)
|
||||
{
|
||||
m_status = EParsingStatus::Nothing;
|
||||
m_ctx->processStop();
|
||||
}
|
||||
else if (top == "Setting" && m_status == EParsingStatus::ScenarioSetting)
|
||||
{
|
||||
m_status = EParsingStatus::Scenario;
|
||||
m_ctx->processStop();
|
||||
}
|
||||
else if (top == "Input" && m_status == EParsingStatus::ScenarioInput)
|
||||
{
|
||||
m_status = EParsingStatus::Scenario;
|
||||
m_ctx->processStop();
|
||||
}
|
||||
else if (top == "Output" && m_status == EParsingStatus::ScenarioOutput)
|
||||
{
|
||||
m_status = EParsingStatus::Scenario;
|
||||
m_ctx->processStop();
|
||||
}
|
||||
else if (top == "Attribute" && m_status == EParsingStatus::ScenarioAttribute)
|
||||
{
|
||||
m_status = EParsingStatus::Scenario;
|
||||
m_ctx->processStop();
|
||||
}
|
||||
|
||||
else if (top == "Box" && m_status == EParsingStatus::Box)
|
||||
{
|
||||
m_status = EParsingStatus::Scenario;
|
||||
m_ctx->processStop();
|
||||
}
|
||||
else if (top == "Input" && m_status == EParsingStatus::BoxInput)
|
||||
{
|
||||
m_status = EParsingStatus::Box;
|
||||
m_ctx->processStop();
|
||||
}
|
||||
|
||||
else if (top == "Output" && m_status == EParsingStatus::BoxOutput)
|
||||
{
|
||||
m_status = EParsingStatus::Box;
|
||||
m_ctx->processStop();
|
||||
}
|
||||
else if (top == "Setting" && m_status == EParsingStatus::BoxSetting)
|
||||
{
|
||||
m_status = EParsingStatus::Box;
|
||||
m_ctx->processStop();
|
||||
}
|
||||
else if (top == "Attribute" && m_status == EParsingStatus::BoxAttribute)
|
||||
{
|
||||
m_status = EParsingStatus::Box;
|
||||
m_ctx->processStop();
|
||||
}
|
||||
|
||||
else if (top == "Comment" && m_status == EParsingStatus::Comment)
|
||||
{
|
||||
m_status = EParsingStatus::Scenario;
|
||||
m_ctx->processStop();
|
||||
}
|
||||
else if (top == "Attribute" && m_status == EParsingStatus::CommentAttribute)
|
||||
{
|
||||
m_status = EParsingStatus::Comment;
|
||||
m_ctx->processStop();
|
||||
}
|
||||
|
||||
else if (top == "Entry" && m_status == EParsingStatus::MetadataEntry)
|
||||
{
|
||||
m_status = EParsingStatus::Scenario;
|
||||
m_ctx->processStop();
|
||||
}
|
||||
|
||||
else if (top == "Link" && m_status == EParsingStatus::Link)
|
||||
{
|
||||
m_status = EParsingStatus::Scenario;
|
||||
m_ctx->processStop();
|
||||
}
|
||||
else if (top == "Source" && m_status == EParsingStatus::LinkSource)
|
||||
{
|
||||
m_status = EParsingStatus::Link;
|
||||
m_ctx->processStop();
|
||||
}
|
||||
else if (top == "Target" && m_status == EParsingStatus::LinkTarget)
|
||||
{
|
||||
m_status = EParsingStatus::Link;
|
||||
m_ctx->processStop();
|
||||
}
|
||||
else if (top == "Attribute" && m_status == EParsingStatus::LinkAttribute)
|
||||
{
|
||||
m_status = EParsingStatus::Link;
|
||||
m_ctx->processStop();
|
||||
}
|
||||
|
||||
m_nodes.pop();
|
||||
}
|
||||
|
||||
bool CAlgorithmXMLScenarioImporter::validateXML(const unsigned char* buffer, const size_t size)
|
||||
{
|
||||
// implementation of the fallback mechanism
|
||||
|
||||
// error manager is used to differentiate errors from invalid xml
|
||||
this->getErrorManager().releaseErrors();
|
||||
|
||||
if (this->validateXMLAgainstSchema((Directories::getDataDir() + "/kernel/openvibe-scenario-v2.xsd"), buffer, size)) { return true; }
|
||||
if (this->getErrorManager().hasError())
|
||||
{
|
||||
// this is not a validation error thus we return directly
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->validateXMLAgainstSchema((Directories::getDataDir() + "/kernel/openvibe-scenario-v1.xsd"), buffer, size))
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Trace <<
|
||||
"Importing scenario with legacy format: v1 scenario might be deprecated in the future so upgrade to v2 format when possible\n";
|
||||
return true;
|
||||
}
|
||||
if (this->getErrorManager().hasError())
|
||||
{
|
||||
// this is not a validation error thus we return directly
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->validateXMLAgainstSchema((Directories::getDataDir() + "/kernel/openvibe-scenario-legacy.xsd"), buffer, size))
|
||||
{
|
||||
OV_WARNING_K("Importing scenario with legacy format: legacy scenario might be deprecated in the future so upgrade to v2 format when possible");
|
||||
return true;
|
||||
}
|
||||
if (this->getErrorManager().hasError())
|
||||
{
|
||||
// this is not a validation error thus we return directly
|
||||
return false;
|
||||
}
|
||||
|
||||
OV_ERROR_KRF("Failed to validate scenario against XSD schemas", Kernel::ErrorType::BadXMLSchemaValidation);
|
||||
}
|
||||
|
||||
bool CAlgorithmXMLScenarioImporter::validateXMLAgainstSchema(const char* validationSchema, const unsigned char* buffer, const size_t size)
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Trace << "Validating XML against schema [" << validationSchema << "]\n";
|
||||
|
||||
size_t errorCount;
|
||||
XMLPlatformUtils::Initialize();
|
||||
|
||||
{ // scope the content here to ensure unique_ptr contents are destroyed before the call to XMLPlatformUtils::Terminate();
|
||||
const std::unique_ptr<MemBufInputSource> xercesBuffer(new MemBufInputSource(buffer, size, "xml memory buffer"));
|
||||
|
||||
std::unique_ptr<XercesDOMParser> parser(new XercesDOMParser());
|
||||
parser->setValidationScheme(XercesDOMParser::Val_Always);
|
||||
parser->setDoNamespaces(true);
|
||||
parser->setDoSchema(true);
|
||||
parser->setValidationConstraintFatal(true);
|
||||
parser->setValidationSchemaFullChecking(true);
|
||||
parser->setExternalNoNamespaceSchemaLocation(validationSchema);
|
||||
|
||||
const std::unique_ptr<ErrorHandler> errorHandler(new CErrorHandler(this->getAlgorithmContext()));
|
||||
parser->setErrorHandler(errorHandler.get());
|
||||
|
||||
parser->parse(*xercesBuffer);
|
||||
errorCount = parser->getErrorCount();
|
||||
}
|
||||
|
||||
XMLPlatformUtils::Terminate();
|
||||
|
||||
return (errorCount == 0);
|
||||
}
|
||||
|
||||
bool CAlgorithmXMLScenarioImporter::import(IAlgorithmScenarioImporterContext& rContext, const IMemoryBuffer& memoryBuffer)
|
||||
{
|
||||
m_ctx = &rContext;
|
||||
if (!this->validateXML(memoryBuffer.getDirectPointer(), memoryBuffer.getSize())) { return false; } // error handling is handled in validateXML
|
||||
return m_reader->processData(memoryBuffer.getDirectPointer(), memoryBuffer.getSize());
|
||||
}
|
||||
|
||||
} // namespace FileIO
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
#include <xml/IReader.h>
|
||||
#include <stack>
|
||||
#include <string>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace FileIO {
|
||||
class CAlgorithmXMLScenarioImporter final : public Toolkit::CAlgorithmScenarioImporter, public XML::IReaderCallback
|
||||
{
|
||||
public:
|
||||
|
||||
CAlgorithmXMLScenarioImporter();
|
||||
~CAlgorithmXMLScenarioImporter() override;
|
||||
bool import(IAlgorithmScenarioImporterContext& rContext, const IMemoryBuffer& memoryBuffer) override;
|
||||
void openChild(const char* name, const char** attributeName, const char** sAttributeValue, const size_t nAttribute)
|
||||
override; // XML::IReaderCallback
|
||||
void processChildData(const char* data) override; // XML::IReaderCallback
|
||||
void closeChild() override; // XML::IReaderCallback
|
||||
|
||||
_IsDerivedFromClass_Final_(Toolkit::CAlgorithmScenarioImporter, OVP_ClassId_Algorithm_XMLScenarioImporter)
|
||||
|
||||
protected:
|
||||
enum class EParsingStatus
|
||||
{
|
||||
Nothing,
|
||||
Scenario, ScenarioAttribute, ScenarioInput, ScenarioOutput, ScenarioSetting,
|
||||
Box, BoxInput, BoxOutput, BoxSetting, BoxAttribute,
|
||||
Comment, CommentAttribute,
|
||||
MetadataEntry,
|
||||
Link, LinkSource, LinkTarget, LinkAttribute
|
||||
};
|
||||
|
||||
bool validateXML(const unsigned char* buffer, size_t size);
|
||||
bool validateXMLAgainstSchema(const char* validationSchema, const unsigned char* buffer, size_t size);
|
||||
|
||||
IAlgorithmScenarioImporterContext* m_ctx = nullptr;
|
||||
EParsingStatus m_status = EParsingStatus::Nothing;
|
||||
XML::IReader* m_reader = nullptr;
|
||||
std::stack<std::string> m_nodes;
|
||||
};
|
||||
|
||||
class CAlgorithmXMLScenarioImporterDesc final : public Toolkit::CAlgorithmScenarioImporterDesc
|
||||
{
|
||||
public:
|
||||
void release() override { }
|
||||
CString getName() const override { return CString("XML Scenario importer"); }
|
||||
CString getAuthorName() const override { return CString("Yann Renard"); }
|
||||
CString getAuthorCompanyName() const override { return CString("INRIA/IRISA"); }
|
||||
CString getShortDescription() const override { return CString("A sample XML scenario importer"); }
|
||||
|
||||
CString getDetailedDescription() const override { return CString("This scenario importer uses simple XML format to input the scenario"); }
|
||||
|
||||
CString getCategory() const override { return CString("Samples"); }
|
||||
CString getVersion() const override { return CString("1.0"); }
|
||||
// virtual CString getFileExtension() const { return CString("xml;XML"); }
|
||||
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
|
||||
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_Algorithm_XMLScenarioImporter; }
|
||||
IPluginObject* create() override { return new CAlgorithmXMLScenarioImporter(); }
|
||||
|
||||
_IsDerivedFromClass_Final_(Toolkit::CAlgorithmScenarioImporterDesc, OVP_ClassId_Algorithm_XMLScenarioImporterDesc)
|
||||
};
|
||||
} // namespace FileIO
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,572 @@
|
||||
#include "ovpCBoxAlgorithmCSVFileReader.h"
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <cmath> // std::ceil() on Linux
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace FileIO {
|
||||
|
||||
namespace {
|
||||
std::vector<std::string> split(const std::string& sString, const std::string& c)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
size_t i = 0;
|
||||
size_t j;
|
||||
while ((j = sString.find(c, i)) != std::string::npos)
|
||||
{
|
||||
result.push_back(std::string(sString, i, j - i));
|
||||
i = j + c.size();
|
||||
}
|
||||
//the last element without the \n character
|
||||
result.push_back(std::string(sString, i, sString.size() - 1 - i));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void clearMatrix(std::vector<std::vector<std::string>>& vMatrix)
|
||||
{
|
||||
for (size_t i = 0; i < vMatrix.size(); ++i) { vMatrix[i].clear(); }
|
||||
vMatrix.clear();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool CBoxAlgorithmCSVFileReader::initialize()
|
||||
{
|
||||
m_sampling = 0;
|
||||
m_encoder = nullptr;
|
||||
|
||||
this->getStaticBoxContext().getOutputType(0, m_typeID);
|
||||
|
||||
m_filename = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
|
||||
const CString token = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1);
|
||||
m_separator = token.toASCIIString();
|
||||
m_doNotUseFileTime = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2);
|
||||
m_samplesPerBuffer = 1;
|
||||
if (m_typeID == OV_TypeId_ChannelLocalisation) { m_channelsPerBuffer = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 3); }
|
||||
else if (m_typeID != OV_TypeId_Stimulations && m_typeID != OV_TypeId_Spectrum)
|
||||
{
|
||||
m_samplesPerBuffer = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 3);
|
||||
}
|
||||
|
||||
m_nextTime = 0.;
|
||||
|
||||
m_startTime = 0;
|
||||
m_endTime = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmCSVFileReader::uninitialize()
|
||||
{
|
||||
if (m_file)
|
||||
{
|
||||
fclose(m_file);
|
||||
m_file = nullptr;
|
||||
}
|
||||
if (m_encoder)
|
||||
{
|
||||
m_encoder->uninitialize();
|
||||
delete m_encoder;
|
||||
m_encoder = nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmCSVFileReader::initializeFile()
|
||||
{
|
||||
//open file, we don't open as binary as that gives us \r\n on Windows as line-endings and leaves a dangling char after split. CSV files should be text.
|
||||
m_file = fopen(m_filename.toASCIIString(), "r");
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_file, "Error opening file [" << m_filename << "] for reading", Kernel::ErrorType::BadFileRead);
|
||||
|
||||
// simulate RAII through closure
|
||||
const auto releaseResources = [&]()
|
||||
{
|
||||
fclose(m_file);
|
||||
m_file = nullptr;
|
||||
};
|
||||
|
||||
//read the header
|
||||
char line[BUFFER_LEN];
|
||||
char* result = fgets(line, BUFFER_LEN, m_file);
|
||||
if (nullptr == result)
|
||||
{
|
||||
releaseResources();
|
||||
OV_ERROR_KRF("Error reading data from file", Kernel::ErrorType::BadParsing);
|
||||
}
|
||||
|
||||
m_headerFiles = split(std::string(line), m_separator);
|
||||
m_nCol = m_headerFiles.size();
|
||||
|
||||
if (m_typeID == OV_TypeId_ChannelLocalisation)
|
||||
{
|
||||
m_encoder = new Toolkit::TChannelLocalisationEncoder<CBoxAlgorithmCSVFileReader>(*this, 0);
|
||||
//number of column without the column contains the dynamic parameter
|
||||
//m_nCol-=1;
|
||||
m_realProcess = &CBoxAlgorithmCSVFileReader::processChannelLocalisation;
|
||||
}
|
||||
else if (m_typeID == OV_TypeId_FeatureVector)
|
||||
{
|
||||
m_encoder = new Toolkit::TFeatureVectorEncoder<CBoxAlgorithmCSVFileReader>(*this, 0);
|
||||
m_realProcess = &CBoxAlgorithmCSVFileReader::processFeatureVector;
|
||||
m_samplesPerBuffer = 1;
|
||||
}
|
||||
else if (m_typeID == OV_TypeId_Spectrum)
|
||||
{
|
||||
m_encoder = new Toolkit::TSpectrumEncoder<CBoxAlgorithmCSVFileReader>(*this, 0);
|
||||
m_realProcess = &CBoxAlgorithmCSVFileReader::processSpectrum;
|
||||
|
||||
//number of column without columns contains min max frequency bands parameters
|
||||
m_nCol -= 2;
|
||||
}
|
||||
else if (m_typeID == OV_TypeId_Signal)
|
||||
{
|
||||
m_encoder = new Toolkit::TSignalEncoder<CBoxAlgorithmCSVFileReader>(*this, 0);
|
||||
m_realProcess = &CBoxAlgorithmCSVFileReader::processSignal;
|
||||
|
||||
//find the sampling rate
|
||||
result = fgets(line, BUFFER_LEN, m_file);
|
||||
|
||||
if (nullptr == result)
|
||||
{
|
||||
releaseResources();
|
||||
OV_ERROR_KRF("Error reading sampling rate from file", Kernel::ErrorType::BadParsing);
|
||||
}
|
||||
|
||||
std::vector<std::string> parsed = split(std::string(line), m_separator);
|
||||
|
||||
if ((m_nCol - 1) >= parsed.size())
|
||||
{
|
||||
releaseResources();
|
||||
OV_ERROR_KRF("Error reading columns (not enough columns found) from file", Kernel::ErrorType::BadParsing);
|
||||
}
|
||||
|
||||
const double sampling = double(atof(parsed[m_nCol - 1].c_str()));
|
||||
if (ceil(sampling) != sampling)
|
||||
{
|
||||
releaseResources();
|
||||
OV_ERROR_KRF("Invalid fractional sampling rate (" << sampling << ") in file", Kernel::ErrorType::BadValue);
|
||||
}
|
||||
|
||||
m_sampling = uint64_t(sampling);
|
||||
|
||||
if (m_sampling == 0)
|
||||
{
|
||||
releaseResources();
|
||||
OV_ERROR_KRF("Invalid NULL sampling rate in file", Kernel::ErrorType::BadValue);
|
||||
}
|
||||
|
||||
// Skip the header
|
||||
rewind(m_file);
|
||||
result = fgets(line, BUFFER_LEN, m_file);
|
||||
if (nullptr == result)
|
||||
{
|
||||
releaseResources();
|
||||
OV_ERROR_KRF("Error reading data from file", Kernel::ErrorType::BadParsing);
|
||||
}
|
||||
|
||||
//number of column without the column contains the sampling rate parameters
|
||||
m_nCol -= 1;
|
||||
}
|
||||
else if (m_typeID == OV_TypeId_StreamedMatrix)
|
||||
{
|
||||
m_encoder = new Toolkit::TStreamedMatrixEncoder<CBoxAlgorithmCSVFileReader>(*this, 0);
|
||||
m_realProcess = &CBoxAlgorithmCSVFileReader::processStreamedMatrix;
|
||||
}
|
||||
else if (m_typeID == OV_TypeId_Stimulations)
|
||||
{
|
||||
m_encoder = new Toolkit::TStimulationEncoder<CBoxAlgorithmCSVFileReader>(*this, 0);
|
||||
m_realProcess = &CBoxAlgorithmCSVFileReader::processStimulation;
|
||||
}
|
||||
else
|
||||
{
|
||||
releaseResources();
|
||||
OV_ERROR_KRF("Invalid input type identifier " << this->getTypeManager().getTypeName(m_typeID) << " in file ", Kernel::ErrorType::BadValue);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmCSVFileReader::processClock(Kernel::CMessageClock& /*msg*/)
|
||||
{
|
||||
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmCSVFileReader::process()
|
||||
{
|
||||
if (m_file == nullptr) { OV_ERROR_UNLESS_KRF(initializeFile(), "Error reading data from csv file " << m_filename, Kernel::ErrorType::Internal); }
|
||||
//line buffer
|
||||
char line[BUFFER_LEN];
|
||||
const double currentTime = CTime(getPlayerContext().getCurrentTime()).toSeconds();
|
||||
|
||||
//if no line was read, read the first data line.
|
||||
if (m_lastLineSplits.empty())
|
||||
{
|
||||
//next line
|
||||
size_t nSamples = 0;
|
||||
while (!feof(m_file) && nSamples < m_samplesPerBuffer && fgets(line, BUFFER_LEN, m_file) != nullptr)
|
||||
{
|
||||
m_lastLineSplits = split(std::string(line), m_separator);
|
||||
|
||||
nSamples++;
|
||||
|
||||
if (m_typeID != OV_TypeId_Stimulations
|
||||
&& m_typeID != OV_TypeId_Spectrum
|
||||
&& m_typeID != OV_TypeId_ChannelLocalisation) { m_dataMatrices.push_back(m_lastLineSplits); }
|
||||
}
|
||||
if ((m_typeID == OV_TypeId_StreamedMatrix || m_typeID == OV_TypeId_Signal)
|
||||
&& feof(m_file) && nSamples < m_samplesPerBuffer)
|
||||
{
|
||||
// Last chunk will be partial, zero the whole output matrix...
|
||||
CMatrix* iMatrix = static_cast<Toolkit::TStreamedMatrixEncoder<CBoxAlgorithmCSVFileReader>*>(m_encoder)->getInputMatrix();
|
||||
iMatrix->resetBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
bool somethingToSend = (!m_lastLineSplits.empty()) && atof(m_lastLineSplits[0].c_str()) < currentTime;
|
||||
somethingToSend |= (m_typeID == OV_TypeId_Stimulations); // we always send a stim chunk, even if empty
|
||||
|
||||
if (m_typeID == OV_TypeId_Stimulations || m_typeID == OV_TypeId_ChannelLocalisation || m_typeID == OV_TypeId_Spectrum)
|
||||
{
|
||||
while (!m_lastLineSplits.empty() && atof(m_lastLineSplits[0].c_str()) < currentTime)
|
||||
{
|
||||
m_dataMatrices.push_back(m_lastLineSplits);
|
||||
|
||||
somethingToSend = true;
|
||||
|
||||
if (!feof(m_file) && fgets(line, BUFFER_LEN, m_file) != nullptr) { m_lastLineSplits = split(std::string(line), m_separator); }
|
||||
else { m_lastLineSplits.clear(); }
|
||||
}
|
||||
}
|
||||
|
||||
//convert data to the good output type
|
||||
|
||||
if (somethingToSend)
|
||||
{
|
||||
// Encode the data
|
||||
OV_ERROR_UNLESS_KRF((this->*m_realProcess)(), "Error encoding data from csv file " << m_filename << " into the right output format",
|
||||
Kernel::ErrorType::Internal);
|
||||
|
||||
//for the stimulation, the line contents in m_vLastLineSplit isn't processed.
|
||||
if (m_typeID != OV_TypeId_Stimulations && m_typeID != OV_TypeId_Spectrum && m_typeID != OV_TypeId_ChannelLocalisation) { m_lastLineSplits.clear(); }
|
||||
|
||||
//clear the Data Matrix.
|
||||
clearMatrix(m_dataMatrices);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmCSVFileReader::processStreamedMatrix()
|
||||
{
|
||||
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
|
||||
CMatrix* iMatrix = static_cast<Toolkit::TStreamedMatrixEncoder<CBoxAlgorithmCSVFileReader>*>(m_encoder)->getInputMatrix();
|
||||
|
||||
//Header
|
||||
if (!m_headerSent)
|
||||
{
|
||||
iMatrix->resize(m_nCol - 1, m_samplesPerBuffer);
|
||||
|
||||
for (size_t i = 1; i < m_nCol; ++i) { iMatrix->setDimensionLabel(0, i - 1, m_headerFiles[i].c_str()); }
|
||||
m_encoder->encodeHeader();
|
||||
m_headerSent = true;
|
||||
|
||||
boxContext.markOutputAsReadyToSend(0, 0, 0);
|
||||
}
|
||||
|
||||
OV_ERROR_UNLESS_KRF(convertVectorDataToMatrix(iMatrix), "Error converting vector data to streamed matrix", Kernel::ErrorType::Internal);
|
||||
|
||||
m_encoder->encodeBuffer();
|
||||
|
||||
if (m_doNotUseFileTime)
|
||||
{
|
||||
m_startTime = m_endTime;
|
||||
m_endTime = this->getPlayerContext().getCurrentTime();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_startTime = CTime(atof(m_dataMatrices[0][0].c_str())).time();
|
||||
m_endTime = CTime(atof(m_dataMatrices.back()[0].c_str())).time();
|
||||
}
|
||||
|
||||
boxContext.markOutputAsReadyToSend(0, m_startTime, m_endTime);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmCSVFileReader::processStimulation()
|
||||
{
|
||||
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
|
||||
//Header
|
||||
if (!m_headerSent)
|
||||
{
|
||||
m_encoder->encodeHeader();
|
||||
m_headerSent = true;
|
||||
|
||||
boxContext.markOutputAsReadyToSend(0, 0, 0);
|
||||
}
|
||||
|
||||
IStimulationSet* ip_stimSet = static_cast<Toolkit::TStimulationEncoder<CBoxAlgorithmCSVFileReader>*>(m_encoder)->getInputStimulationSet();
|
||||
ip_stimSet->clear();
|
||||
|
||||
for (size_t i = 0; i < m_dataMatrices.size(); ++i)
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(m_dataMatrices[i].size() == 3, "Invalid data row length: must be 3 for stimulation date, index and duration",
|
||||
Kernel::ErrorType::BadParsing);
|
||||
|
||||
const uint64_t date = CTime(atof(m_dataMatrices[i][0].c_str())).time();
|
||||
const uint64_t id = uint64_t(atof(m_dataMatrices[i][1].c_str()));
|
||||
const uint64_t duration = CTime(atof(m_dataMatrices[i][2].c_str())).time();
|
||||
|
||||
ip_stimSet->appendStimulation(id, date, duration);
|
||||
}
|
||||
|
||||
m_encoder->encodeBuffer();
|
||||
|
||||
// Never use file time
|
||||
m_startTime = m_endTime;
|
||||
m_endTime = this->getPlayerContext().getCurrentTime();
|
||||
|
||||
boxContext.markOutputAsReadyToSend(0, m_startTime, m_endTime);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmCSVFileReader::processSignal()
|
||||
{
|
||||
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
|
||||
CMatrix* iMatrix = static_cast<Toolkit::TSignalEncoder<CBoxAlgorithmCSVFileReader>*>(m_encoder)->getInputMatrix();
|
||||
|
||||
//Header
|
||||
if (!m_headerSent)
|
||||
{
|
||||
// This is the first chunk, find out the start time from the file
|
||||
// (to keep time chunks continuous, start time is previous end time, hence set end time)
|
||||
if (!m_doNotUseFileTime) { m_endTime = CTime(atof(m_dataMatrices[0][0].c_str())).time(); }
|
||||
|
||||
iMatrix->resize(m_nCol - 1, m_samplesPerBuffer);
|
||||
|
||||
for (size_t i = 1; i < m_nCol; ++i) { iMatrix->setDimensionLabel(0, i - 1, m_headerFiles[i].c_str()); }
|
||||
|
||||
static_cast<Toolkit::TSignalEncoder<CBoxAlgorithmCSVFileReader>*>(m_encoder)->getInputSamplingRate() = m_sampling;
|
||||
|
||||
m_encoder->encodeHeader();
|
||||
m_headerSent = true;
|
||||
|
||||
this->getDynamicBoxContext().markOutputAsReadyToSend(0, 0, 0);
|
||||
}
|
||||
|
||||
OV_ERROR_UNLESS_KRF(convertVectorDataToMatrix(iMatrix), "Error converting vector data to signal", Kernel::ErrorType::Internal);
|
||||
|
||||
// this->getLogManager() << Kernel::LogLevel_Info << "Cols from header " << m_nCol << "\n";
|
||||
// this->getLogManager() << Kernel::LogLevel_Info << "InMatrix " << (m_dataMatrices.size() > 0 ? m_dataMatrices[0].size() : 0) << " outMatrix " << iMatrix->getDimensionSize(0) << "\n";
|
||||
|
||||
m_encoder->encodeBuffer();
|
||||
|
||||
if (m_doNotUseFileTime)
|
||||
{
|
||||
// We use time dictated by the sampling rate
|
||||
m_startTime = m_endTime; // previous time end is current time start
|
||||
m_endTime = m_startTime + CTime(m_sampling, m_samplesPerBuffer).time();
|
||||
}
|
||||
else
|
||||
{
|
||||
// We use time suggested by the last sample of the chunk
|
||||
m_startTime = CTime(atof(m_dataMatrices[0][0].c_str())).time();
|
||||
m_endTime = CTime(atof(m_dataMatrices.back()[0].c_str())).time();
|
||||
}
|
||||
|
||||
boxContext.markOutputAsReadyToSend(0, m_startTime, m_endTime);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmCSVFileReader::processChannelLocalisation()
|
||||
{
|
||||
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
|
||||
CMatrix* iMatrix = static_cast<Toolkit::TChannelLocalisationEncoder<CBoxAlgorithmCSVFileReader>*>(m_encoder)->getInputMatrix();
|
||||
|
||||
if (!m_headerSent)
|
||||
{
|
||||
iMatrix->resize(m_nCol - 1, m_samplesPerBuffer);
|
||||
|
||||
for (size_t i = 1; i < m_nCol; ++i) { iMatrix->setDimensionLabel(0, i - 1, m_headerFiles[i].c_str()); }
|
||||
|
||||
static_cast<Toolkit::TChannelLocalisationEncoder<CBoxAlgorithmCSVFileReader>*>(m_encoder)->getInputDynamic() = false;
|
||||
//atoi(m_dataMatrices[0][m_nCol].c_str());
|
||||
|
||||
m_encoder->encodeHeader();
|
||||
|
||||
boxContext.markOutputAsReadyToSend(0, 0, 0);
|
||||
|
||||
m_headerSent = true;
|
||||
}
|
||||
|
||||
std::vector<std::vector<std::string>> channelBloc;
|
||||
for (size_t i = 0; i < m_dataMatrices.size(); ++i) { channelBloc.push_back(m_dataMatrices[i]); }
|
||||
|
||||
//clear matrix
|
||||
clearMatrix(m_dataMatrices);
|
||||
|
||||
for (size_t i = 0; i < channelBloc.size(); ++i)
|
||||
{
|
||||
m_dataMatrices.push_back(channelBloc[i]);
|
||||
|
||||
//send the current bloc if the next data hasn't the same date
|
||||
if (i >= channelBloc.size() - 1 || channelBloc[(i + 1)][0] != m_dataMatrices[0][0])
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(convertVectorDataToMatrix(iMatrix), "Error converting vector data to channel localisation", Kernel::ErrorType::Internal);
|
||||
|
||||
m_encoder->encodeBuffer();
|
||||
const uint64_t date = CTime(atof(m_dataMatrices[0][0].c_str())).time();
|
||||
boxContext.markOutputAsReadyToSend(0, date, date);
|
||||
|
||||
//clear matrix
|
||||
clearMatrix(m_dataMatrices);
|
||||
}
|
||||
}
|
||||
|
||||
//clear matrix
|
||||
clearMatrix(channelBloc);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmCSVFileReader::processFeatureVector()
|
||||
{
|
||||
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
|
||||
CMatrix* matrix = static_cast<Toolkit::TFeatureVectorEncoder<CBoxAlgorithmCSVFileReader>*>(m_encoder)->getInputMatrix();
|
||||
|
||||
//Header
|
||||
if (!m_headerSent)
|
||||
{
|
||||
// in this case we need to transpose it
|
||||
CMatrix* iMatrix = static_cast<Toolkit::TStreamedMatrixEncoder<CBoxAlgorithmCSVFileReader>*>(m_encoder)->getInputMatrix();
|
||||
|
||||
iMatrix->resize(m_nCol - 1);
|
||||
|
||||
for (size_t i = 1; i < m_nCol; ++i) { iMatrix->setDimensionLabel(0, i - 1, m_headerFiles[i].c_str()); }
|
||||
|
||||
m_encoder->encodeHeader();
|
||||
|
||||
boxContext.markOutputAsReadyToSend(0, 0, 0);
|
||||
|
||||
m_headerSent = true;
|
||||
}
|
||||
|
||||
// Each vector has to be sent separately
|
||||
for (size_t i = 0; i < m_dataMatrices.size(); ++i)
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(m_dataMatrices[i].size() == m_nCol,
|
||||
"Unexpected number of elements" << "(got " << uint64_t(m_dataMatrices[i].size()) << ", expected " << m_nCol << ")",
|
||||
Kernel::ErrorType::BadParsing);
|
||||
|
||||
for (size_t j = 0; j < m_nCol - 1; ++j) { matrix->getBuffer()[j] = atof(m_dataMatrices[i][j + 1].c_str()); }
|
||||
|
||||
m_encoder->encodeBuffer();
|
||||
|
||||
const uint64_t date = CTime(atof(m_dataMatrices[i][0].c_str())).time();
|
||||
boxContext.markOutputAsReadyToSend(0, date, date);
|
||||
}
|
||||
|
||||
clearMatrix(m_dataMatrices);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmCSVFileReader::processSpectrum()
|
||||
{
|
||||
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
|
||||
CMatrix* iMatrix = static_cast<Toolkit::TSpectrumEncoder<CBoxAlgorithmCSVFileReader>*>(m_encoder)->getInputMatrix();
|
||||
CMatrix* iFrequencyAbscissa = static_cast<Toolkit::TSpectrumEncoder<CBoxAlgorithmCSVFileReader>*>(m_encoder)->getInputFrequencyAbscissa();
|
||||
|
||||
//Header
|
||||
if (!m_headerSent)
|
||||
{
|
||||
iMatrix->resize(m_nCol - 1, m_dataMatrices.size());
|
||||
|
||||
for (size_t i = 1; i < m_nCol; ++i) { iMatrix->setDimensionLabel(0, i - 1, m_headerFiles[i].c_str()); }
|
||||
iFrequencyAbscissa->resize(m_dataMatrices.size());
|
||||
if (m_dataMatrices.size() > 1)
|
||||
{
|
||||
for (size_t i = 0; i < m_dataMatrices.size(); ++i)
|
||||
{
|
||||
const double curFrequencyAbscissa = std::stod(m_dataMatrices[i][m_nCol]) + double(i) / (m_dataMatrices.size() - 1)
|
||||
* (std::stod(m_dataMatrices[i][m_nCol + 1]) - std::stod(m_dataMatrices[i][m_nCol]));
|
||||
iFrequencyAbscissa->getBuffer()[i] = curFrequencyAbscissa;
|
||||
|
||||
std::stringstream label;
|
||||
label << curFrequencyAbscissa;
|
||||
iFrequencyAbscissa->setDimensionLabel(0, i, label.str().c_str());
|
||||
}
|
||||
}
|
||||
else { iFrequencyAbscissa->getBuffer()[0] = 0; }
|
||||
|
||||
static_cast<Toolkit::TSpectrumEncoder<CBoxAlgorithmCSVFileReader>*>(m_encoder)->getInputSamplingRate() = uint64_t(
|
||||
m_dataMatrices.size() / (stod(m_dataMatrices[m_dataMatrices.size() - 1][m_nCol]) - stod(m_dataMatrices[0][m_nCol])));
|
||||
m_headerSent = true;
|
||||
m_encoder->encodeHeader();
|
||||
|
||||
boxContext.markOutputAsReadyToSend(0, 0, 0);
|
||||
}
|
||||
|
||||
std::vector<std::vector<std::string>> spectrumBloc;
|
||||
for (size_t i = 0; i < m_dataMatrices.size(); ++i) { spectrumBloc.push_back(m_dataMatrices[i]); }
|
||||
|
||||
//clear matrix
|
||||
clearMatrix(m_dataMatrices);
|
||||
|
||||
for (size_t i = 0; i < spectrumBloc.size(); ++i)
|
||||
{
|
||||
m_dataMatrices.push_back(spectrumBloc[i]);
|
||||
//send the current bloc if the next data hasn't the same date
|
||||
if (i >= spectrumBloc.size() - 1 || spectrumBloc[i + 1][0] != m_dataMatrices[0][0])
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(convertVectorDataToMatrix(iMatrix), "Error converting vector data to spectrum", Kernel::ErrorType::Internal);
|
||||
|
||||
m_encoder->encodeBuffer();
|
||||
const uint64_t date = CTime(std::stod(m_dataMatrices[0][0])).time();
|
||||
boxContext.markOutputAsReadyToSend(0, date - 1, date);
|
||||
|
||||
//clear matrix
|
||||
clearMatrix(m_dataMatrices);
|
||||
}
|
||||
}
|
||||
|
||||
//clear matrix
|
||||
clearMatrix(spectrumBloc);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmCSVFileReader::convertVectorDataToMatrix(CMatrix* matrix)
|
||||
{
|
||||
// note: Chunk size shouldn't change after encoding header, do not mess with it here, even if the input has different size
|
||||
|
||||
// We accept partial data, but not buffer overruns ...
|
||||
OV_ERROR_UNLESS_KRF(matrix->getDimensionSize(1) >= m_dataMatrices.size() && matrix->getDimensionSize(0) >= (m_nCol-1),
|
||||
"Matrix size incompatibility, data suggests " << m_nCol-1 << "x" << m_dataMatrices.size()
|
||||
<< ", expected at most " << matrix->getDimensionSize(0) << "x" << matrix->getDimensionSize(0), Kernel::ErrorType::Overflow);
|
||||
|
||||
std::stringstream ss;
|
||||
for (size_t i = 0; i < m_dataMatrices.size(); ++i)
|
||||
{
|
||||
ss << "at time (" << m_dataMatrices[i][0].c_str() << "):";
|
||||
for (size_t j = 0; j < m_nCol - 1; ++j)
|
||||
{
|
||||
matrix->getBuffer()[j * matrix->getDimensionSize(1) + i] = std::stod(m_dataMatrices[i][j + 1]);
|
||||
ss << matrix->getBuffer()[j * matrix->getDimensionSize(1) + i] << ";";
|
||||
}
|
||||
ss << "\n";
|
||||
}
|
||||
getLogManager() << Kernel::LogLevel_Debug << "Matrix:\n" << ss.str();
|
||||
getLogManager() << Kernel::LogLevel_Debug << "Matrix:\n" << ss.str();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace FileIO
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
@@ -0,0 +1,155 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdio>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace FileIO {
|
||||
class CBoxAlgorithmCSVFileReader final : public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
|
||||
{
|
||||
public:
|
||||
|
||||
CBoxAlgorithmCSVFileReader() {}
|
||||
void release() override { delete this; }
|
||||
uint64_t getClockFrequency() override { return 128LL << 32; } // the box clock frequency
|
||||
bool initialize() override;
|
||||
bool uninitialize() override;
|
||||
bool processClock(Kernel::CMessageClock& msg) override;
|
||||
bool process() override;
|
||||
|
||||
bool processStreamedMatrix();
|
||||
bool processStimulation();
|
||||
bool processSignal();
|
||||
bool processChannelLocalisation();
|
||||
bool processFeatureVector();
|
||||
bool processSpectrum();
|
||||
bool convertVectorDataToMatrix(CMatrix* matrix);
|
||||
|
||||
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_CSVFileReader)
|
||||
|
||||
|
||||
protected:
|
||||
bool initializeFile();
|
||||
|
||||
FILE* m_file = nullptr;
|
||||
std::string m_separator;
|
||||
bool m_doNotUseFileTime = false;
|
||||
CString m_filename;
|
||||
|
||||
CIdentifier m_typeID = CIdentifier::undefined();
|
||||
size_t m_nCol = 0;
|
||||
size_t m_sampling = 0;
|
||||
size_t m_samplesPerBuffer = 0;
|
||||
size_t m_channelsPerBuffer = 0;
|
||||
|
||||
bool (CBoxAlgorithmCSVFileReader::*m_realProcess)() = nullptr;
|
||||
|
||||
Toolkit::TEncoder<CBoxAlgorithmCSVFileReader>* m_encoder = nullptr;
|
||||
|
||||
bool m_headerSent = false;
|
||||
std::vector<std::string> m_lastLineSplits;
|
||||
std::vector<std::string> m_headerFiles;
|
||||
std::vector<std::vector<std::string>> m_dataMatrices;
|
||||
|
||||
double m_nextTime = 0;
|
||||
|
||||
uint64_t m_startTime = 0;
|
||||
uint64_t m_endTime = 0;
|
||||
|
||||
static const size_t BUFFER_LEN = 16384; // Side-effect: a maximum allowed length for a line of a CSV file
|
||||
};
|
||||
|
||||
class CBoxAlgorithmCSVFileReaderListener final : public Toolkit::TBoxListener<IBoxListener>
|
||||
{
|
||||
public:
|
||||
bool onOutputTypeChanged(Kernel::IBox& box, const size_t index) override
|
||||
{
|
||||
CIdentifier typeID = CIdentifier::undefined();
|
||||
box.getOutputType(index, typeID);
|
||||
if (typeID == OV_TypeId_Spectrum)
|
||||
{
|
||||
box.setSettingName(3, "Unused parameter");
|
||||
box.setSettingValue(3, "0");
|
||||
}
|
||||
else if (typeID == OV_TypeId_ChannelLocalisation)
|
||||
{
|
||||
box.setSettingName(3, "Channels number");
|
||||
box.setSettingValue(3, "32");
|
||||
}
|
||||
else if (typeID == OV_TypeId_FeatureVector)
|
||||
{
|
||||
box.setSettingName(3, "Unused parameter");
|
||||
box.setSettingValue(3, "0");
|
||||
}
|
||||
else if (typeID == OV_TypeId_StreamedMatrix)
|
||||
{
|
||||
box.setSettingName(3, "Samples per buffer");
|
||||
box.setSettingValue(3, "32");
|
||||
}
|
||||
else if (typeID == OV_TypeId_Stimulations)
|
||||
{
|
||||
box.setSettingName(3, "Unused parameter");
|
||||
box.setSettingValue(3, "0");
|
||||
}
|
||||
else
|
||||
{
|
||||
box.setOutputType(index, OV_TypeId_Signal);
|
||||
box.setSettingName(3, "Samples per buffer");
|
||||
box.setSettingValue(3, "32");
|
||||
|
||||
OV_ERROR_KRF("Unsupported stream type " << typeID.str(), Kernel::ErrorType::BadOutput);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
|
||||
};
|
||||
|
||||
class CBoxAlgorithmCSVFileReaderDesc final : virtual public IBoxAlgorithmDesc
|
||||
{
|
||||
public:
|
||||
void release() override { }
|
||||
CString getName() const override { return CString("CSV File Reader (Deprecated)"); }
|
||||
CString getAuthorName() const override { return CString("Baptiste Payan"); }
|
||||
CString getAuthorCompanyName() const override { return CString("INRIA"); }
|
||||
CString getShortDescription() const override { return CString("Read signal in a CSV (text based) file"); }
|
||||
CString getDetailedDescription() const override { return CString(""); }
|
||||
CString getCategory() const override { return CString("File reading and writing/CSV"); }
|
||||
CString getVersion() const override { return CString("1.0"); }
|
||||
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
|
||||
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_CSVFileReader; }
|
||||
IPluginObject* create() override { return new CBoxAlgorithmCSVFileReader; }
|
||||
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmCSVFileReaderListener; }
|
||||
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
|
||||
|
||||
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
|
||||
{
|
||||
prototype.addOutput("Output stream", OV_TypeId_Signal);
|
||||
prototype.addSetting("Filename", OV_TypeId_Filename, "");
|
||||
prototype.addSetting("Column separator", OV_TypeId_String, ";");
|
||||
prototype.addSetting("Don't use the file time",OV_TypeId_Boolean, "false");
|
||||
prototype.addSetting("Samples per buffer", OV_TypeId_Integer, "32");
|
||||
|
||||
prototype.addFlag(Kernel::BoxFlag_CanModifyOutput);
|
||||
|
||||
prototype.addOutputSupport(OV_TypeId_StreamedMatrix);
|
||||
prototype.addOutputSupport(OV_TypeId_FeatureVector);
|
||||
prototype.addOutputSupport(OV_TypeId_ChannelLocalisation);
|
||||
prototype.addOutputSupport(OV_TypeId_Signal);
|
||||
prototype.addOutputSupport(OV_TypeId_Spectrum);
|
||||
prototype.addOutputSupport(OV_TypeId_Stimulations);
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_CSVFileReaderDesc)
|
||||
};
|
||||
} // namespace FileIO
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||