Commit dfcc50c0 authored by Adrien Dorsaz's avatar Adrien Dorsaz

Big commit : add a naïve baise classifier, set filters and modifiy application...

Big commit : add a naïve baise classifier, set filters and modifiy application to initiate classifiers.
parent 258e849f
......@@ -17,8 +17,8 @@ QML_IMPORT_PATH =
# The .cpp file which was generated for your project. Feel free to hack it.
SOURCES += main.cpp \
emotions.cpp \
mycallback.cpp \
pocketbrain.cpp
mycallback.cpp \
naivebaiseclassifier.cpp
# Please do not modify the following two lines. Required for deployment.
include(qmlapplicationviewer/qmlapplicationviewer.pri)
......@@ -28,8 +28,8 @@ include(./think-core/src/think-core.pri)
HEADERS += \
mycallback.h \
emotions.h \
pocketbrain.h
emotions.h \
naivebaiseclassifier.h
OTHER_FILES += \
android/src/org/kde/necessitas/ministro/IMinistro.aidl \
......
......@@ -2,138 +2,146 @@
Emotions::Emotions()
{
arousal_max=UNSET_VALUE;
arousal_min=UNSET_VALUE;
valence_max=UNSET_VALUE;
valence_min=UNSET_VALUE;
max_diff_arousal=0;
max_diff_valence=0;
resetValues();
_arousalClassifier=NULL;
_valenceClassifier=NULL;
_saveCalm=false;
_saveJoy=false;
_saveSad=false;
_saveFear=false;
_nbInitDone=0;
_sizeSet=0;
_arousalSet = new QMap<double,double>();
_valenceSet = new QMap<double,double>();
_totalArousalOccurrences = new QMap<double, double>();
_totalValenceOccurrences = new QMap<double, double>();
_trainedArousalClasses = new QMap<QString, QMap<double, double>*>();
_trainedValenceClasses = new QMap<QString, QMap<double, double>*>();
}
void Emotions::resetValues(){
arousal=UNSET_VALUE;
arousal_4s=UNSET_VALUE;
arousal_20s=UNSET_VALUE;
arousal_wait_4s=true;
arousal_wait_20s=true;
void Emotions::arousalValence(double arousal, double valence){
insertValueAndTotal(_arousalSet, _totalArousalOccurrences, arousal);
insertValueAndTotal(_valenceSet, _totalValenceOccurrences, valence);
_sizeSet++;
if(_saveCalm){
if(_sizeSet >= NB_AROUSAL_VALENCE_BEFORE_CLASS){
updateTrainedClass("calm", "positive");
_saveCalm=false;
_nbInitDone++;
qDebug()<<"initCalm done";
}
}
if(_saveJoy){
if(_sizeSet >= NB_AROUSAL_VALENCE_BEFORE_CLASS){
updateTrainedClass("exited", "positive");
_saveJoy=false;
_nbInitDone++;
qDebug()<<"initJoy done";
}
}
if(_saveSad){
if(_sizeSet >= NB_AROUSAL_VALENCE_BEFORE_CLASS){
updateTrainedClass("calm", "negative");
_saveSad=false;
_nbInitDone++;
qDebug()<<"initSad done";
}
}
if(_saveFear){
if(_sizeSet >= NB_AROUSAL_VALENCE_BEFORE_CLASS){
updateTrainedClass("exited", "negative");
_saveFear=false;
_nbInitDone++;
qDebug()<<"initFear done";
}
}
if(_nbInitDone==4 && _sizeSet>=NB_AROUSAL_VALENCE_BEFORE_CLASS){
if(_arousalClassifier==NULL){
_arousalClassifier = new NaiveBaiseClassifier(_trainedArousalClasses, _totalArousalOccurrences);
}
if(_valenceClassifier==NULL){
_valenceClassifier = new NaiveBaiseClassifier(_trainedValenceClasses, _totalValenceOccurrences);
}
valence=UNSET_VALUE;
valence_4s=UNSET_VALUE;
valence_20s=UNSET_VALUE;
valence_wait_4s=true;
valence_wait_20s=true;
curArousal = _arousalClassifier->classify(_arousalSet);
curValence = _valenceClassifier->classify(_valenceSet);
emit giveEmotion(QVariant(curArousal+" "+curValence));
}
}
void Emotions::wait_arousal_4s(){
arousal_wait_4s=false;
void Emotions::initCalm(){
qDebug()<<"initCalm begin";
_saveCalm = true;
}
void Emotions::wait_arousal_20s(){
arousal_wait_20s=false;
void Emotions::initJoy(){
qDebug()<<"initJoy begin";
_saveJoy = true;
}
void Emotions::wait_valence_4s(){
valence_wait_4s=false;
void Emotions::initSad(){
qDebug()<<"initSad begin";
_saveSad=true;
}
void Emotions::wait_valence_20s(){
valence_wait_20s=false;
void Emotions::initFear(){
qDebug()<<"initFear begin";
_saveFear=true;
}
void Emotions::compute_arousal(double value){
if(arousal_wait_4s){
QTimer::singleShot(4000, this, SLOT(wait_arousal_4s()));
}else{
if(arousal_4s==UNSET_VALUE)
arousal_4s=value;
}
void Emotions::insertValueAndTotal(QMap<double, double> *valueSet, QMap<double, double> *totalSet, double val){
insertValue(valueSet, val);
if(arousal_wait_20s){
QTimer::singleShot(20000, this, SLOT(wait_arousal_20s()));
if(totalSet->contains(val)){
totalSet->insert(val, totalSet->value(val)+1);
}else{
if(arousal_20s==UNSET_VALUE)
arousal_20s=value;
}
if(arousal_4s!=-1 && arousal_20s!=-1){
double arousal_diff = abs(arousal_4s-arousal_20s);
if(max_diff_arousal < arousal_diff)
max_diff_arousal = arousal_diff;
arousal=(arousal_4s-arousal_20s)/max_diff_arousal;
if(arousal_max<arousal || arousal_max==UNSET_VALUE)
arousal_max=arousal;
if(arousal_min>arousal || arousal_max==UNSET_VALUE)
arousal_min=arousal;
computeEmotion();
totalSet->insert(val, 1);
}
}
void Emotions::compute_valence(double value){
if(valence_wait_4s){
QTimer::singleShot(4000, this, SLOT(wait_valence_4s()));
void Emotions::insertValue(QMap<double, double> *valueSet, double val){
if(valueSet->contains(val)){
valueSet->insert(val, valueSet->value(val)+1);
}else{
if(valence_4s==UNSET_VALUE)
valence_4s=value;
valueSet->insert(val, 1);
}
}
if(valence_wait_20s){
QTimer::singleShot(20000, this, SLOT(wait_valence_20s()));
void Emotions::updateTrainedClass(QString arousal, QString valence){
if(_trainedArousalClasses->contains(arousal)){
/*QMapIterator<double, double> arousalIt(*_arousalSet);
while (arousalIt.hasNext()) {
double curKey = arousalIt.key();
double curVal = arousalIt.value();
if(_trainedArousalClasses->value(arousal)->contains(curKey)){
_trainedArousalClasses->value(arousal)->insert(curKey,
_trainedArousalClasses->value(arousal)->value(curKey)+curVal);
}else{
_trainedArousalClasses->value(arousal)->insert(curKey,curVal);
}
}*/
}else{
if(valence_20s==UNSET_VALUE)
valence_20s=value;
_trainedArousalClasses->insert(arousal, _arousalSet);
}
if(valence_4s!=-1 && valence_20s!=-1){
double valence_diff = abs(valence_4s-valence_20s);
if(max_diff_valence < valence_diff)
max_diff_valence = valence_diff;
valence=(valence_4s-valence_20s)/max_diff_valence;
if(valence_max<valence || arousal_min==UNSET_VALUE)
valence_max=valence;
if(valence_min>valence || arousal_min==UNSET_VALUE)
valence_min=valence;
computeEmotion();
}
}
void Emotions::computeEmotion(){
QString emotion = *(new QString("error_novalue"));
//TODO : use better algorithm to suppress too high/min value
double arousal_center = (arousal_max+arousal_min)/2;
double arousal_mean_exited = arousal_max/2;
double arousal_mean_calm = arousal_min/2;
double valence_center = (valence_max+valence_min)/2;
double valence_mean_positive = valence_max/2;
double valence_mean_negative = valence_min/2;
if(valence!=UNSET_VALUE && arousal!=UNSET_VALUE){
if(arousal >= arousal_center
&& valence >= valence_center){
emotion="calm";
}else if(arousal < arousal_center
&& valence < valence_center){
emotion="sad";
}else if((arousal > arousal_center && arousal <= arousal_mean_exited)
&& valence < valence_center){
emotion="fear";
}else if((arousal > arousal_mean_exited)
&& valence < valence_center){
emotion="angry";
}else if((arousal > arousal_center && arousal <= arousal_mean_exited)
&& valence > valence_center){
emotion="joy";
}else if((arousal>arousal_mean_exited)
&& valence > valence_center){
emotion="surprise";
}
emit giveEmotion(emotion);
resetValues();
if(_trainedValenceClasses->contains(valence)){
/*QMapIterator<double, double> valenceIt(*_valenceSet);
while (valenceIt.hasNext()) {
double curKey = valenceIt.key();
double curVal = valenceIt.value();
if(_trainedValenceClasses->value(valence)->contains(curKey)){
_trainedValenceClasses->value(valence)->insert(curKey,
_trainedValenceClasses->value(valence)->value(curKey)+curVal);
}else{
_trainedValenceClasses->value(valence)->insert(curKey,curVal);
}
}*/
}else{
_trainedValenceClasses->insert(valence, _valenceSet);
}
_arousalSet->clear();
_valenceSet->clear();
_sizeSet = 0;
}
......@@ -2,54 +2,56 @@
#define EMOTIONS_H
#include <QString>
#include <QTimer>
#include <math.h>
#include <QObject>
#include <QDebug>
#include <QMapIterator>
#include <QVariant>
#include "naivebaiseclassifier.h"
#define UNSET_VALUE INFINITY
#define NB_AROUSAL_VALENCE_BEFORE_CLASS 1000
class Emotions : public QObject{
Q_OBJECT
private:
NaiveBaiseClassifier* _arousalClassifier;
NaiveBaiseClassifier* _valenceClassifier;
QMap<QString,QMap<double, double>*>* _trainedArousalClasses; // calm / exited
QMap<QString,QMap<double, double>*>* _trainedValenceClasses; // negative / positive
QMap<double, double>* _totalArousalOccurrences;
QMap<double, double>* _totalValenceOccurrences;
bool _saveCalm;
bool _saveJoy;
bool _saveSad;
bool _saveFear;
double _sizeSet;
int _nbInitDone;
QMap<double, double>* _arousalSet;
QMap<double, double>* _valenceSet;
QString curArousal;
QString curValence;
void insertValueAndTotal(QMap<double, double>* valueSet, QMap<double, double>* totalSet, double val);
void insertValue(QMap<double, double> *valueSet, double val);
void updateTrainedClass(QString arousal, QString valence);
public:
Emotions();
private:
double arousal;
double arousal_4s;
double arousal_20s;
double arousal_max;
double arousal_min;
bool arousal_wait_4s;
bool arousal_wait_20s;
double valence;
double valence_4s;
double valence_20s;
double valence_max;
double valence_min;
bool valence_wait_4s;
bool valence_wait_20s;
double max_diff_arousal;
double max_diff_valence;
double max_arousal;
double min_arousal;
double max_valence;
double min_valence;
void resetValues();
void computeEmotion();
public slots:
void compute_arousal(double value);
void compute_valence(double value);
void wait_arousal_4s();
void wait_arousal_20s();
void wait_valence_4s();
void wait_valence_20s();
void arousalValence(double arousal, double valence);
void initCalm();
void initJoy();
void initSad();
void initFear();
signals:
void giveEmotion(QString emotion);
void giveEmotion(QVariant emotion);
};
#endif // EMOTIONS_H
......@@ -5,6 +5,9 @@
#include <hardware/emotiv/sbs2emotivdatareader.h>
#include <mycallback.h>
#include <QVariant>
#include "emotions.h"
Q_DECL_EXPORT int main(int argc, char *argv[])
{
......@@ -25,10 +28,15 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
//create mycallback object and link it well
MyCallback* myCallback = new MyCallback();
Sbs2EmotivDataReader* sbs2DataReader = Sbs2EmotivDataReader::New(myCallback,0);
//communication between SBS2 and QML visualization
Emotions *emotions = new Emotions();
QObject::connect(myCallback, SIGNAL(arousalValence(double, double)), emotions, SLOT(arousalValence(double,double)));
QObject::connect(rootObject, SIGNAL(initSad()), emotions, SLOT(initSad()));
QObject::connect(rootObject, SIGNAL(initCalm()), emotions, SLOT(initCalm()));
QObject::connect(rootObject, SIGNAL(initFear()), emotions, SLOT(initFear()));
QObject::connect(rootObject, SIGNAL(initJoy()), emotions, SLOT(initJoy()));
QObject::connect(emotions, SIGNAL(giveEmotion(QVariant)), rootObject, SLOT(updateEmotion(QVariant)));
QObject::connect(myCallback,SIGNAL(timeTick8()),rootObject,SLOT(timeTick()));
QObject::connect(myCallback,SIGNAL(valuesSignal(QVariant, QVariant)),rootObject,SLOT(channelValue(QVariant, QVariant)));
//required for proper closing of the app
QObject::connect((QObject*)viewer.engine(), SIGNAL(quit()), app.data(), SLOT(quit()));
......
......@@ -13,8 +13,9 @@ void MyCallback::getData(Sbs2Packet *packet)
//set current packet, increment counters
setPacket(packet);
if (currentPacketCounter%8 == 0)
if (currentPacketCounter%8 == 0){
emit timeTick8();
}
double f3=thisPacket->values["F3"];
double f4=thisPacket->values["F4"];
......@@ -33,25 +34,29 @@ void MyCallback::getData(Sbs2Packet *packet)
double af3_beta=thisPacket->filteredValues["AF3"];
double af4_beta=thisPacket->filteredValues["AF4"];
double arousal = (f3_alpha/f3_beta + f4_alpha/f4_beta)/2;
// With bipolar measure :
double valence = ((f4-af4_alpha)/(f4-af4_beta) - (f3-af3_alpha)/(f3-af3_beta));
// Goal is to use af3/af4 because they are hairless copmared to f3/f4
// So we'll apply same idea than colorOfMind but we use sensors otherwise
// colorOfMind : double arousal = (f3_alpha/f3_beta + f4_alpha/f4_beta)/2;
double arousal = (af3_alpha/af3_beta + af4_alpha/af4_beta)/2;
// colorOfMind : double valence = ((f4-af4_alpha)/(f4-af4_beta) - (f3-af3_alpha)/(f3-af3_beta));
double valence = ((af4-f4_alpha)/(af4-f4_beta) - (af3-f3_alpha)/(af3-f3_beta));
emit arousalValue(arousal);
emit valenceValue(valence);
//qDebug()<<"arousal : "<<arousal<<" and valence : "<<valence<<" emited";
emit arousalValence(arousal,valence);
}
void MyCallback::changeBand(QString name)
{
qDebug()<<"MyCallBack : change band to "<<name;
// Values found in emokit_dsp.c (see colorOfMind)
sbs2DataHandler->turnFilterOff();
//qDebug()<<"MyCallBack : change band to "<<name;
if (name.compare("alpha")){
lowFreq = 8;
highFreq = 12;
highFreq = 13;
}else if (name.compare("beta")){
lowFreq = 16;
highFreq = 24;
lowFreq = 13;
highFreq = 30;
}
sbs2DataHandler->turnFilterOn(lowFreq, highFreq, 32); //Need to understand what is "filterOrder" (last param.)
sbs2DataHandler->turnFilterOn(lowFreq, highFreq, 32);
sbs2DataHandler->filter();
}
......@@ -2,6 +2,7 @@
#define MYCALLBACK_H
#include <sbs2callback.h>
#include <sbs2datahandler.h>
class MyCallback : public Sbs2Callback
{
......@@ -20,9 +21,7 @@ public:
void getData(Sbs2Packet *packet);
signals:
void arousalValue(double value);
void valenceValue(double value);
void arousalValence(double arousal, double valence);
};
#endif // MYCALLBACK_H
#include "naivebaiseclassifier.h"
NaiveBaiseClassifier::NaiveBaiseClassifier(QMap<QString, QMap<double, double> *> *trainedClasses,
QMap<double, double> *totalFeatureOccurrences){
_trainedClasses=trainedClasses;
_totalFeatureOccurrences=totalFeatureOccurrences;
}
QString NaiveBaiseClassifier::classify(QMap<double, double> *featureSet){
QMap<QString, double> * classProbability = new QMap<QString, double>();
foreach(QString klass, _trainedClasses->keys()){
classProbability->insert(klass, 0);
}
QMapIterator<double, double> featureIt(*featureSet);
while (featureIt.hasNext()) {
foreach(QString klass, _trainedClasses->keys()){
classProbability->insert(klass,
classProbability->value(klass) + (featureIt.value() * log(probability(featureIt.key(), klass))));
}
}
QString resultClass="";
foreach(QString klass, classProbability->keys()){
if(resultClass.isEmpty()){
resultClass=klass;
}
if(classProbability->value(klass)>classProbability->value(resultClass)){
resultClass=klass;
}
}
return resultClass;
}
double NaiveBaiseClassifier::probability(double feature, QString klass){
double num = _trainedClasses->value(klass)->value(feature)+1.0;
double den = _totalFeatureOccurrences->value(feature)+_trainedClasses->size();
return num/den;
}
/*
This code was inspired by this article :
http://www.nils-haldenwang.de/computer-science/machine-learning/how-to-apply-naive-bayes-classifiers-to-document-classification-problems
*/
#ifndef NAIVEBAISECLASSIFIER_H
#define NAIVEBAISECLASSIFIER_H
#include <QMap>
#include <QMapIterator>
#include <QString>
#include <math.h>
#include <QObject>
class NaiveBaiseClassifier: public QObject
{
Q_OBJECT
private:
QMap<QString,QMap<double, double>*> * _trainedClasses;
QMap<double, double> * _totalFeatureOccurrences;
double probability(double feature, QString klass);
public:
NaiveBaiseClassifier(QMap<QString,QMap<double, double>*>* trainedClasses, // key : class name, value : value found during training and their number of occurences
QMap<double, double>* totalFeatureOccurrences); // key : found feature, value : total of occurences sum of every class
QString classify(QMap<double, double>* featureSet); // featureSet : set of values to classify with number of occurence
};
#endif // NAIVEBAISECLASSIFIER_H
......@@ -5,10 +5,8 @@ Rectangle {
width: 640 //width of the window
height: 640 //height of the window
color: "black" //background color of the window
property int value: 0 //value of one EEG channel
property int filteredValue:0 // filtered value
property int powerValue: 0 //power value of one EEG channel
property int packetCounter: 0 //used for visualizing activity
property string emotion: "wait..." ;
property int packetCounter: 0 ;//used for visualizing activity
/**
Function receiving time ticks from C++ code.
......@@ -18,30 +16,130 @@ Rectangle {
page.packetCounter = (page.packetCounter + 1)%16;
}
function channelValues(value_, filtered_)
function updateEmotion(emotion_)
{
page.value = value_;
page.filteredValue = filtered_;
page.emotion = emotion_;
}
signal initCalm;
signal initSad;
signal initFear;
signal initJoy;
Rectangle
{
id: textRectangle;
anchors.centerIn: parent;
width:300;
height:80;
width:640;
height:150;
color:"black";
Text{
anchors.left: textRectangle.left;
font.pixelSize: 60;
color: "white";
text: page.value;
text: "I think you are : \n\t"+emotion;
}
Text{
anchors.right: textRectangle.right;
font.pixelSize: 60;
color: "red";
text: page.filteredValue;
}
Rectangle
{
id: initDiv;
anchors.bottom: parent.bottom;
width:640;
height:50;
color:"black";
Rectangle{
id:initCalmDiv;
anchors.left: initDiv.left;
width:160;
height:50;
border.color:"white";
color:"black";
Text{
anchors.centerIn: initCalmDiv;
font.pixelSize: 20;
color: "white";
text: "InitCalm";
}
MouseArea
{
anchors.fill: parent
onClicked:
{
page.initCalm();
}
}
}
Rectangle{
id:initSadDiv;
anchors.left:initCalmDiv.right;
width:160;
height:50;
border.color: "white";
color:"black";
Text{
anchors.centerIn: initSadDiv;
font.pixelSize: 20;
color: "white";
text: "InitSad";
}
MouseArea
{
anchors.fill: parent
onClicked:
{
page.initSad();
}
}
}
Rectangle{
id:initFearDiv;
width:160;
height:50;
border.color:"white";
color:"black";
anchors.left: initSadDiv.right
Text{
anchors.centerIn: initFearDiv;
font.pixelSize: 20;
color: "white";
text: "InitFear";
}
MouseArea
{
anchors.fill: parent
onClicked:
{
page.initFear();
}
}
}
Rectangle{
id:initJoyDiv;
width:160;
height:50;
border.color:"white";
color:"black";
anchors.left: initFearDiv.right
Text{
anchors.centerIn: initJoyDiv;
font.pixelSize: 20;
color: "white";
text: "InitJoy";
}
MouseArea
{
anchors.fill: parent
onClicked:
{
page.initJoy();
}
}
}
}
......@@ -69,14 +167,4 @@ Rectangle {
}
}
}
Rectangle
{
//activity visualization
color: "blue"
height: 33
width: height
opacity: page.packetCounter/16.0
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment