From 4cbefd85c5a68b2e1f768f63cdc9fb16cd86a05e Mon Sep 17 00:00:00 2001 From: Francisco Bernardo <f.bernardo@gold.ac.uk> Date: Tue, 30 May 2017 17:06:26 +0100 Subject: [PATCH] Added GVF with git read-tree; I'm assuming that we aren't going to make local changes to the merged in files and will always overwrite the local subdirectory with the latest version from upstream --- GVF/GVF.cpp | 1375 +++++++++++++++++++++++++++++++++ GVF/GVF.h | 487 ++++++++++++ GVF/GVFGesture.h | 240 ++++++ GVF/GVFUtils.h | 309 ++++++++ dependencies/GVF/GVF.cpp | 1375 +++++++++++++++++++++++++++++++++ dependencies/GVF/GVF.h | 487 ++++++++++++ dependencies/GVF/GVFGesture.h | 240 ++++++ dependencies/GVF/GVFUtils.h | 309 ++++++++ 8 files changed, 4822 insertions(+) create mode 100755 GVF/GVF.cpp create mode 100755 GVF/GVF.h create mode 100644 GVF/GVFGesture.h create mode 100755 GVF/GVFUtils.h create mode 100755 dependencies/GVF/GVF.cpp create mode 100755 dependencies/GVF/GVF.h create mode 100644 dependencies/GVF/GVFGesture.h create mode 100755 dependencies/GVF/GVFUtils.h diff --git a/GVF/GVF.cpp b/GVF/GVF.cpp new file mode 100755 index 0000000..36ef7d5 --- /dev/null +++ b/GVF/GVF.cpp @@ -0,0 +1,1375 @@ +/** + * Gesture Variation Follower class allows for early gesture recognition and variation tracking + * + * @details Original algorithm designed and implemented in 2011 at Ircam Centre Pompidou + * by Baptiste Caramiaux and Nicola Montecchio. The library has been created and is maintained by Baptiste Caramiaux + * + * Copyright (C) 2015 Baptiste Caramiaux, Nicola Montecchio + * STMS lab Ircam-CRNS-UPMC, University of Padova, Goldsmiths College University of London + * + * The library is under the GNU Lesser General Public License (LGPL v3) + */ + +#include "GVF.h" +#include <string.h> +#include <stdio.h> +#include <iostream> +#include <fstream> +#include <sstream> +#include <memory> +#include <algorithm> +#include <numeric> + +//debug max +//#include "ext.h" + + +using namespace std; + +//-------------------------------------------------------------- +GVF::GVF() +{ + config.inputDimensions = 2; + config.translate = true; + config.segmentation = false; + + parameters.numberParticles = 1000; + parameters.tolerance = 0.2f; + parameters.resamplingThreshold = 250; + parameters.distribution = 0.0f; + parameters.alignmentVariance = sqrt(0.000001f); + parameters.dynamicsVariance = vector<float>(1,sqrt(0.001f)); + parameters.scalingsVariance = vector<float>(1,sqrt(0.00001f)); + parameters.rotationsVariance = vector<float>(1,sqrt(0.0f)); + parameters.predictionSteps = 1; + parameters.dimWeights = vector<float>(1,sqrt(1.0f)); + parameters.alignmentSpreadingCenter = 0.0; + parameters.alignmentSpreadingRange = 0.2; + parameters.dynamicsSpreadingCenter = 1.0; + parameters.dynamicsSpreadingRange = 0.3; + parameters.scalingsSpreadingCenter = 1.0; + parameters.scalingsSpreadingRange = 0.3; + parameters.rotationsSpreadingCenter = 0.0; + parameters.rotationsSpreadingRange = 0.5; + + tolerancesetmanually = false; + learningGesture = -1; + + normgen = std::mt19937(rd()); + rndnorm = new std::normal_distribution<float>(0.0,1.0); + unifgen = std::default_random_engine(rd()); + rndunif = new std::uniform_real_distribution<float>(0.0,1.0); + +} + +////-------------------------------------------------------------- +//GVF::GVF(GVFConfig _config){ +// setup(_config); +//} +// +////-------------------------------------------------------------- +//GVF::GVF(GVFConfig _config, GVFParameters _parameters){ +// setup(_config, _parameters); +//} +// +////-------------------------------------------------------------- +//void GVF::setup(){ +// +// // use defualt parameters +// GVFConfig defaultConfig; +// +// defaultConfig.inputDimensions = 2; +// defaultConfig.translate = true; +// defaultConfig.segmentation = false; +// +// setup(defaultConfig); +//} +// +////-------------------------------------------------------------- +//void GVF::setup(GVFConfig _config){ +// +// clear(); // just in case +// +// learningGesture = -1; +// +// // Set configuration: +// config = _config; +// +// // default parameters +// GVFParameters defaultParameters; +// defaultParameters.numberParticles = 1000; +// defaultParameters.tolerance = 0.2f; +// defaultParameters.resamplingThreshold = 250; +// defaultParameters.distribution = 0.0f; +// defaultParameters.alignmentVariance = sqrt(0.000001f); +// defaultParameters.dynamicsVariance = vector<float>(1,sqrt(0.001f)); +// defaultParameters.scalingsVariance = vector<float>(1,sqrt(0.00001f)); +// defaultParameters.rotationsVariance = vector<float>(1,sqrt(0.0f)); +// defaultParameters.predictionSteps = 1; +// defaultParameters.dimWeights = vector<float>(1,sqrt(1.0f)); +// +// // default spreading +// defaultParameters.alignmentSpreadingCenter = 0.0; +// defaultParameters.alignmentSpreadingRange = 0.2; +// +// defaultParameters.dynamicsSpreadingCenter = 1.0; +// defaultParameters.dynamicsSpreadingRange = 0.3; +// +// defaultParameters.scalingsSpreadingCenter = 1.0; +// defaultParameters.scalingsSpreadingRange = 0.3; +// +// defaultParameters.rotationsSpreadingCenter = 0.0; +// defaultParameters.rotationsSpreadingRange = 0.0; +// +// tolerancesetmanually = false; +// +// setup(_config, defaultParameters); +// +//} +// +////-------------------------------------------------------------- +//void GVF::setup(GVFConfig _config, GVFParameters _parameters) +//{ +// clear(); // just in case +// // Set configuration and parameters +// config = _config; +// parameters = _parameters; +// // Init random generators +// normgen = std::mt19937(rd()); +// rndnorm = new std::normal_distribution<float>(0.0,1.0); +// unifgen = std::default_random_engine(rd()); +// rndunif = new std::uniform_real_distribution<float>(0.0,1.0); +//} + +//-------------------------------------------------------------- +GVF::~GVF() +{ + if (rndnorm != NULL) + delete (rndnorm); + clear(); // not really necessary but it's polite ;) +} + +//-------------------------------------------------------------- +void GVF::clear() +{ + state = STATE_CLEAR; + gestureTemplates.clear(); + mostProbableIndex = -1; +} + +//-------------------------------------------------------------- +void GVF::startGesture() +{ + if (state==STATE_FOLLOWING) + { + restart(); + } + else if (state==STATE_LEARNING) + { + if (theGesture.getNumberOfTemplates()>0) + { + if (theGesture.getTemplateLength()>0) + addGestureTemplate(theGesture); + } + theGesture.clear(); + } +} + +//-------------------------------------------------------------- +void GVF::addObservation(vector<float> data) +{ + theGesture.addObservation(data); +} + +//-------------------------------------------------------------- +void GVF::addGestureTemplate(GVFGesture & gestureTemplate) +{ + + // if (getState() != GVF::STATE_LEARNING) + // setState(GVF::STATE_LEARNING); + + int inputDimension = gestureTemplate.getNumberDimensions(); + config.inputDimensions = inputDimension; + + gestureTemplates.push_back(gestureTemplate); + activeGestures.push_back(gestureTemplates.size()); + + if(minRange.size() == 0){ + minRange.resize(inputDimension); + maxRange.resize(inputDimension); + } + + for(int j = 0; j < inputDimension; j++){ + minRange[j] = INFINITY; + maxRange[j] = -INFINITY; + } + + // compute min/max from the data + for(int i = 0; i < gestureTemplates.size(); i++){ + GVFGesture& tGestureTemplate = gestureTemplates[i]; + vector<float>& tMinRange = tGestureTemplate.getMinRange(); + vector<float>& tMaxRange = tGestureTemplate.getMaxRange(); + for(int j = 0; j < inputDimension; j++){ + if(tMinRange[j] < minRange[j]) minRange[j] = tMinRange[j]; + if(tMaxRange[j] > maxRange[j]) maxRange[j] = tMaxRange[j]; + } + } + + for(int i = 0; i < gestureTemplates.size(); i++){ + GVFGesture& tGestureTemplate = gestureTemplates[i]; + tGestureTemplate.setMinRange(minRange); + tGestureTemplate.setMaxRange(maxRange); + } + train(); + +} + +//-------------------------------------------------------------- +void GVF::replaceGestureTemplate(GVFGesture & gestureTemplate, int index) +{ + if(gestureTemplate.getNumberDimensions()!=config.inputDimensions) + return; + if(minRange.size() == 0) + { + minRange.resize(config.inputDimensions); + maxRange.resize(config.inputDimensions); + } + for(int j = 0; j < config.inputDimensions; j++) + { + minRange[j] = INFINITY; + maxRange[j] = -INFINITY; + } + if (index<=gestureTemplates.size()) + gestureTemplates[index-1]=gestureTemplate; + for(int i = 0; i < gestureTemplates.size(); i++) + { + GVFGesture& tGestureTemplate = gestureTemplates[i]; + vector<float>& tMinRange = tGestureTemplate.getMinRange(); + vector<float>& tMaxRange = tGestureTemplate.getMaxRange(); + for(int j = 0; j < config.inputDimensions; j++){ + if(tMinRange[j] < minRange[j]) minRange[j] = tMinRange[j]; + if(tMaxRange[j] > maxRange[j]) maxRange[j] = tMaxRange[j]; + } + } + for(int i = 0; i < gestureTemplates.size(); i++) + { + GVFGesture& tGestureTemplate = gestureTemplates[i]; + tGestureTemplate.setMinRange(minRange); + tGestureTemplate.setMaxRange(maxRange); + } +} + +////-------------------------------------------------------------- +//vector<float>& GVF::getGestureTemplateSample(int gestureIndex, float cursor) +//{ +// int frameindex = min((int)(gestureTemplates[gestureIndex].getTemplateLength() - 1), +// (int)(floor(cursor * gestureTemplates[gestureIndex].getTemplateLength() ) ) ); +// return gestureTemplates[gestureIndex].getTemplate()[frameindex]; +//} + +//-------------------------------------------------------------- +GVFGesture & GVF::getGestureTemplate(int index){ + assert(index < gestureTemplates.size()); + return gestureTemplates[index]; +} + +//-------------------------------------------------------------- +vector<GVFGesture> & GVF::getAllGestureTemplates(){ + return gestureTemplates; +} + +//-------------------------------------------------------------- +int GVF::getNumberOfGestureTemplates(){ + return (int)gestureTemplates.size(); +} + +//-------------------------------------------------------------- +void GVF::removeGestureTemplate(int index){ + assert(index < gestureTemplates.size()); + gestureTemplates.erase(gestureTemplates.begin() + index); +} + +//-------------------------------------------------------------- +void GVF::removeAllGestureTemplates(){ + gestureTemplates.clear(); +} + +//---------------------------------------------- +void GVF::train(){ + + if (gestureTemplates.size() > 0) + { + + // get the number of dimension in templates + config.inputDimensions = gestureTemplates[0].getTemplateDimension(); + + dynamicsDim = 2; // hard coded: just speed now + scalingsDim = config.inputDimensions; + + // manage orientation + if (config.inputDimensions==2) rotationsDim=1; + else if (config.inputDimensions==3) rotationsDim=3; + else rotationsDim=0; + + // Init state space + initVec(classes, parameters.numberParticles); // Vector of gesture class + initVec(alignment, parameters.numberParticles); // Vector of phase values (alignment) + initMat(dynamics, parameters.numberParticles, dynamicsDim); // Matric of dynamics + initMat(scalings, parameters.numberParticles, scalingsDim); // Matrix of scaling + if (rotationsDim!=0) initMat(rotations, parameters.numberParticles, rotationsDim); // Matrix of rotations + initMat(offsets, parameters.numberParticles, config.inputDimensions); + initVec(weights, parameters.numberParticles); // Weights + + initMat(particles, parameters.numberParticles, 3); + // std::cout << particles.size() << " " << parameters.numberParticles << std::endl; + + // bayesian elements + initVec(prior, parameters.numberParticles); + initVec(posterior, parameters.numberParticles); + initVec(likelihood, parameters.numberParticles); + + + initPrior(); // prior on init state values + initNoiseParameters(); // init noise parameters (transition and likelihood) + + + // weighted dimensions in case: default is not weighted + if (parameters.dimWeights.size()!=config.inputDimensions){ + parameters.dimWeights = vector<float> (config.inputDimensions); + for(int k = 0; k < config.inputDimensions; k++) parameters.dimWeights[k] = 1.0 / config.inputDimensions; + } + + // NORMALIZATION +// if (config.normalization) { // update the global normaliation factor +// globalNormalizationFactor = -1.0; +// // loop on previous gestures already learned +// // take the max of all the gesture learned ... +// for (int k=0; k<getNumberOfGestureTemplates() ; k++){ +// for(int j = 0; j < config.inputDimensions; j++){ +// float rangetmp = fabs(getGestureTemplate(k).getMaxRange()[j]-getGestureTemplate(k).getMinRange()[j]); +// if (rangetmp > globalNormalizationFactor) +// globalNormalizationFactor=rangetmp; +// } +// } +// } +// // only for logs +// if (config.logOn) { +// vecRef = vector<vector<float> > (parameters.numberParticles); +// vecObs = vector<float> (config.inputDimensions); +// stateNoiseDist = vector<float> (parameters.numberParticles); +// } + } +} + +//-------------------------------------------------------------- +//void GVF::initPrior() +//{ +// +// // PATICLE FILTERING +// for (int k = 0; k < parameters.numberParticles; k++) +// { +// initPrior(k); +// +// classes[k] = activeGestures[k % activeGestures.size()] - 1; +// } +// +//} + +//-------------------------------------------------------------- +void GVF::initPrior() //int pf_n) +{ + for (int pf_n = 0; pf_n < parameters.numberParticles; pf_n++) + { + // alignment + alignment[pf_n] = ((*rndunif)(unifgen) - 0.5) * parameters.alignmentSpreadingRange + parameters.alignmentSpreadingCenter; // spread phase + + + // dynamics + dynamics[pf_n][0] = ((*rndunif)(unifgen) - 0.5) * parameters.dynamicsSpreadingRange + parameters.dynamicsSpreadingCenter; // spread speed + if (dynamics[pf_n].size()>1) + { + dynamics[pf_n][1] = ((*rndunif)(unifgen) - 0.5) * parameters.dynamicsSpreadingRange; // spread accel + } + + // scalings + for(int l = 0; l < scalings[pf_n].size(); l++) { + scalings[pf_n][l] = ((*rndunif)(unifgen) - 0.5) * parameters.scalingsSpreadingRange + parameters.scalingsSpreadingCenter; // spread scalings + } + + // rotations + if (rotationsDim!=0) + for(int l = 0; l < rotations[pf_n].size(); l++) + rotations[pf_n][l] = ((*rndunif)(unifgen) - 0.5) * parameters.rotationsSpreadingRange + parameters.rotationsSpreadingCenter; // spread rotations + + if (config.translate) for(int l = 0; l < offsets[pf_n].size(); l++) offsets[pf_n][l] = 0.0; + + + prior[pf_n] = 1.0 / (float) parameters.numberParticles; + + // set the posterior to the prior at the initialization + posterior[pf_n] = prior[pf_n]; + + classes[pf_n] = activeGestures[pf_n % activeGestures.size()] - 1; + } + +} + +//-------------------------------------------------------------- +void GVF::initNoiseParameters() { + + // NOISE (ADDITIVE GAUSSIAN NOISE) + // --------------------------- + + if (parameters.dynamicsVariance.size() != dynamicsDim) + { + float variance = parameters.dynamicsVariance[0]; + parameters.dynamicsVariance.resize(dynamicsDim); + for (int k=0; k<dynamicsDim; k++) + parameters.dynamicsVariance[k] = variance; + } + + if (parameters.scalingsVariance.size() != scalingsDim) + { + float variance = parameters.scalingsVariance[0]; + parameters.scalingsVariance.resize(scalingsDim); + for (int k=0; k<scalingsDim; k++) + parameters.scalingsVariance[k] = variance; + } + + if (rotationsDim!=0) + { + if (parameters.rotationsVariance.size() != rotationsDim) + { + float variance = parameters.rotationsVariance[0]; + parameters.rotationsVariance.resize(rotationsDim); + for (int k=0; k<rotationsDim; k++) + parameters.rotationsVariance[k] = variance; + } + } + + // ADAPTATION OF THE TOLERANCE IF DEFAULT PARAMTERS + // --------------------------- + if (!tolerancesetmanually){ + float obsMeanRange = 0.0f; + for (int gt=0; gt<gestureTemplates.size(); gt++) { + for (int d=0; d<config.inputDimensions; d++) + obsMeanRange += (gestureTemplates[gt].getMaxRange()[d] - gestureTemplates[gt].getMinRange()[d]) + /config.inputDimensions; + } + obsMeanRange /= gestureTemplates.size(); + parameters.tolerance = obsMeanRange / 4.0f; // dividing by an heuristic factor [to be learned?] + } +} + +//-------------------------------------------------------------- +void GVF::setState(GVFState _state, vector<int> indexes) +{ + switch (_state) + { + case STATE_CLEAR: + clear(); + theGesture.clear(); + break; + + case STATE_LEARNING: + if ((state==STATE_LEARNING) && (theGesture.getNumberOfTemplates()>0)) + { + if (learningGesture==-1) + addGestureTemplate(theGesture); + else + { + replaceGestureTemplate(theGesture, learningGesture); + learningGesture=-1; + } + if (indexes.size()!=0) + learningGesture=indexes[0]; + } + state = _state; + theGesture.clear(); + break; + + case STATE_FOLLOWING: + if ((state==STATE_LEARNING) && (theGesture.getNumberOfTemplates()>0)) + { + if (learningGesture==-1) + addGestureTemplate(theGesture); + else + { + replaceGestureTemplate(theGesture, learningGesture); + learningGesture=-1; + } + } + if (gestureTemplates.size() > 0) + { + train(); + state = _state; + } + else + state = STATE_CLEAR; + theGesture.clear(); + break; + + default: + theGesture.clear(); + break; + } +} + +//-------------------------------------------------------------- +GVF::GVFState GVF::getState() +{ + return state; +} + +////-------------------------------------------------------------- +//int GVF::getDynamicsDimension(){ +// return dynamicsDim; +//} + +//-------------------------------------------------------------- +vector<int> GVF::getGestureClasses() +{ + return classes; +} + +////-------------------------------------------------------------- +//vector<float> GVF::getAlignment(){ +// return alignment; +//} +// +////-------------------------------------------------------------- +//vector<float> GVF::getEstimatedAlignment(){ +// return estimatedAlignment; +//} +// +////-------------------------------------------------------------- +//vector< vector<float> > GVF::getDynamics(){ +// return dynamics; +//} +// +////-------------------------------------------------------------- +//vector< vector<float> > GVF::getEstimatedDynamics(){ +// return estimatedDynamics; +//} +// +////-------------------------------------------------------------- +//vector< vector<float> > GVF::getScalings(){ +// return scalings; +//} +// +////-------------------------------------------------------------- +//vector< vector<float> > GVF::getEstimatedScalings(){ +// return estimatedScalings; +//} +// +////-------------------------------------------------------------- +//vector< vector<float> > GVF::getRotations(){ +// return rotations; +//} +// +////-------------------------------------------------------------- +//vector< vector<float> > GVF::getEstimatedRotations(){ +// return estimatedRotations; +//} + +////-------------------------------------------------------------- +//vector<float> GVF::getEstimatedProbabilities(){ +// return estimatedProbabilities; +//} +// +////-------------------------------------------------------------- +//vector<float> GVF::getEstimatedLikelihoods(){ +// return estimatedLikelihoods; +//} +// +////-------------------------------------------------------------- +//vector<float> GVF::getWeights(){ +// return weights; +//} +// +////-------------------------------------------------------------- +//vector<float> GVF::getPrior(){ +// return prior; +//} + +////-------------------------------------------------------------- +//vector<vector<float> > GVF::getVecRef() { +// return vecRef; +//} +// +////-------------------------------------------------------------- +//vector<float> GVF::getVecObs() { +// return vecObs; +//} +// +////-------------------------------------------------------------- +//vector<float> GVF::getStateNoiseDist(){ +// return stateNoiseDist; +//} + +////-------------------------------------------------------------- +//int GVF::getScalingsDim(){ +// return scalingsDim; +//} +// +////-------------------------------------------------------------- +//int GVF::getRotationsDim(){ +// return rotationsDim; +//} + +//-------------------------------------------------------------- +void GVF::restart() +{ + theGesture.clear(); + initPrior(); +} + +#pragma mark - PARTICLE FILTERING + +//-------------------------------------------------------------- +void GVF::updatePrior(int n) { + + // Update alignment / dynamics / scalings + float L = gestureTemplates[classes[n]].getTemplateLength(); + alignment[n] += (*rndnorm)(normgen) * parameters.alignmentVariance + dynamics[n][0]/L; // + dynamics[n][1]/(L*L); + + if (dynamics[n].size()>1){ + dynamics[n][0] += (*rndnorm)(normgen) * parameters.dynamicsVariance[0] + dynamics[n][1]/L; + dynamics[n][1] += (*rndnorm)(normgen) * parameters.dynamicsVariance[1]; + } + else { + dynamics[n][0] += (*rndnorm)(normgen) * parameters.dynamicsVariance[0]; + } + + // for(int l= 0; l < dynamics[n].size(); l++) dynamics[n][l] += (*rndnorm)(normgen) * parameters.dynamicsVariance[l]; + for(int l= 0; l < scalings[n].size(); l++) scalings[n][l] += (*rndnorm)(normgen) * parameters.scalingsVariance[l]; + if (rotationsDim!=0) for(int l= 0; l < rotations[n].size(); l++) rotations[n][l] += (*rndnorm)(normgen) * parameters.rotationsVariance[l]; + + // update prior (bayesian incremental inference) + prior[n] = posterior[n]; +} + +//-------------------------------------------------------------- +void GVF::updateLikelihood(vector<float> obs, int n) +{ + +// if (config.normalization) for (int kk=0; kk<vobs.size(); kk++) vobs[kk] = vobs[kk] / globalNormalizationFactor; + + if(alignment[n] < 0.0) + { + alignment[n] = fabs(alignment[n]); // re-spread at the beginning +// if (config.segmentation) +// classes[n] = n % getNumberOfGestureTemplates(); + } + else if(alignment[n] > 1.0) + { + if (config.segmentation) + { +// alignment[n] = fabs(1.0-alignment[n]); // re-spread at the beginning + alignment[n] = fabs((*rndunif)(unifgen) * 0.5); // + classes[n] = n % getNumberOfGestureTemplates(); + offsets[n] = obs; + // dynamics + dynamics[n][0] = ((*rndunif)(unifgen) - 0.5) * parameters.dynamicsSpreadingRange + parameters.dynamicsSpreadingCenter; // spread speed + if (dynamics[n].size()>1) + dynamics[n][1] = ((*rndunif)(unifgen) - 0.5) * parameters.dynamicsSpreadingRange; + // scalings + for(int l = 0; l < scalings[n].size(); l++) + scalings[n][l] = ((*rndunif)(unifgen) - 0.5) * parameters.scalingsSpreadingRange + parameters.scalingsSpreadingCenter; // spread scalings + // rotations + if (rotationsDim!=0) + for(int l = 0; l < rotations[n].size(); l++) + rotations[n][l] = ((*rndunif)(unifgen) - 0.5) * parameters.rotationsSpreadingRange + parameters.rotationsSpreadingCenter; // spread rotations + // prior + prior[n] = 1/(float)parameters.numberParticles; + } + else{ + alignment[n] = fabs(2.0-alignment[n]); // re-spread at the end + } + } + + vector<float> vobs(config.inputDimensions); + setVec(vobs, obs); + + if (config.translate) + for (int j=0; j < config.inputDimensions; j++) + vobs[j] = vobs[j] - offsets[n][j]; + + + // take vref from template at the given alignment + int gestureIndex = classes[n]; + float cursor = alignment[n]; + int frameindex = min((int)(gestureTemplates[gestureIndex].getTemplateLength() - 1), + (int)(floor(cursor * gestureTemplates[gestureIndex].getTemplateLength() ) ) ); +// return gestureTemplates[gestureIndex].getTemplate()[frameindex]; + vector<float> vref = gestureTemplates[gestureIndex].getTemplate()[frameindex];; //getGestureTemplateSample(classes[n], alignment[n]); + + // Apply scaling coefficients + for (int k=0;k < config.inputDimensions; k++) + { +// if (config.normalization) vref[k] = vref[k] / globalNormalizationFactor; + vref[k] *= scalings[n][k]; + } + + // Apply rotation coefficients + if (config.inputDimensions==2) { + float tmp0=vref[0]; float tmp1=vref[1]; + vref[0] = cos(rotations[n][0])*tmp0 - sin(rotations[n][0])*tmp1; + vref[1] = sin(rotations[n][0])*tmp0 + cos(rotations[n][0])*tmp1; + } + else if (config.inputDimensions==3) { + // Rotate template sample according to the estimated angles of rotations (3d) + vector<vector< float> > RotMatrix = getRotationMatrix3d(rotations[n][0],rotations[n][1],rotations[n][2]); + vref = multiplyMat(RotMatrix, vref); + } + + // weighted euclidean distance + float dist = distance_weightedEuclidean(vref,vobs,parameters.dimWeights); + + if(parameters.distribution == 0.0f){ // Gaussian distribution + likelihood[n] = exp(- dist * 1 / (parameters.tolerance * parameters.tolerance)); + } + else { // Student's distribution + likelihood[n] = pow(dist/parameters.distribution + 1, -parameters.distribution/2 - 1); // dimension is 2 .. pay attention if editing] + } +// // if log on keep track on vref and vobs +// if (config.logOn){ +// vecRef.push_back(vref); +// vecObs = vobs; +// } +} + +//-------------------------------------------------------------- +void GVF::updatePosterior(int n) { + posterior[n] = prior[n] * likelihood[n]; +} + +//-------------------------------------------------------------- +GVFOutcomes & GVF::update(vector<float> & observation) +{ + + if (state != GVF::STATE_FOLLOWING) setState(GVF::STATE_FOLLOWING); + + theGesture.addObservation(observation); + vector<float> obs = theGesture.getLastObservation(); + + // std::cout << obs[0] << " " << obs[0] << " " + // << gestureTemplates[0].getTemplate()[20][0] << " " << gestureTemplates[0].getTemplate()[20][1] << " " + // << gestureTemplates[1].getTemplate()[20][0] << " " << gestureTemplates[1].getTemplate()[20][1] << std::endl; + + + // for each particle: perform updates of state space / likelihood / prior (weights) + float sumw = 0.0; + for(int n = 0; n< parameters.numberParticles; n++) + { + + for (int m=0; m<parameters.predictionSteps; m++) + { + updatePrior(n); + updateLikelihood(obs, n); + updatePosterior(n); + } + + sumw += posterior[n]; // sum posterior to normalise the distrib afterwards + + particles[n][0] = alignment[n]; + particles[n][1] = scalings[n][0]; + particles[n][2] = classes[n]; + } + + // normalize the weights and compute the resampling criterion + float dotProdw = 0.0; + for (int k = 0; k < parameters.numberParticles; k++){ + posterior[k] /= sumw; + dotProdw += posterior[k] * posterior[k]; + } + // avoid degeneracy (no particles active, i.e. weight = 0) by resampling + if( (1./dotProdw) < parameters.resamplingThreshold) + resampleAccordingToWeights(obs); + + // estimate outcomes + estimates(); + + return outcomes; + +} + +//-------------------------------------------------------------- +void GVF::resampleAccordingToWeights(vector<float> obs) +{ + // covennient + int numOfPart = parameters.numberParticles; + + // cumulative dist + vector<float> c(numOfPart); + + // tmp matrices + vector<int> oldClasses; + vector<float> oldAlignment; + vector< vector<float> > oldDynamics; + vector< vector<float> > oldScalings; + vector< vector<float> > oldRotations; + + setVec(oldClasses, classes); + setVec(oldAlignment, alignment); + setMat(oldDynamics, dynamics); + setMat(oldScalings, scalings); + if (rotationsDim!=0) setMat(oldRotations, rotations); + + + c[0] = 0; + for(int i = 1; i < numOfPart; i++) c[i] = c[i-1] + posterior[i]; + + + float u0 = (*rndunif)(unifgen)/numOfPart; + + int i = 0; + for (int j = 0; j < numOfPart; j++) + { + float uj = u0 + (j + 0.) / numOfPart; + + while (uj > c[i] && i < numOfPart - 1){ + i++; + } + + classes[j] = oldClasses[i]; + alignment[j] = oldAlignment[i]; + + for (int l=0;l<dynamicsDim;l++) dynamics[j][l] = oldDynamics[i][l]; + for (int l=0;l<scalingsDim;l++) scalings[j][l] = oldScalings[i][l]; + if (rotationsDim!=0) for (int l=0;l<rotationsDim;l++) rotations[j][l] = oldRotations[i][l]; + + // update posterior (partilces' weights) + posterior[j] = 1.0/(float)numOfPart; + } + +} + + +//-------------------------------------------------------------- +void GVF::estimates(){ + + + int numOfPart = parameters.numberParticles; + vector<float> probabilityNormalisation(getNumberOfGestureTemplates()); + setVec(probabilityNormalisation, 0.0f, getNumberOfGestureTemplates()); // rows are gestures + setVec(estimatedAlignment, 0.0f, getNumberOfGestureTemplates()); // rows are gestures + setMat(estimatedDynamics, 0.0f, getNumberOfGestureTemplates(), dynamicsDim); // rows are gestures, cols are features + probabilities + setMat(estimatedScalings, 0.0f, getNumberOfGestureTemplates(), scalingsDim); // rows are gestures, cols are features + probabilities + if (rotationsDim!=0) setMat(estimatedRotations, 0.0f, getNumberOfGestureTemplates(), rotationsDim); // .. + setVec(estimatedProbabilities, 0.0f, getNumberOfGestureTemplates()); // rows are gestures + setVec(estimatedLikelihoods, 0.0f, getNumberOfGestureTemplates()); // rows are gestures + + // float sumposterior = 0.; + + for(int n = 0; n < numOfPart; n++) + { + probabilityNormalisation[classes[n]] += posterior[n]; + } + + + // compute the estimated features and likelihoods + for(int n = 0; n < numOfPart; n++) + { + + // sumposterior += posterior[n]; + estimatedAlignment[classes[n]] += alignment[n] * posterior[n]; + + for(int m = 0; m < dynamicsDim; m++) + estimatedDynamics[classes[n]][m] += dynamics[n][m] * (posterior[n]/probabilityNormalisation[classes[n]]); + + for(int m = 0; m < scalingsDim; m++) + estimatedScalings[classes[n]][m] += scalings[n][m] * (posterior[n]/probabilityNormalisation[classes[n]]); + + if (rotationsDim!=0) + for(int m = 0; m < rotationsDim; m++) + estimatedRotations[classes[n]][m] += rotations[n][m] * (posterior[n]/probabilityNormalisation[classes[n]]); + + if (!isnan(posterior[n])) + estimatedProbabilities[classes[n]] += posterior[n]; + estimatedLikelihoods[classes[n]] += likelihood[n]; + } + + // calculate most probable index during scaling... + float maxProbability = 0.0f; + mostProbableIndex = -1; + + for(int gi = 0; gi < getNumberOfGestureTemplates(); gi++) + { + if(estimatedProbabilities[gi] > maxProbability){ + maxProbability = estimatedProbabilities[gi]; + mostProbableIndex = gi; + } + } + // std::cout << estimatedProbabilities[0] << " " << estimatedProbabilities[1] << std::endl; + + // outcomes.estimations.clear(); + outcomes.likelihoods.clear(); + outcomes.alignments.clear(); + outcomes.scalings.clear(); + outcomes.dynamics.clear(); + outcomes.rotations.clear(); + + // most probable gesture index + outcomes.likeliestGesture = mostProbableIndex; + + // Fill estimation for each gesture + for (int gi = 0; gi < gestureTemplates.size(); ++gi) { + + // GVFEstimation estimation; + outcomes.likelihoods.push_back(estimatedProbabilities[gi]); + outcomes.alignments.push_back(estimatedAlignment[gi]); + // estimation.probability = estimatedProbabilities[gi]; + // estimation.alignment = estimatedAlignment[gi]; + + + vector<float> gDynamics(dynamicsDim, 0.0); + for (int j = 0; j < dynamicsDim; ++j) gDynamics[j] = estimatedDynamics[gi][j]; + outcomes.dynamics.push_back(gDynamics); + + vector<float> gScalings(scalingsDim, 0.0); + for (int j = 0; j < scalingsDim; ++j) gScalings[j] = estimatedScalings[gi][j]; + outcomes.scalings.push_back(gScalings); + + vector<float> gRotations; + if (rotationsDim!=0) + { + gRotations.resize(rotationsDim); + for (int j = 0; j < rotationsDim; ++j) gRotations[j] = estimatedRotations[gi][j]; + outcomes.rotations.push_back(gRotations); + } + + // estimation.likelihood = estimatedLikelihoods[gi]; + + // push estimation for gesture gi in outcomes + // outcomes.estimations.push_back(estimation); + } + + + // assert(outcomes.estimations.size() == gestureTemplates.size()); + +} + +////-------------------------------------------------------------- +//int GVF::getMostProbableGestureIndex() +//{ +// return mostProbableIndex; +//} + +////-------------------------------------------------------------- +//GVFOutcomes GVF::getOutcomes() +//{ +// return outcomes; +//} + +////-------------------------------------------------------------- +//GVFEstimation GVF::getTemplateRecogInfo(int templateNumber) +//{ +// if (getOutcomes().estimations.size() <= templateNumber) { +// GVFEstimation estimation; +// return estimation; // blank +// } +// else +// return getOutcomes().estimations[templateNumber]; +//} +// +////-------------------------------------------------------------- +//GVFEstimation GVF::getRecogInfoOfMostProbable() // FIXME: Rename! +//{ +// int indexMostProbable = getMostProbableGestureIndex(); +// +// if ((getState() == GVF::STATE_FOLLOWING) && (getMostProbableGestureIndex() != -1)) { +// return getTemplateRecogInfo(indexMostProbable); +// } +// else { +// GVFEstimation estimation; +// return estimation; // blank +// } +//} + + +////-------------------------------------------------------------- +//vector<float> & GVF::getGestureProbabilities() +//{ +// gestureProbabilities.resize(getNumberOfGestureTemplates()); +// setVec(gestureProbabilities, 0.0f); +// for(int n = 0; n < parameters.numberParticles; n++) +// gestureProbabilities[classes[n]] += posterior[n]; +// +// return gestureProbabilities; +//} + +//-------------------------------------------------------------- +const vector<vector<float> > & GVF::getParticlesPositions(){ + return particles; +} + +////-------------------------------------------------------------- +//void GVF::setParameters(GVFParameters _parameters){ +// +// // if the number of particles has changed, we have to re-allocate matrices +// if (_parameters.numberParticles != parameters.numberParticles) +// { +// parameters = _parameters; +// +// // minimum number of particles allowed +// if (parameters.numberParticles < 4) parameters.numberParticles = 4; +// +// // re-learn +// train(); +// +// // adapt the resampling threshold in case if RT < NS +// if (parameters.numberParticles <= parameters.resamplingThreshold) +// parameters.resamplingThreshold = parameters.numberParticles / 4; +// +// } +// else +// parameters = _parameters; +// +// +//} +// +//GVFParameters GVF::getParameters(){ +// return parameters; +//} + +//-------------------------------------------------------------- +// Update the number of particles +void GVF::setNumberOfParticles(int numberOfParticles){ + + parameters.numberParticles = numberOfParticles; + + if (parameters.numberParticles < 4) // minimum number of particles allowed + parameters.numberParticles = 4; + + train(); + + if (parameters.numberParticles <= parameters.resamplingThreshold) { + parameters.resamplingThreshold = parameters.numberParticles / 4; + } + +} + +//-------------------------------------------------------------- +int GVF::getNumberOfParticles(){ + return parameters.numberParticles; // Return the number of particles +} + +//-------------------------------------------------------------- +void GVF::setActiveGestures(vector<int> activeGestureIds) +{ + int argmax = *std::max_element(activeGestureIds.begin(), activeGestureIds.end()); + if (activeGestureIds[argmax] <= gestureTemplates.size()) + { + activeGestures = activeGestureIds; + } + else + { + activeGestures.resize(gestureTemplates.size()); + std::iota(activeGestures.begin(), activeGestures.end(), 1); + } +} + +//-------------------------------------------------------------- +void GVF::setPredictionSteps(int predictionSteps) +{ + if (predictionSteps<1) + parameters.predictionSteps = 1; + else + parameters.predictionSteps = predictionSteps; +} + +//-------------------------------------------------------------- +int GVF::getPredictionSteps() +{ + return parameters.predictionSteps; // Return the number of particles +} + +//-------------------------------------------------------------- +// Update the resampling threshold used to avoid degeneracy problem +void GVF::setResamplingThreshold(int _resamplingThreshold){ + if (_resamplingThreshold >= parameters.numberParticles) + _resamplingThreshold = floor(parameters.numberParticles/2.0f); + parameters.resamplingThreshold = _resamplingThreshold; +} + +//-------------------------------------------------------------- +// Return the resampling threshold used to avoid degeneracy problem +int GVF::getResamplingThreshold(){ + return parameters.resamplingThreshold; +} + +//-------------------------------------------------------------- +// Update the standard deviation of the observation distribution +// this value acts as a tolerance for the algorithm +// low value: less tolerant so more precise but can diverge +// high value: more tolerant so less precise but converge more easily +void GVF::setTolerance(float _tolerance){ + if (_tolerance <= 0.0) _tolerance = 0.1; + parameters.tolerance = _tolerance; + tolerancesetmanually = true; +} + +//-------------------------------------------------------------- +float GVF::getTolerance(){ + return parameters.tolerance; +} + +////-------------------------------------------------------------- +void GVF::setDistribution(float _distribution){ + //nu = _distribution; + parameters.distribution = _distribution; +} +// +////-------------------------------------------------------------- +//float GVF::getDistribution(){ +// return parameters.distribution; +//} + +//void GVF::setDimWeights(vector<float> dimWeights){ +// if (dimWeights.size()!=parameters.dimWeights.size()) +// parameters.dimWeights.resize(dimWeights.size()); +// parameters.dimWeights = dimWeights; +//} +// +//vector<float> GVF::getDimWeights(){ +// return parameters.dimWeights; +//} + + +//// VARIANCE COEFFICIENTS: PHASE +////-------------------------------------------------------------- +//void GVF::setAlignmentVariance(float alignmentVariance){ +// parameters.alignmentVariance = sqrt(alignmentVariance); +//} +////-------------------------------------------------------------- +//float GVF::getAlignmentVariance(){ +// return parameters.alignmentVariance; +//} + + +// VARIANCE COEFFICIENTS: DYNAMICS +//-------------------------------------------------------------- +//void GVF::setDynamicsVariance(float dynVariance) +//{ +// for (int k=0; k< parameters.dynamicsVariance.size(); k++) +// parameters.dynamicsVariance[k] = dynVariance; +//} +//-------------------------------------------------------------- +void GVF::setDynamicsVariance(float dynVariance, int dim) +{ + if (dim == -1) + { + for (int k=0; k< parameters.dynamicsVariance.size(); k++) + parameters.dynamicsVariance[k] = dynVariance; + } + else + { + if (dim<parameters.dynamicsVariance.size()) + parameters.dynamicsVariance[dim-1] = dynVariance; + } +} + +//-------------------------------------------------------------- +void GVF::setDynamicsVariance(vector<float> dynVariance) +{ + parameters.dynamicsVariance = dynVariance; +} +//-------------------------------------------------------------- +vector<float> GVF::getDynamicsVariance() +{ + return parameters.dynamicsVariance; +} + +//-------------------------------------------------------------- +void GVF::setScalingsVariance(float scaleVariance, int dim) +{ + if (dim == -1) + { + for (int k=0; k< parameters.scalingsVariance.size(); k++) + parameters.scalingsVariance[k] = scaleVariance; + } + else + { + if (dim<parameters.scalingsVariance.size()) + parameters.scalingsVariance[dim-1] = scaleVariance; + } +} + +//-------------------------------------------------------------- +void GVF::setScalingsVariance(vector<float> scaleVariance) +{ + parameters.scaleVariance = scaleVariance; +} + +//-------------------------------------------------------------- +vector<float> GVF::getScalingsVariance() +{ + return parameters.scalingsVariance; +} + +//-------------------------------------------------------------- +void GVF::setRotationsVariance(float rotationVariance, int dim) +{ + if (dim == -1) + { + for (int k=0; k< parameters.rotationsVariance.size(); k++) + parameters.rotationsVariance[k] = rotationVariance; + } + else + { + if (dim<parameters.rotationsVariance.size()) + parameters.scalingsVariance[dim-1] = rotationVariance; + } +} + +//-------------------------------------------------------------- +void GVF::setRotationsVariance(vector<float> rotationVariance) +{ + parameters.scaleVariance = rotationVariance; +} + +//-------------------------------------------------------------- +vector<float> GVF::getRotationsVariance() +{ + return parameters.rotationsVariance; +} + +//-------------------------------------------------------------- +void GVF::setSpreadDynamics(float center, float range, int dim) +{ + parameters.dynamicsSpreadingCenter = center; + parameters.dynamicsSpreadingRange = range; +} + +//-------------------------------------------------------------- +void GVF::setSpreadScalings(float center, float range, int dim) +{ + parameters.scalingsSpreadingCenter = center; + parameters.scalingsSpreadingRange = range; +} + +//-------------------------------------------------------------- +void GVF::setSpreadRotations(float center, float range, int dim) +{ + parameters.rotationsSpreadingCenter = center; + parameters.rotationsSpreadingRange = range; +} + +//-------------------------------------------------------------- +void GVF::translate(bool translateFlag) +{ + config.translate = translateFlag; +} + +//-------------------------------------------------------------- +void GVF::segmentation(bool segmentationFlag) +{ + config.segmentation = segmentationFlag; +} + + +// UTILITIES + +//-------------------------------------------------------------- +// Save function. This function is used by applications to save the +// vocabulary in a text file given by filename (filename is also the complete path + filename) +void GVF::saveTemplates(string filename){ + + std::string directory = filename; + + std::ofstream file_write(directory.c_str()); + + for(int i=0; i < gestureTemplates.size(); i++) // Number of gesture templates + { + file_write << "template " << i << " " << config.inputDimensions << endl; + vector<vector<float> > templateTmp = gestureTemplates[i].getTemplate(); + for(int j = 0; j < templateTmp.size(); j++) + { + for(int k = 0; k < config.inputDimensions; k++) + file_write << templateTmp[j][k] << " "; + file_write << endl; + } + } + file_write.close(); + +} + + + + +//-------------------------------------------------------------- +// Load function. This function is used by applications to load a vocabulary +// given by filename (filename is also the complete path + filename) +void GVF::loadTemplates(string filename){ + // clear(); + // + + GVFGesture loadedGesture; + loadedGesture.clear(); + + ifstream infile; + stringstream doung; + + infile.open (filename.c_str(), ifstream::in); + // + string line; + vector<string> list; + int cl = -1; + while(!infile.eof()) + { + cl++; + infile >> line; + + list.push_back(line); + } + + int k = 0; + int template_id = -1; + int template_dim = 0; + + + while (k < (list.size() - 1)){ // TODO to be changed if dim>2 + + + if (!strcmp(list[k].c_str(),"template")) + { + template_id = atoi(list[k+1].c_str()); + template_dim = atoi(list[k+2].c_str()); + k = k + 3; + + if (loadedGesture.getNumberOfTemplates() > 0){ + addGestureTemplate(loadedGesture); + loadedGesture.clear(); + } + } + + if (template_dim <= 0){ + //post("bug dim = -1"); + } + else{ + + vector<float> vect(template_dim); + + for (int kk = 0; kk < template_dim; kk++) + vect[kk] = (float) atof(list[k + kk].c_str()); + + loadedGesture.addObservation(vect); + } + k += template_dim; + + } + + if (loadedGesture.getTemplateLength() > 0){ + addGestureTemplate(loadedGesture); + loadedGesture.clear(); + } + + infile.close(); +} + + + + + + diff --git a/GVF/GVF.h b/GVF/GVF.h new file mode 100755 index 0000000..647fe7c --- /dev/null +++ b/GVF/GVF.h @@ -0,0 +1,487 @@ +/** + * Gesture Variation Follower class allows for early gesture recognition and variation tracking + * + * @details Original algorithm designed and implemented in 2011 at Ircam Centre Pompidou + * by Baptiste Caramiaux and Nicola Montecchio. The library has been created and is maintained by Baptiste Caramiaux + * + * Copyright (C) 2015 Baptiste Caramiaux, Nicola Montecchio + * STMS lab Ircam-CRNS-UPMC, University of Padova, Goldsmiths College University of London + * + * The library is under the GNU Lesser General Public License (LGPL v3) + */ + + +#ifndef _H_GVF +#define _H_GVF + +#include "GVFUtils.h" +#include "GVFGesture.h" +#include <random> +#include <iostream> +#include <iomanip> +#include <string> +#include <map> +#include <random> +#include <cmath> + + +using namespace std; + +class GVF +{ + +public: + + /** + * GVF possible states + */ + enum GVFState + { + STATE_CLEAR = 0, /**< STATE_CLEAR: clear the GVF and be in standby */ + STATE_LEARNING, /**< STATE_LEARNING: recording mode, input gestures are added to the templates */ + STATE_FOLLOWING, /**< STATE_FOLLOWING: tracking mode, input gestures are classifed and their variations tracked (need the GVF to be trained) */ + STATE_BYPASS /**< STATE_BYPASS: by pass GVF but does not erase templates or training */ + }; + + +#pragma mark - Constructors + + /** + * GVF default constructor + * @details use default configuration and parameters, can be changed using accessors + */ + GVF(); + + /** + * GVF default destructor + */ + ~GVF(); + +#pragma mark - Gesture templates + + /** + * Start a gesture either to be recorded or followed + */ + void startGesture(); + + /** + * Add an observation to a gesture template + * @details + * @param data vector of features + */ + void addObservation(vector<float> data); + + /** + * Add gesture template to the vocabulary + * + * @details a gesture template is a GVFGesture object and can be added directly to the vocabulqry or + * recorded gesture templates by using this method + * @param gestureTemplate the gesture template to be recorded + */ + void addGestureTemplate(GVFGesture & gestureTemplate); + + /** + * Replace a specific gesture template by another + * + * @param gestureTemplate the gesture template to be used + * @param index the gesture index (as integer) to be replaced + */ + void replaceGestureTemplate(GVFGesture & gestureTemplate, int index); + + /** + * Remove a specific template + * + * @param index the gesture index (as integer) to be removed + */ + void removeGestureTemplate(int index); + + /** + * Remove every recorded gesture template + */ + void removeAllGestureTemplates(); + + /** + * Get a specific gesture template a gesture template by another + * + * @param index the index of the template to be returned + * @return the template + */ + GVFGesture & getGestureTemplate(int index); + + /** + * Get every recorded gesture template + * + * @return the vecotr of gesture templates + */ + vector<GVFGesture> & getAllGestureTemplates(); + + /** + * Get number of gesture templates in the vocabulary + * @return the number of templates + */ + int getNumberOfGestureTemplates(); + + /** + * Get gesture classes + */ + vector<int> getGestureClasses(); + + +#pragma mark - Recognition and tracking + + /** + * Set the state of GVF + * @param _state the state to be given to GVF, it is a GVFState + * @param indexes an optional argument providing a list of gesture index. + * In learning mode the index of the gesture being recorded can be given as an argument + * since the type is vector<int>, it should be something like '{3}'. In following mode, the list of indexes + * is the list of active gestures to be considered in the recognition/tracking. + */ + void setState(GVFState _state, vector<int> indexes = vector<int>()); + + /** + * Return the current state of GVF + * @return GVFState the current state + */ + GVFState getState(); + + /** + * Compute the estimated gesture and its potential variations + * + * @details infers the probability that the current observation belongs to + * one of the recorded gesture template and track the variations of this gesture + * according to each template + * + * @param observation vector of the observation data at current time + * @return the estimated probabilities and variaitons relative to each template + */ + GVFOutcomes & update(vector<float> & observation); + + /** + * Define a subset of gesture templates on which to perform the recognition + * and variation tracking + * + * @details By default every recorded gesture template is considered + * @param set of gesture template index to consider + */ + void setActiveGestures(vector<int> activeGestureIds); + + /** + * Restart GVF + * @details re-sample particles at the origin (i.e. initial prior) + */ + void restart(); + + /** + * Clear GVF + * @details delete templates + */ + void clear(); + + /** + * Translate data according to the first point + * @details substract each gesture feature by the first point of the gesture + * @param boolean to activate or deactivate translation + */ + void translate(bool translateFlag); + + /** + * Segment gestures within a continuous gesture stream + * @details if segmentation is true, the method will segment a continuous gesture into a sequence + * of gestures. In other words no need to call the method startGesture(), it is done automatically + * @param segmentationFlag boolean to activate or deactivate segmentation + */ + void segmentation(bool segmentationFlag); + +#pragma mark - [ Accessors ] +#pragma mark > Parameters + /** + * Set tolerance between observation and estimation + * @details tolerance depends on the range of the data + * typially tolerance = (data range)/3.0; + * @param tolerance value + */ + void setTolerance(float tolerance); + + /** + * Get the obervation tolerance value + * @details see setTolerance(float tolerance) + * @return the current toleranc value + */ + float getTolerance(); + + void setDistribution(float _distribution); + + /** + * Set number of particles used in estimation + * @details default valye is 1000, note that the computational + * cost directly depends on the number of particles + * @param new number of particles + */ + void setNumberOfParticles(int numberOfParticles); + + /** + * Get the current number of particles + * @return the current number of particles + */ + int getNumberOfParticles(); + + /** + * Number of prediciton steps + * @details it is possible to leave GVF to perform few steps of prediction + * ahead which can be useful to estimate more fastly the variations. Default value is 1 + * which means no prediction ahead + * @param the number of prediction steps + */ + void setPredictionSteps(int predictionSteps); + + /** + * Get the current number of prediction steps + * @return current number of prediciton steps + */ + int getPredictionSteps(); + + /** + * Set resampling threshold + * @details resampling threshold is the minimum number of active particles + * before resampling all the particles by the estimated posterior distribution. + * in other words, it re-targets particles around the best current estimates + * @param the minimum number of particles (default is (number of particles)/2) + */ + void setResamplingThreshold(int resamplingThreshold); + + /** + * Get the current resampling threshold + * @return resampling threshold + */ + int getResamplingThreshold(); + +#pragma mark > Dynamics + /** + * Change variance of adaptation in dynamics + * @details if dynamics adaptation variance is high the method will adapt faster to + * fast changes in dynamics. Dynamics is 2-dimensional: the first dimension is the speed + * The second dimension is the acceleration. + * + * Typically the variance is the average amount the speed or acceleration can change from + * one sample to another. As an example, if the relative estimated speed can change from 1.1 to 1.2 + * from one sample to another, the variance should allow a change of 0.1 in speed. So the variance + * should be set to 0.1*0.1 = 0.01 + * + * @param dynVariance dynamics variance value + * @param dim optional dimension of the dynamics for which the change of variance is applied (default value is 1) + */ + void setDynamicsVariance(float dynVariance, int dim = -1); + + /** + * Change variance of adaptation in dynamics + * @details See setDynamicsVariance(float dynVariance, int dim) for more details + * @param dynVariance vector of dynamics variances, each vector index is the variance to be applied to + * each dynamics dimension (consequently the vector should be 2-dimensional). + */ + void setDynamicsVariance(vector<float> dynVariance); + + /** + * Get dynamics variances + * @return the vector of variances (the returned vector is 2-dimensional) + */ + vector<float> getDynamicsVariance(); + +#pragma mark > Scalings + /** + * Change variance of adaptation in scalings + * @details if scalings adaptation variance is high the method will adapt faster to + * fast changes in relative sizes. There is one scaling variance for each dimension + * of the input gesture. If the gesture is 2-dimensional, the scalings variances will + * also be 2-dimensional. + * + * Typically the variance is the average amount the size can change from + * one sample to another. As an example, if the relative estimated size changes from 1.1 to 1.15 + * from one sample to another, the variance should allow a change of 0.05 in size. So the variance + * should be set to 0.05*0.05 = 0.0025 + * + * @param scalings variance value + * @param dimension of the scalings for which the change of variance is applied + */ + void setScalingsVariance(float scaleVariance, int dim = -1); + + /** + * Change variance of adaptation in dynamics + * @details See setScalingsVariance(float scaleVariance, int dim) for more details + * @param vector of scalings variances, each vector index is the variance to be applied to + * each scaling dimension. + * @param vector of variances (should be the size of the template gestures dimension) + */ + void setScalingsVariance(vector<float> scaleVariance); + + /** + * Get scalings variances + * @return the vector of variances + */ + vector<float> getScalingsVariance(); + +#pragma mark > Rotations + /** + * Change variance of adaptation in orientation + * @details if rotation adaptation variance is high the method will adapt faster to + * fast changes in relative orientation. If the gesture is 2-dimensional, there is + * one variance value since the rotation can be defined by only one angle of rotation. If + * the gesture is 3-dimensional, there are 3 variance values since the rotation in 3-d is + * defined by 3 rotation angles. For any other dimension, the rotation is not defined. + * + * The variance is the average amount the orientation can change from one sample to another. + * As an example, if the relative orientation in rad changes from 0.1 to 0.2 from one observation + * to another, the variance should allow a change of 0.1 in rotation angle. So the variance + * should be set to 0.1*0.1 = 0.01 + * + * @param rotationsVariance rotation variance value + * @param dim optional dimension of the rotation for which the change of variance is applied + */ + void setRotationsVariance(float rotationsVariance, int dim = -1); + + /** + * Change variance of adaptation in orientation + * @details See setRotationsVariance(float rotationsVariance, int dim) for more details + * @param vector of rotation variances, each vector index is the variance to be applied to + * each rotation angle (1 or 3) + * @param vector of variances (should be 1 if the the template gestures are 2-dim or 3 if + * they are 3-dim) + */ + void setRotationsVariance(vector<float> rotationsVariance); + + /** + * Get rotation variances + * @return the vector of variances + */ + vector<float> getRotationsVariance(); + + +#pragma mark > Others + + /** + * Get particle values + * @return vector of list of estimated particles + */ + const vector<vector<float> > & getParticlesPositions(); + + /** + * Set the interval on which the dynamics values should be spread at the beginning (before adaptation) + * @details this interval can be used to concentrate the potential dynamics value on a narrow interval, + * typically around 1 (the default value), for instance between -0.05 and 0.05, or to allow at the very + * beginning, high changes in dynamics by spreading, for instance between 0.0 and 2.0 + * @param min lower value of the inital values for dynamics + * @param max higher value of the inital values for dynamics + * @param dim the dimension on which the change of initial interval should be applied (optional) + */ + void setSpreadDynamics(float min, float max, int dim = -1); + + /** + * Set the interval on which the scalings values should be spread at the beginning (before adaptation) + * @details this interval can be used to concentrate the potential scalings value on a narrow interval, + * typically around 1.0 (the default value), for instance between 0.95 and 1.05, or to allow at the very + * beginning high changes in dynamics by spreading, for instance, between 0.0 and 2.0 + * @param min lower value of the inital values for scalings + * @param max higher value of the inital values for scalings + * @param dim the dimension on which the change of initial interval should be applied (optional) + */ + void setSpreadScalings(float min, float max, int dim = -1); + + /** + * Set the interval on which the angle of rotation values should be spread at the beginning (before adaptation) + * @details this interval can be used to concentrate the potential angle values on a narrow interval, + * typically around 0.0 (the default value), for instance between -0.05 and 0.05, or to allow at the very + * beginning, high changes in orientation by spreading, for instance, between -0.5 and 0.5 + * @param min lower value of the inital values for angle of rotation + * @param max higher value of the inital values for angle of rotation + * @param dim the dimension on which the change of initial interval should be applied (optional) + */ + void setSpreadRotations(float min, float max, int dim = -1); + +#pragma mark - Import/Export templates + /** + * Export template data in a filename + * @param filename file name as a string + */ + void saveTemplates(string filename); + + /** + * Import template data in a filename + * @details needs to respect a given format provided by saveTemplates() + * @param file name as a string + */ + void loadTemplates(string filename); + +protected: + + GVFConfig config; // Structure storing the configuration of GVF (in GVFUtils.h) + GVFParameters parameters; // Structure storing the parameters of GVF (in GVFUtils.h) + GVFOutcomes outcomes; // Structure storing the outputs of GVF (in GVFUtils.h) + GVFState state; // State (defined above) + GVFGesture theGesture; // GVFGesture object to handle incoming data in learning and following modes + + vector<GVFGesture> gestureTemplates; // vector storing the gesture templates recorded when using the methods addObservation(vector<float> data) or addGestureTemplate(GVFGesture & gestureTemplate) + + vector<float> dimWeights; // TOOD: to be put in parameters? + vector<float> maxRange; + vector<float> minRange; + int dynamicsDim; // dynamics state dimension + int scalingsDim; // scalings state dimension + int rotationsDim; // rotation state dimension + float globalNormalizationFactor; // flagged if normalization + int mostProbableIndex; // cached most probable index + int learningGesture; + + vector<int> classes; // gesture index for each particle [ns x 1] + vector<float > alignment; // alignment index (between 0 and 1) [ns x 1] + vector<vector<float> > dynamics; // dynamics estimation [ns x 2] + vector<vector<float> > scalings; // scalings estimation [ns x D] + vector<vector<float> > rotations; // rotations estimation [ns x A] + vector<float> weights; // weight of each particle [ns x 1] + vector<float> prior; // prior of each particle [ns x 1] + vector<float> posterior; // poserior of each particle [ns x 1] + vector<float> likelihood; // likelihood of each particle [ns x 1] + + // estimations + vector<float> estimatedGesture; // .. + vector<float> estimatedAlignment; // .. + vector<vector<float> > estimatedDynamics; // .. + vector<vector<float> > estimatedScalings; // .. + vector<vector<float> > estimatedRotations; // .. + vector<float> estimatedProbabilities; // .. + vector<float> estimatedLikelihoods; // .. + vector<float> absoluteLikelihoods; // .. + + bool tolerancesetmanually; + + + vector<vector<float> > offsets; // translation offset + + vector<int> activeGestures; + + vector<float> gestureProbabilities; + vector< vector<float> > particles; + +private: + + // random number generator + std::random_device rd; + std::mt19937 normgen; + std::normal_distribution<float> *rndnorm; + std::default_random_engine unifgen; + std::uniform_real_distribution<float> *rndunif; + +#pragma mark - Private methods for model mechanics + void initPrior(); + void initNoiseParameters(); + void updateLikelihood(vector<float> obs, int n); + void updatePrior(int n); + void updatePosterior(int n); + void resampleAccordingToWeights(vector<float> obs); + void estimates(); // update estimated outcome + void train(); + + +}; + + +#endif \ No newline at end of file diff --git a/GVF/GVFGesture.h b/GVF/GVFGesture.h new file mode 100644 index 0000000..d556fb0 --- /dev/null +++ b/GVF/GVFGesture.h @@ -0,0 +1,240 @@ +// +// GVFGesture.h +// gvf +// +// Created by Baptiste Caramiaux on 22/01/16. +// +// + +#ifndef GVFGesture_h +#define GVFGesture_h + +#ifndef MAX +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) +#endif + +#ifndef MIN +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) +#endif + + +class GVFGesture +{ +public: + + GVFGesture() + { + inputDimensions = 2; + setAutoAdjustRanges(true); + templatesRaw = vector<vector<vector<float > > >(); + templatesNormal = vector<vector<vector<float > > >(); + clear(); + } + + GVFGesture(int inputDimension){ + inputDimensions = inputDimension; + setAutoAdjustRanges(true); + templatesRaw = vector<vector<vector<float > > >(); + templatesNormal = vector<vector<vector<float > > >(); + clear(); + } + + ~GVFGesture(){ + clear(); + } + + void setNumberDimensions(int dimensions){ + assert(dimensions > 0); + inputDimensions = dimensions; + } + + void setAutoAdjustRanges(bool b){ + // if(b) bIsRangeMinSet = bIsRangeMaxSet = false; + bAutoAdjustNormalRange = b; + } + + void setMax(float x, float y){ + assert(inputDimensions == 2); + vector<float> r(2); + r[0] = x; r[1] = y; + setMaxRange(r); + } + + void setMin(float x, float y){ + assert(inputDimensions == 2); + vector<float> r(2); + r[0] = x; r[1] = y; + setMinRange(r); + } + + void setMax(float x, float y, float z){ + assert(inputDimensions == 3); + vector<float> r(3); + r[0] = x; r[1] = y; r[2] = z; + setMaxRange(r); + } + + void setMin(float x, float y, float z){ + assert(inputDimensions == 3); + vector<float> r(3); + r[0] = x; r[1] = y; r[2] = z; + setMinRange(r); + } + + void setMaxRange(vector<float> observationRangeMax){ + this->observationRangeMax = observationRangeMax; + // bIsRangeMaxSet = true; + normalise(); + } + + void setMinRange(vector<float> observationRangeMin){ + this->observationRangeMin = observationRangeMin; + // bIsRangeMinSet = true; + normalise(); + } + + vector<float>& getMaxRange(){ + return observationRangeMax; + } + + vector<float>& getMinRange(){ + return observationRangeMin; + } + + void autoAdjustMinMax(vector<float> & observation){ + if(observationRangeMax.size() < inputDimensions){ + observationRangeMax.assign(inputDimensions, -INFINITY); + observationRangeMin.assign(inputDimensions, INFINITY); + } + for(int i = 0; i < inputDimensions; i++){ + observationRangeMax[i] = MAX(observationRangeMax[i], observation[i]); + observationRangeMin[i] = MIN(observationRangeMin[i], observation[i]); + } + } + + void addObservation(vector<float> observation, int templateIndex = 0){ + if (observation.size() != inputDimensions) + inputDimensions = observation.size(); + + // check we have a valid templateIndex and correct number of input dimensions + assert(templateIndex <= templatesRaw.size()); + assert(observation.size() == inputDimensions); + + // if the template index is same as the number of temlates make a new template + if(templateIndex == templatesRaw.size()){ // make a new template + + // reserve space in raw and normal template storage + templatesRaw.resize(templatesRaw.size() + 1); + templatesNormal.resize(templatesNormal.size() + 1); + + } + + if(templatesRaw[templateIndex].size() == 0) + { + templateInitialObservation = observation; + templateInitialNormal = observation; + } + + for(int j = 0; j < observation.size(); j++) + observation[j] = observation[j] - templateInitialObservation[j]; + + // store the raw observation + templatesRaw[templateIndex].push_back(observation); + + autoAdjustMinMax(observation); + + normalise(); + } + + + + void normalise() + { + templatesNormal.resize(templatesRaw.size()); + for(int t = 0; t < templatesRaw.size(); t++) + { + templatesNormal[t].resize(templatesRaw[t].size()); + for(int o = 0; o < templatesRaw[t].size(); o++) + { + templatesNormal[t][o].resize(inputDimensions); + for(int d = 0; d < inputDimensions; d++) + { + templatesNormal[t][o][d] = templatesRaw[t][o][d] / (observationRangeMax[d] - observationRangeMin[d]); + templateInitialNormal[d] = templateInitialObservation[d] / (observationRangeMax[d] - observationRangeMin[d]); + } + } + } + } + + void setTemplate(vector< vector<float> > & observations, int templateIndex = 0){ + for(int i = 0; i < observations.size(); i++){ + addObservation(observations[i], templateIndex); + } + } + + vector< vector<float> > & getTemplate(int templateIndex = 0){ + assert(templateIndex < templatesRaw.size()); + return templatesRaw[templateIndex]; + } + + int getNumberOfTemplates(){ + return templatesRaw.size(); + } + + int getNumberDimensions(){ + return inputDimensions; + } + + int getTemplateLength(int templateIndex = 0){ + return templatesRaw[templateIndex].size(); + } + + int getTemplateDimension(int templateIndex = 0){ + return templatesRaw[templateIndex][0].size(); + } + + vector<float>& getLastObservation(int templateIndex = 0){ + return templatesRaw[templateIndex][templatesRaw[templateIndex].size() - 1]; + } + + vector< vector< vector<float> > >& getTemplates(){ + return templatesRaw; + } + + vector<float>& getInitialObservation(){ + return templateInitialObservation; + } + + void deleteTemplate(int templateIndex = 0) + { + assert(templateIndex < templatesRaw.size()); + templatesRaw[templateIndex].clear(); + templatesNormal[templateIndex].clear(); + } + + void clear() + { + templatesRaw.clear(); + templatesNormal.clear(); + observationRangeMax.assign(inputDimensions, -INFINITY); + observationRangeMin.assign(inputDimensions, INFINITY); + } + +private: + + int inputDimensions; + bool bAutoAdjustNormalRange; + + vector<float> observationRangeMax; + vector<float> observationRangeMin; + + vector<float> templateInitialObservation; + vector<float> templateInitialNormal; + + vector< vector< vector<float> > > templatesRaw; + vector< vector< vector<float> > > templatesNormal; + + vector<vector<float> > gestureDataFromFile; +}; + +#endif /* GVFGesture_h */ diff --git a/GVF/GVFUtils.h b/GVF/GVFUtils.h new file mode 100755 index 0000000..125676c --- /dev/null +++ b/GVF/GVFUtils.h @@ -0,0 +1,309 @@ +// +// GVFTypesAndUtils.h +// +// +// + +#ifndef __H_GVFTYPES +#define __H_GVFTYPES + +#include <map> +#include <vector> +#include <iostream> +#include <random> +#include <iostream> +#include <math.h> +#include <assert.h> + +using namespace std; + +/** + * Configuration structure + */ +typedef struct +{ + int inputDimensions; /**< input dimesnion */ + bool translate; /**< translate flag */ + bool segmentation; /**< segmentation flag */ +} GVFConfig; + +/** + * Parameters structure + */ +typedef struct +{ + float tolerance; /**< input dimesnion */ + float distribution; + int numberParticles; + int resamplingThreshold; + float alignmentVariance; + float speedVariance; + vector<float> scaleVariance; + vector<float> dynamicsVariance; + vector<float> scalingsVariance; + vector<float> rotationsVariance; + // spreadings + float alignmentSpreadingCenter; + float alignmentSpreadingRange; + float dynamicsSpreadingCenter; + float dynamicsSpreadingRange; + float scalingsSpreadingCenter; + float scalingsSpreadingRange; + float rotationsSpreadingCenter; + float rotationsSpreadingRange; + + int predictionSteps; + vector<float> dimWeights; +} GVFParameters; + +// Outcomes structure +typedef struct +{ + int likeliestGesture; + vector<float> likelihoods; + vector<float> alignments; + vector<vector<float> > dynamics; + vector<vector<float> > scalings; + vector<vector<float> > rotations; +} GVFOutcomes; + + +//-------------------------------------------------------------- +// init matrix by allocating memory +template <typename T> +inline void initMat(vector< vector<T> > & M, int rows, int cols){ + M.resize(rows); + for (int n=0; n<rows; n++){ + M[n].resize(cols); + } +} + +//-------------------------------------------------------------- +// init matrix and copy values from another matrix +template <typename T> +inline void setMat(vector< vector<T> > & C, vector< vector<float> > & M){ + int rows = M.size(); + int cols = M[0].size(); + //C.resize(rows); + C = vector<vector<T> >(rows); + for (int n=0; n<rows; n++){ + //C[n].resize(cols); + C[n] = vector<T>(cols); + for (int m=0;m<cols;m++){ + C[n][m] = M[n][m]; + } + } +} + +//-------------------------------------------------------------- +// init matrix by allocating memory and fill with T value +template <typename T> +inline void setMat(vector< vector<T> > & M, T value, int rows, int cols){ + M.resize(rows); + for (int n=0; n<rows; n++){ + M[n].resize(cols); + for (int m=0; m<cols; m++){ + M[n][m] = value; + } + } +} + +//-------------------------------------------------------------- +// set matrix filled with T value +template <typename T> +inline void setMat(vector< vector<T> > & M, T value){ + for (int n=0; n<M.size(); n++){ + for (int m=0; m<M[n].size(); m++){ + M[n][m] = value; + } + } +} + +//-------------------------------------------------------------- +template <typename T> +inline void printMat(vector< vector<T> > & M){ + for (int k=0; k<M.size(); k++){ + cout << k << ": "; + for (int l=0; l<M[0].size(); l++){ + cout << M[k][l] << " "; + } + cout << endl; + } + cout << endl; +} + +//-------------------------------------------------------------- +template <typename T> +inline void printVec(vector<T> & V){ + for (int k=0; k<V.size(); k++){ + cout << k << ": " << V[k] << (k == V.size() - 1 ? "" : " ,"); + } + cout << endl; +} + +//-------------------------------------------------------------- +template <typename T> +inline void initVec(vector<T> & V, int rows){ + V.resize(rows); +} + +//-------------------------------------------------------------- +template <typename T> +inline void setVec(vector<T> & C, vector<int> &V){ + int rows = V.size(); + C = vector<T>(rows); + //C.resize(rows); + for (int n=0; n<rows; n++){ + C[n] = V[n]; + } +} + +//-------------------------------------------------------------- +template <typename T> +inline void setVec(vector<T> & C, vector<float> & V){ + int rows = V.size(); + C.resize(rows); + for (int n=0; n<rows; n++){ + C[n] = V[n]; + } +} + +//-------------------------------------------------------------- +template <typename T> +inline void setVec(vector<T> & V, T value){ + for (int n=0; n<V.size(); n++){ + V[n] = value; + } +} + +//-------------------------------------------------------------- +template <typename T> +inline void setVec(vector<T> & V, T value, int rows){ + V.resize(rows); + setVec(V, value); +} + +//-------------------------------------------------------------- +template <typename T> +inline vector< vector<T> > dotMat(vector< vector<T> > & M1, vector< vector<T> > & M2){ + // TODO(Baptiste) +} + +//-------------------------------------------------------------- +template <typename T> +inline vector< vector<T> > multiplyMatf(vector< vector<T> > & M1, T v){ + vector< vector<T> > multiply; + initMat(multiply, M1.size(), M1[0].size()); + for (int i=0; i<M1.size(); i++){ + for (int j=0; j<M1[i].size(); j++){ + multiply[i][j] = M1[i][j] * v; + } + } + return multiply; +} + +//-------------------------------------------------------------- +template <typename T> +inline vector< vector<T> > multiplyMatf(vector< vector<T> > & M1, vector< vector<T> > & M2){ + assert(M1[0].size() == M2.size()); // columns in M1 == rows in M2 + vector< vector<T> > multiply; + initMat(multiply, M1.size(), M2[0].size()); // rows in M1 x cols in M2 + for (int i=0; i<M1.size(); i++){ + for (int j=0; j<M2[i].size(); j++){ + multiply[i][j] = 0.0f; + for(int k=0; k<M1[0].size(); k++){ + multiply[i][j] += M1[i][k] * M2[k][j]; + } + + } + } + return multiply; +} + +//-------------------------------------------------------------- +template <typename T> +inline vector<T> multiplyMat(vector< vector<T> > & M1, vector< T> & Vect){ + assert(Vect.size() == M1[0].size()); // columns in M1 == rows in Vect + vector<T> multiply; + initVec(multiply, Vect.size()); + for (int i=0; i<M1.size(); i++){ + multiply[i] = 0.0f; + for (int j=0; j<M1[i].size(); j++){ + multiply[i] += M1[i][j] * Vect[j]; + } + } + return multiply; +} + +//-------------------------------------------------------------- +template <typename T> +inline float getMeanVec(vector<T>& V){ + float tSum = 0.0f; + for (int n=0; n<V.size(); n++){ + tSum += V[n]; + } + return tSum / (float)V.size(); +} + +template <typename T> +inline vector<vector<float> > getRotationMatrix3d(T phi, T theta, T psi) +{ + vector< vector<float> > M; + initMat(M,3,3); + + M[0][0] = cos(theta)*cos(psi); + M[0][1] = -cos(phi)*sin(psi)+sin(phi)*sin(theta)*cos(psi); + M[0][2] = sin(phi)*sin(psi)+cos(phi)*sin(theta)*cos(psi); + + M[1][0] = cos(theta)*sin(psi); + M[1][1] = cos(phi)*cos(psi)+sin(phi)*sin(theta)*sin(psi); + M[1][2] = -sin(phi)*cos(psi)+cos(phi)*sin(theta)*sin(psi); + + M[2][0] = -sin(theta); + M[2][1] = sin(phi)*cos(theta); + M[2][2] = cos(phi)*cos(theta); + + return M; +} + +template <typename T> +float distance_weightedEuclidean(vector<T> x, vector<T> y, vector<T> w) +{ + int count = x.size(); + if (count <= 0) return 0; + float dist = 0.0; + for(int k = 0; k < count; k++) dist += w[k] * pow((x[k] - y[k]), 2); + return dist; +} + +////-------------------------------------------------------------- +//vector<vector<float> > getRotationMatrix3d(float phi, float theta, float psi) +//{ +// vector< vector<float> > M; +// initMat(M,3,3); +// +// M[0][0] = cos(theta)*cos(psi); +// M[0][1] = -cos(phi)*sin(psi)+sin(phi)*sin(theta)*cos(psi); +// M[0][2] = sin(phi)*sin(psi)+cos(phi)*sin(theta)*cos(psi); +// +// M[1][0] = cos(theta)*sin(psi); +// M[1][1] = cos(phi)*cos(psi)+sin(phi)*sin(theta)*sin(psi); +// M[1][2] = -sin(phi)*cos(psi)+cos(phi)*sin(theta)*sin(psi); +// +// M[2][0] = -sin(theta); +// M[2][1] = sin(phi)*cos(theta); +// M[2][2] = cos(phi)*cos(theta); +// +// return M; +//} + +//float distance_weightedEuclidean(vector<float> x, vector<float> y, vector<float> w) +//{ +// int count = x.size(); +// if (count <= 0) return 0; +// float dist = 0.0; +// for(int k = 0; k < count; k++) dist += w[k] * pow((x[k] - y[k]), 2); +// return dist; +//} + +#endif diff --git a/dependencies/GVF/GVF.cpp b/dependencies/GVF/GVF.cpp new file mode 100755 index 0000000..36ef7d5 --- /dev/null +++ b/dependencies/GVF/GVF.cpp @@ -0,0 +1,1375 @@ +/** + * Gesture Variation Follower class allows for early gesture recognition and variation tracking + * + * @details Original algorithm designed and implemented in 2011 at Ircam Centre Pompidou + * by Baptiste Caramiaux and Nicola Montecchio. The library has been created and is maintained by Baptiste Caramiaux + * + * Copyright (C) 2015 Baptiste Caramiaux, Nicola Montecchio + * STMS lab Ircam-CRNS-UPMC, University of Padova, Goldsmiths College University of London + * + * The library is under the GNU Lesser General Public License (LGPL v3) + */ + +#include "GVF.h" +#include <string.h> +#include <stdio.h> +#include <iostream> +#include <fstream> +#include <sstream> +#include <memory> +#include <algorithm> +#include <numeric> + +//debug max +//#include "ext.h" + + +using namespace std; + +//-------------------------------------------------------------- +GVF::GVF() +{ + config.inputDimensions = 2; + config.translate = true; + config.segmentation = false; + + parameters.numberParticles = 1000; + parameters.tolerance = 0.2f; + parameters.resamplingThreshold = 250; + parameters.distribution = 0.0f; + parameters.alignmentVariance = sqrt(0.000001f); + parameters.dynamicsVariance = vector<float>(1,sqrt(0.001f)); + parameters.scalingsVariance = vector<float>(1,sqrt(0.00001f)); + parameters.rotationsVariance = vector<float>(1,sqrt(0.0f)); + parameters.predictionSteps = 1; + parameters.dimWeights = vector<float>(1,sqrt(1.0f)); + parameters.alignmentSpreadingCenter = 0.0; + parameters.alignmentSpreadingRange = 0.2; + parameters.dynamicsSpreadingCenter = 1.0; + parameters.dynamicsSpreadingRange = 0.3; + parameters.scalingsSpreadingCenter = 1.0; + parameters.scalingsSpreadingRange = 0.3; + parameters.rotationsSpreadingCenter = 0.0; + parameters.rotationsSpreadingRange = 0.5; + + tolerancesetmanually = false; + learningGesture = -1; + + normgen = std::mt19937(rd()); + rndnorm = new std::normal_distribution<float>(0.0,1.0); + unifgen = std::default_random_engine(rd()); + rndunif = new std::uniform_real_distribution<float>(0.0,1.0); + +} + +////-------------------------------------------------------------- +//GVF::GVF(GVFConfig _config){ +// setup(_config); +//} +// +////-------------------------------------------------------------- +//GVF::GVF(GVFConfig _config, GVFParameters _parameters){ +// setup(_config, _parameters); +//} +// +////-------------------------------------------------------------- +//void GVF::setup(){ +// +// // use defualt parameters +// GVFConfig defaultConfig; +// +// defaultConfig.inputDimensions = 2; +// defaultConfig.translate = true; +// defaultConfig.segmentation = false; +// +// setup(defaultConfig); +//} +// +////-------------------------------------------------------------- +//void GVF::setup(GVFConfig _config){ +// +// clear(); // just in case +// +// learningGesture = -1; +// +// // Set configuration: +// config = _config; +// +// // default parameters +// GVFParameters defaultParameters; +// defaultParameters.numberParticles = 1000; +// defaultParameters.tolerance = 0.2f; +// defaultParameters.resamplingThreshold = 250; +// defaultParameters.distribution = 0.0f; +// defaultParameters.alignmentVariance = sqrt(0.000001f); +// defaultParameters.dynamicsVariance = vector<float>(1,sqrt(0.001f)); +// defaultParameters.scalingsVariance = vector<float>(1,sqrt(0.00001f)); +// defaultParameters.rotationsVariance = vector<float>(1,sqrt(0.0f)); +// defaultParameters.predictionSteps = 1; +// defaultParameters.dimWeights = vector<float>(1,sqrt(1.0f)); +// +// // default spreading +// defaultParameters.alignmentSpreadingCenter = 0.0; +// defaultParameters.alignmentSpreadingRange = 0.2; +// +// defaultParameters.dynamicsSpreadingCenter = 1.0; +// defaultParameters.dynamicsSpreadingRange = 0.3; +// +// defaultParameters.scalingsSpreadingCenter = 1.0; +// defaultParameters.scalingsSpreadingRange = 0.3; +// +// defaultParameters.rotationsSpreadingCenter = 0.0; +// defaultParameters.rotationsSpreadingRange = 0.0; +// +// tolerancesetmanually = false; +// +// setup(_config, defaultParameters); +// +//} +// +////-------------------------------------------------------------- +//void GVF::setup(GVFConfig _config, GVFParameters _parameters) +//{ +// clear(); // just in case +// // Set configuration and parameters +// config = _config; +// parameters = _parameters; +// // Init random generators +// normgen = std::mt19937(rd()); +// rndnorm = new std::normal_distribution<float>(0.0,1.0); +// unifgen = std::default_random_engine(rd()); +// rndunif = new std::uniform_real_distribution<float>(0.0,1.0); +//} + +//-------------------------------------------------------------- +GVF::~GVF() +{ + if (rndnorm != NULL) + delete (rndnorm); + clear(); // not really necessary but it's polite ;) +} + +//-------------------------------------------------------------- +void GVF::clear() +{ + state = STATE_CLEAR; + gestureTemplates.clear(); + mostProbableIndex = -1; +} + +//-------------------------------------------------------------- +void GVF::startGesture() +{ + if (state==STATE_FOLLOWING) + { + restart(); + } + else if (state==STATE_LEARNING) + { + if (theGesture.getNumberOfTemplates()>0) + { + if (theGesture.getTemplateLength()>0) + addGestureTemplate(theGesture); + } + theGesture.clear(); + } +} + +//-------------------------------------------------------------- +void GVF::addObservation(vector<float> data) +{ + theGesture.addObservation(data); +} + +//-------------------------------------------------------------- +void GVF::addGestureTemplate(GVFGesture & gestureTemplate) +{ + + // if (getState() != GVF::STATE_LEARNING) + // setState(GVF::STATE_LEARNING); + + int inputDimension = gestureTemplate.getNumberDimensions(); + config.inputDimensions = inputDimension; + + gestureTemplates.push_back(gestureTemplate); + activeGestures.push_back(gestureTemplates.size()); + + if(minRange.size() == 0){ + minRange.resize(inputDimension); + maxRange.resize(inputDimension); + } + + for(int j = 0; j < inputDimension; j++){ + minRange[j] = INFINITY; + maxRange[j] = -INFINITY; + } + + // compute min/max from the data + for(int i = 0; i < gestureTemplates.size(); i++){ + GVFGesture& tGestureTemplate = gestureTemplates[i]; + vector<float>& tMinRange = tGestureTemplate.getMinRange(); + vector<float>& tMaxRange = tGestureTemplate.getMaxRange(); + for(int j = 0; j < inputDimension; j++){ + if(tMinRange[j] < minRange[j]) minRange[j] = tMinRange[j]; + if(tMaxRange[j] > maxRange[j]) maxRange[j] = tMaxRange[j]; + } + } + + for(int i = 0; i < gestureTemplates.size(); i++){ + GVFGesture& tGestureTemplate = gestureTemplates[i]; + tGestureTemplate.setMinRange(minRange); + tGestureTemplate.setMaxRange(maxRange); + } + train(); + +} + +//-------------------------------------------------------------- +void GVF::replaceGestureTemplate(GVFGesture & gestureTemplate, int index) +{ + if(gestureTemplate.getNumberDimensions()!=config.inputDimensions) + return; + if(minRange.size() == 0) + { + minRange.resize(config.inputDimensions); + maxRange.resize(config.inputDimensions); + } + for(int j = 0; j < config.inputDimensions; j++) + { + minRange[j] = INFINITY; + maxRange[j] = -INFINITY; + } + if (index<=gestureTemplates.size()) + gestureTemplates[index-1]=gestureTemplate; + for(int i = 0; i < gestureTemplates.size(); i++) + { + GVFGesture& tGestureTemplate = gestureTemplates[i]; + vector<float>& tMinRange = tGestureTemplate.getMinRange(); + vector<float>& tMaxRange = tGestureTemplate.getMaxRange(); + for(int j = 0; j < config.inputDimensions; j++){ + if(tMinRange[j] < minRange[j]) minRange[j] = tMinRange[j]; + if(tMaxRange[j] > maxRange[j]) maxRange[j] = tMaxRange[j]; + } + } + for(int i = 0; i < gestureTemplates.size(); i++) + { + GVFGesture& tGestureTemplate = gestureTemplates[i]; + tGestureTemplate.setMinRange(minRange); + tGestureTemplate.setMaxRange(maxRange); + } +} + +////-------------------------------------------------------------- +//vector<float>& GVF::getGestureTemplateSample(int gestureIndex, float cursor) +//{ +// int frameindex = min((int)(gestureTemplates[gestureIndex].getTemplateLength() - 1), +// (int)(floor(cursor * gestureTemplates[gestureIndex].getTemplateLength() ) ) ); +// return gestureTemplates[gestureIndex].getTemplate()[frameindex]; +//} + +//-------------------------------------------------------------- +GVFGesture & GVF::getGestureTemplate(int index){ + assert(index < gestureTemplates.size()); + return gestureTemplates[index]; +} + +//-------------------------------------------------------------- +vector<GVFGesture> & GVF::getAllGestureTemplates(){ + return gestureTemplates; +} + +//-------------------------------------------------------------- +int GVF::getNumberOfGestureTemplates(){ + return (int)gestureTemplates.size(); +} + +//-------------------------------------------------------------- +void GVF::removeGestureTemplate(int index){ + assert(index < gestureTemplates.size()); + gestureTemplates.erase(gestureTemplates.begin() + index); +} + +//-------------------------------------------------------------- +void GVF::removeAllGestureTemplates(){ + gestureTemplates.clear(); +} + +//---------------------------------------------- +void GVF::train(){ + + if (gestureTemplates.size() > 0) + { + + // get the number of dimension in templates + config.inputDimensions = gestureTemplates[0].getTemplateDimension(); + + dynamicsDim = 2; // hard coded: just speed now + scalingsDim = config.inputDimensions; + + // manage orientation + if (config.inputDimensions==2) rotationsDim=1; + else if (config.inputDimensions==3) rotationsDim=3; + else rotationsDim=0; + + // Init state space + initVec(classes, parameters.numberParticles); // Vector of gesture class + initVec(alignment, parameters.numberParticles); // Vector of phase values (alignment) + initMat(dynamics, parameters.numberParticles, dynamicsDim); // Matric of dynamics + initMat(scalings, parameters.numberParticles, scalingsDim); // Matrix of scaling + if (rotationsDim!=0) initMat(rotations, parameters.numberParticles, rotationsDim); // Matrix of rotations + initMat(offsets, parameters.numberParticles, config.inputDimensions); + initVec(weights, parameters.numberParticles); // Weights + + initMat(particles, parameters.numberParticles, 3); + // std::cout << particles.size() << " " << parameters.numberParticles << std::endl; + + // bayesian elements + initVec(prior, parameters.numberParticles); + initVec(posterior, parameters.numberParticles); + initVec(likelihood, parameters.numberParticles); + + + initPrior(); // prior on init state values + initNoiseParameters(); // init noise parameters (transition and likelihood) + + + // weighted dimensions in case: default is not weighted + if (parameters.dimWeights.size()!=config.inputDimensions){ + parameters.dimWeights = vector<float> (config.inputDimensions); + for(int k = 0; k < config.inputDimensions; k++) parameters.dimWeights[k] = 1.0 / config.inputDimensions; + } + + // NORMALIZATION +// if (config.normalization) { // update the global normaliation factor +// globalNormalizationFactor = -1.0; +// // loop on previous gestures already learned +// // take the max of all the gesture learned ... +// for (int k=0; k<getNumberOfGestureTemplates() ; k++){ +// for(int j = 0; j < config.inputDimensions; j++){ +// float rangetmp = fabs(getGestureTemplate(k).getMaxRange()[j]-getGestureTemplate(k).getMinRange()[j]); +// if (rangetmp > globalNormalizationFactor) +// globalNormalizationFactor=rangetmp; +// } +// } +// } +// // only for logs +// if (config.logOn) { +// vecRef = vector<vector<float> > (parameters.numberParticles); +// vecObs = vector<float> (config.inputDimensions); +// stateNoiseDist = vector<float> (parameters.numberParticles); +// } + } +} + +//-------------------------------------------------------------- +//void GVF::initPrior() +//{ +// +// // PATICLE FILTERING +// for (int k = 0; k < parameters.numberParticles; k++) +// { +// initPrior(k); +// +// classes[k] = activeGestures[k % activeGestures.size()] - 1; +// } +// +//} + +//-------------------------------------------------------------- +void GVF::initPrior() //int pf_n) +{ + for (int pf_n = 0; pf_n < parameters.numberParticles; pf_n++) + { + // alignment + alignment[pf_n] = ((*rndunif)(unifgen) - 0.5) * parameters.alignmentSpreadingRange + parameters.alignmentSpreadingCenter; // spread phase + + + // dynamics + dynamics[pf_n][0] = ((*rndunif)(unifgen) - 0.5) * parameters.dynamicsSpreadingRange + parameters.dynamicsSpreadingCenter; // spread speed + if (dynamics[pf_n].size()>1) + { + dynamics[pf_n][1] = ((*rndunif)(unifgen) - 0.5) * parameters.dynamicsSpreadingRange; // spread accel + } + + // scalings + for(int l = 0; l < scalings[pf_n].size(); l++) { + scalings[pf_n][l] = ((*rndunif)(unifgen) - 0.5) * parameters.scalingsSpreadingRange + parameters.scalingsSpreadingCenter; // spread scalings + } + + // rotations + if (rotationsDim!=0) + for(int l = 0; l < rotations[pf_n].size(); l++) + rotations[pf_n][l] = ((*rndunif)(unifgen) - 0.5) * parameters.rotationsSpreadingRange + parameters.rotationsSpreadingCenter; // spread rotations + + if (config.translate) for(int l = 0; l < offsets[pf_n].size(); l++) offsets[pf_n][l] = 0.0; + + + prior[pf_n] = 1.0 / (float) parameters.numberParticles; + + // set the posterior to the prior at the initialization + posterior[pf_n] = prior[pf_n]; + + classes[pf_n] = activeGestures[pf_n % activeGestures.size()] - 1; + } + +} + +//-------------------------------------------------------------- +void GVF::initNoiseParameters() { + + // NOISE (ADDITIVE GAUSSIAN NOISE) + // --------------------------- + + if (parameters.dynamicsVariance.size() != dynamicsDim) + { + float variance = parameters.dynamicsVariance[0]; + parameters.dynamicsVariance.resize(dynamicsDim); + for (int k=0; k<dynamicsDim; k++) + parameters.dynamicsVariance[k] = variance; + } + + if (parameters.scalingsVariance.size() != scalingsDim) + { + float variance = parameters.scalingsVariance[0]; + parameters.scalingsVariance.resize(scalingsDim); + for (int k=0; k<scalingsDim; k++) + parameters.scalingsVariance[k] = variance; + } + + if (rotationsDim!=0) + { + if (parameters.rotationsVariance.size() != rotationsDim) + { + float variance = parameters.rotationsVariance[0]; + parameters.rotationsVariance.resize(rotationsDim); + for (int k=0; k<rotationsDim; k++) + parameters.rotationsVariance[k] = variance; + } + } + + // ADAPTATION OF THE TOLERANCE IF DEFAULT PARAMTERS + // --------------------------- + if (!tolerancesetmanually){ + float obsMeanRange = 0.0f; + for (int gt=0; gt<gestureTemplates.size(); gt++) { + for (int d=0; d<config.inputDimensions; d++) + obsMeanRange += (gestureTemplates[gt].getMaxRange()[d] - gestureTemplates[gt].getMinRange()[d]) + /config.inputDimensions; + } + obsMeanRange /= gestureTemplates.size(); + parameters.tolerance = obsMeanRange / 4.0f; // dividing by an heuristic factor [to be learned?] + } +} + +//-------------------------------------------------------------- +void GVF::setState(GVFState _state, vector<int> indexes) +{ + switch (_state) + { + case STATE_CLEAR: + clear(); + theGesture.clear(); + break; + + case STATE_LEARNING: + if ((state==STATE_LEARNING) && (theGesture.getNumberOfTemplates()>0)) + { + if (learningGesture==-1) + addGestureTemplate(theGesture); + else + { + replaceGestureTemplate(theGesture, learningGesture); + learningGesture=-1; + } + if (indexes.size()!=0) + learningGesture=indexes[0]; + } + state = _state; + theGesture.clear(); + break; + + case STATE_FOLLOWING: + if ((state==STATE_LEARNING) && (theGesture.getNumberOfTemplates()>0)) + { + if (learningGesture==-1) + addGestureTemplate(theGesture); + else + { + replaceGestureTemplate(theGesture, learningGesture); + learningGesture=-1; + } + } + if (gestureTemplates.size() > 0) + { + train(); + state = _state; + } + else + state = STATE_CLEAR; + theGesture.clear(); + break; + + default: + theGesture.clear(); + break; + } +} + +//-------------------------------------------------------------- +GVF::GVFState GVF::getState() +{ + return state; +} + +////-------------------------------------------------------------- +//int GVF::getDynamicsDimension(){ +// return dynamicsDim; +//} + +//-------------------------------------------------------------- +vector<int> GVF::getGestureClasses() +{ + return classes; +} + +////-------------------------------------------------------------- +//vector<float> GVF::getAlignment(){ +// return alignment; +//} +// +////-------------------------------------------------------------- +//vector<float> GVF::getEstimatedAlignment(){ +// return estimatedAlignment; +//} +// +////-------------------------------------------------------------- +//vector< vector<float> > GVF::getDynamics(){ +// return dynamics; +//} +// +////-------------------------------------------------------------- +//vector< vector<float> > GVF::getEstimatedDynamics(){ +// return estimatedDynamics; +//} +// +////-------------------------------------------------------------- +//vector< vector<float> > GVF::getScalings(){ +// return scalings; +//} +// +////-------------------------------------------------------------- +//vector< vector<float> > GVF::getEstimatedScalings(){ +// return estimatedScalings; +//} +// +////-------------------------------------------------------------- +//vector< vector<float> > GVF::getRotations(){ +// return rotations; +//} +// +////-------------------------------------------------------------- +//vector< vector<float> > GVF::getEstimatedRotations(){ +// return estimatedRotations; +//} + +////-------------------------------------------------------------- +//vector<float> GVF::getEstimatedProbabilities(){ +// return estimatedProbabilities; +//} +// +////-------------------------------------------------------------- +//vector<float> GVF::getEstimatedLikelihoods(){ +// return estimatedLikelihoods; +//} +// +////-------------------------------------------------------------- +//vector<float> GVF::getWeights(){ +// return weights; +//} +// +////-------------------------------------------------------------- +//vector<float> GVF::getPrior(){ +// return prior; +//} + +////-------------------------------------------------------------- +//vector<vector<float> > GVF::getVecRef() { +// return vecRef; +//} +// +////-------------------------------------------------------------- +//vector<float> GVF::getVecObs() { +// return vecObs; +//} +// +////-------------------------------------------------------------- +//vector<float> GVF::getStateNoiseDist(){ +// return stateNoiseDist; +//} + +////-------------------------------------------------------------- +//int GVF::getScalingsDim(){ +// return scalingsDim; +//} +// +////-------------------------------------------------------------- +//int GVF::getRotationsDim(){ +// return rotationsDim; +//} + +//-------------------------------------------------------------- +void GVF::restart() +{ + theGesture.clear(); + initPrior(); +} + +#pragma mark - PARTICLE FILTERING + +//-------------------------------------------------------------- +void GVF::updatePrior(int n) { + + // Update alignment / dynamics / scalings + float L = gestureTemplates[classes[n]].getTemplateLength(); + alignment[n] += (*rndnorm)(normgen) * parameters.alignmentVariance + dynamics[n][0]/L; // + dynamics[n][1]/(L*L); + + if (dynamics[n].size()>1){ + dynamics[n][0] += (*rndnorm)(normgen) * parameters.dynamicsVariance[0] + dynamics[n][1]/L; + dynamics[n][1] += (*rndnorm)(normgen) * parameters.dynamicsVariance[1]; + } + else { + dynamics[n][0] += (*rndnorm)(normgen) * parameters.dynamicsVariance[0]; + } + + // for(int l= 0; l < dynamics[n].size(); l++) dynamics[n][l] += (*rndnorm)(normgen) * parameters.dynamicsVariance[l]; + for(int l= 0; l < scalings[n].size(); l++) scalings[n][l] += (*rndnorm)(normgen) * parameters.scalingsVariance[l]; + if (rotationsDim!=0) for(int l= 0; l < rotations[n].size(); l++) rotations[n][l] += (*rndnorm)(normgen) * parameters.rotationsVariance[l]; + + // update prior (bayesian incremental inference) + prior[n] = posterior[n]; +} + +//-------------------------------------------------------------- +void GVF::updateLikelihood(vector<float> obs, int n) +{ + +// if (config.normalization) for (int kk=0; kk<vobs.size(); kk++) vobs[kk] = vobs[kk] / globalNormalizationFactor; + + if(alignment[n] < 0.0) + { + alignment[n] = fabs(alignment[n]); // re-spread at the beginning +// if (config.segmentation) +// classes[n] = n % getNumberOfGestureTemplates(); + } + else if(alignment[n] > 1.0) + { + if (config.segmentation) + { +// alignment[n] = fabs(1.0-alignment[n]); // re-spread at the beginning + alignment[n] = fabs((*rndunif)(unifgen) * 0.5); // + classes[n] = n % getNumberOfGestureTemplates(); + offsets[n] = obs; + // dynamics + dynamics[n][0] = ((*rndunif)(unifgen) - 0.5) * parameters.dynamicsSpreadingRange + parameters.dynamicsSpreadingCenter; // spread speed + if (dynamics[n].size()>1) + dynamics[n][1] = ((*rndunif)(unifgen) - 0.5) * parameters.dynamicsSpreadingRange; + // scalings + for(int l = 0; l < scalings[n].size(); l++) + scalings[n][l] = ((*rndunif)(unifgen) - 0.5) * parameters.scalingsSpreadingRange + parameters.scalingsSpreadingCenter; // spread scalings + // rotations + if (rotationsDim!=0) + for(int l = 0; l < rotations[n].size(); l++) + rotations[n][l] = ((*rndunif)(unifgen) - 0.5) * parameters.rotationsSpreadingRange + parameters.rotationsSpreadingCenter; // spread rotations + // prior + prior[n] = 1/(float)parameters.numberParticles; + } + else{ + alignment[n] = fabs(2.0-alignment[n]); // re-spread at the end + } + } + + vector<float> vobs(config.inputDimensions); + setVec(vobs, obs); + + if (config.translate) + for (int j=0; j < config.inputDimensions; j++) + vobs[j] = vobs[j] - offsets[n][j]; + + + // take vref from template at the given alignment + int gestureIndex = classes[n]; + float cursor = alignment[n]; + int frameindex = min((int)(gestureTemplates[gestureIndex].getTemplateLength() - 1), + (int)(floor(cursor * gestureTemplates[gestureIndex].getTemplateLength() ) ) ); +// return gestureTemplates[gestureIndex].getTemplate()[frameindex]; + vector<float> vref = gestureTemplates[gestureIndex].getTemplate()[frameindex];; //getGestureTemplateSample(classes[n], alignment[n]); + + // Apply scaling coefficients + for (int k=0;k < config.inputDimensions; k++) + { +// if (config.normalization) vref[k] = vref[k] / globalNormalizationFactor; + vref[k] *= scalings[n][k]; + } + + // Apply rotation coefficients + if (config.inputDimensions==2) { + float tmp0=vref[0]; float tmp1=vref[1]; + vref[0] = cos(rotations[n][0])*tmp0 - sin(rotations[n][0])*tmp1; + vref[1] = sin(rotations[n][0])*tmp0 + cos(rotations[n][0])*tmp1; + } + else if (config.inputDimensions==3) { + // Rotate template sample according to the estimated angles of rotations (3d) + vector<vector< float> > RotMatrix = getRotationMatrix3d(rotations[n][0],rotations[n][1],rotations[n][2]); + vref = multiplyMat(RotMatrix, vref); + } + + // weighted euclidean distance + float dist = distance_weightedEuclidean(vref,vobs,parameters.dimWeights); + + if(parameters.distribution == 0.0f){ // Gaussian distribution + likelihood[n] = exp(- dist * 1 / (parameters.tolerance * parameters.tolerance)); + } + else { // Student's distribution + likelihood[n] = pow(dist/parameters.distribution + 1, -parameters.distribution/2 - 1); // dimension is 2 .. pay attention if editing] + } +// // if log on keep track on vref and vobs +// if (config.logOn){ +// vecRef.push_back(vref); +// vecObs = vobs; +// } +} + +//-------------------------------------------------------------- +void GVF::updatePosterior(int n) { + posterior[n] = prior[n] * likelihood[n]; +} + +//-------------------------------------------------------------- +GVFOutcomes & GVF::update(vector<float> & observation) +{ + + if (state != GVF::STATE_FOLLOWING) setState(GVF::STATE_FOLLOWING); + + theGesture.addObservation(observation); + vector<float> obs = theGesture.getLastObservation(); + + // std::cout << obs[0] << " " << obs[0] << " " + // << gestureTemplates[0].getTemplate()[20][0] << " " << gestureTemplates[0].getTemplate()[20][1] << " " + // << gestureTemplates[1].getTemplate()[20][0] << " " << gestureTemplates[1].getTemplate()[20][1] << std::endl; + + + // for each particle: perform updates of state space / likelihood / prior (weights) + float sumw = 0.0; + for(int n = 0; n< parameters.numberParticles; n++) + { + + for (int m=0; m<parameters.predictionSteps; m++) + { + updatePrior(n); + updateLikelihood(obs, n); + updatePosterior(n); + } + + sumw += posterior[n]; // sum posterior to normalise the distrib afterwards + + particles[n][0] = alignment[n]; + particles[n][1] = scalings[n][0]; + particles[n][2] = classes[n]; + } + + // normalize the weights and compute the resampling criterion + float dotProdw = 0.0; + for (int k = 0; k < parameters.numberParticles; k++){ + posterior[k] /= sumw; + dotProdw += posterior[k] * posterior[k]; + } + // avoid degeneracy (no particles active, i.e. weight = 0) by resampling + if( (1./dotProdw) < parameters.resamplingThreshold) + resampleAccordingToWeights(obs); + + // estimate outcomes + estimates(); + + return outcomes; + +} + +//-------------------------------------------------------------- +void GVF::resampleAccordingToWeights(vector<float> obs) +{ + // covennient + int numOfPart = parameters.numberParticles; + + // cumulative dist + vector<float> c(numOfPart); + + // tmp matrices + vector<int> oldClasses; + vector<float> oldAlignment; + vector< vector<float> > oldDynamics; + vector< vector<float> > oldScalings; + vector< vector<float> > oldRotations; + + setVec(oldClasses, classes); + setVec(oldAlignment, alignment); + setMat(oldDynamics, dynamics); + setMat(oldScalings, scalings); + if (rotationsDim!=0) setMat(oldRotations, rotations); + + + c[0] = 0; + for(int i = 1; i < numOfPart; i++) c[i] = c[i-1] + posterior[i]; + + + float u0 = (*rndunif)(unifgen)/numOfPart; + + int i = 0; + for (int j = 0; j < numOfPart; j++) + { + float uj = u0 + (j + 0.) / numOfPart; + + while (uj > c[i] && i < numOfPart - 1){ + i++; + } + + classes[j] = oldClasses[i]; + alignment[j] = oldAlignment[i]; + + for (int l=0;l<dynamicsDim;l++) dynamics[j][l] = oldDynamics[i][l]; + for (int l=0;l<scalingsDim;l++) scalings[j][l] = oldScalings[i][l]; + if (rotationsDim!=0) for (int l=0;l<rotationsDim;l++) rotations[j][l] = oldRotations[i][l]; + + // update posterior (partilces' weights) + posterior[j] = 1.0/(float)numOfPart; + } + +} + + +//-------------------------------------------------------------- +void GVF::estimates(){ + + + int numOfPart = parameters.numberParticles; + vector<float> probabilityNormalisation(getNumberOfGestureTemplates()); + setVec(probabilityNormalisation, 0.0f, getNumberOfGestureTemplates()); // rows are gestures + setVec(estimatedAlignment, 0.0f, getNumberOfGestureTemplates()); // rows are gestures + setMat(estimatedDynamics, 0.0f, getNumberOfGestureTemplates(), dynamicsDim); // rows are gestures, cols are features + probabilities + setMat(estimatedScalings, 0.0f, getNumberOfGestureTemplates(), scalingsDim); // rows are gestures, cols are features + probabilities + if (rotationsDim!=0) setMat(estimatedRotations, 0.0f, getNumberOfGestureTemplates(), rotationsDim); // .. + setVec(estimatedProbabilities, 0.0f, getNumberOfGestureTemplates()); // rows are gestures + setVec(estimatedLikelihoods, 0.0f, getNumberOfGestureTemplates()); // rows are gestures + + // float sumposterior = 0.; + + for(int n = 0; n < numOfPart; n++) + { + probabilityNormalisation[classes[n]] += posterior[n]; + } + + + // compute the estimated features and likelihoods + for(int n = 0; n < numOfPart; n++) + { + + // sumposterior += posterior[n]; + estimatedAlignment[classes[n]] += alignment[n] * posterior[n]; + + for(int m = 0; m < dynamicsDim; m++) + estimatedDynamics[classes[n]][m] += dynamics[n][m] * (posterior[n]/probabilityNormalisation[classes[n]]); + + for(int m = 0; m < scalingsDim; m++) + estimatedScalings[classes[n]][m] += scalings[n][m] * (posterior[n]/probabilityNormalisation[classes[n]]); + + if (rotationsDim!=0) + for(int m = 0; m < rotationsDim; m++) + estimatedRotations[classes[n]][m] += rotations[n][m] * (posterior[n]/probabilityNormalisation[classes[n]]); + + if (!isnan(posterior[n])) + estimatedProbabilities[classes[n]] += posterior[n]; + estimatedLikelihoods[classes[n]] += likelihood[n]; + } + + // calculate most probable index during scaling... + float maxProbability = 0.0f; + mostProbableIndex = -1; + + for(int gi = 0; gi < getNumberOfGestureTemplates(); gi++) + { + if(estimatedProbabilities[gi] > maxProbability){ + maxProbability = estimatedProbabilities[gi]; + mostProbableIndex = gi; + } + } + // std::cout << estimatedProbabilities[0] << " " << estimatedProbabilities[1] << std::endl; + + // outcomes.estimations.clear(); + outcomes.likelihoods.clear(); + outcomes.alignments.clear(); + outcomes.scalings.clear(); + outcomes.dynamics.clear(); + outcomes.rotations.clear(); + + // most probable gesture index + outcomes.likeliestGesture = mostProbableIndex; + + // Fill estimation for each gesture + for (int gi = 0; gi < gestureTemplates.size(); ++gi) { + + // GVFEstimation estimation; + outcomes.likelihoods.push_back(estimatedProbabilities[gi]); + outcomes.alignments.push_back(estimatedAlignment[gi]); + // estimation.probability = estimatedProbabilities[gi]; + // estimation.alignment = estimatedAlignment[gi]; + + + vector<float> gDynamics(dynamicsDim, 0.0); + for (int j = 0; j < dynamicsDim; ++j) gDynamics[j] = estimatedDynamics[gi][j]; + outcomes.dynamics.push_back(gDynamics); + + vector<float> gScalings(scalingsDim, 0.0); + for (int j = 0; j < scalingsDim; ++j) gScalings[j] = estimatedScalings[gi][j]; + outcomes.scalings.push_back(gScalings); + + vector<float> gRotations; + if (rotationsDim!=0) + { + gRotations.resize(rotationsDim); + for (int j = 0; j < rotationsDim; ++j) gRotations[j] = estimatedRotations[gi][j]; + outcomes.rotations.push_back(gRotations); + } + + // estimation.likelihood = estimatedLikelihoods[gi]; + + // push estimation for gesture gi in outcomes + // outcomes.estimations.push_back(estimation); + } + + + // assert(outcomes.estimations.size() == gestureTemplates.size()); + +} + +////-------------------------------------------------------------- +//int GVF::getMostProbableGestureIndex() +//{ +// return mostProbableIndex; +//} + +////-------------------------------------------------------------- +//GVFOutcomes GVF::getOutcomes() +//{ +// return outcomes; +//} + +////-------------------------------------------------------------- +//GVFEstimation GVF::getTemplateRecogInfo(int templateNumber) +//{ +// if (getOutcomes().estimations.size() <= templateNumber) { +// GVFEstimation estimation; +// return estimation; // blank +// } +// else +// return getOutcomes().estimations[templateNumber]; +//} +// +////-------------------------------------------------------------- +//GVFEstimation GVF::getRecogInfoOfMostProbable() // FIXME: Rename! +//{ +// int indexMostProbable = getMostProbableGestureIndex(); +// +// if ((getState() == GVF::STATE_FOLLOWING) && (getMostProbableGestureIndex() != -1)) { +// return getTemplateRecogInfo(indexMostProbable); +// } +// else { +// GVFEstimation estimation; +// return estimation; // blank +// } +//} + + +////-------------------------------------------------------------- +//vector<float> & GVF::getGestureProbabilities() +//{ +// gestureProbabilities.resize(getNumberOfGestureTemplates()); +// setVec(gestureProbabilities, 0.0f); +// for(int n = 0; n < parameters.numberParticles; n++) +// gestureProbabilities[classes[n]] += posterior[n]; +// +// return gestureProbabilities; +//} + +//-------------------------------------------------------------- +const vector<vector<float> > & GVF::getParticlesPositions(){ + return particles; +} + +////-------------------------------------------------------------- +//void GVF::setParameters(GVFParameters _parameters){ +// +// // if the number of particles has changed, we have to re-allocate matrices +// if (_parameters.numberParticles != parameters.numberParticles) +// { +// parameters = _parameters; +// +// // minimum number of particles allowed +// if (parameters.numberParticles < 4) parameters.numberParticles = 4; +// +// // re-learn +// train(); +// +// // adapt the resampling threshold in case if RT < NS +// if (parameters.numberParticles <= parameters.resamplingThreshold) +// parameters.resamplingThreshold = parameters.numberParticles / 4; +// +// } +// else +// parameters = _parameters; +// +// +//} +// +//GVFParameters GVF::getParameters(){ +// return parameters; +//} + +//-------------------------------------------------------------- +// Update the number of particles +void GVF::setNumberOfParticles(int numberOfParticles){ + + parameters.numberParticles = numberOfParticles; + + if (parameters.numberParticles < 4) // minimum number of particles allowed + parameters.numberParticles = 4; + + train(); + + if (parameters.numberParticles <= parameters.resamplingThreshold) { + parameters.resamplingThreshold = parameters.numberParticles / 4; + } + +} + +//-------------------------------------------------------------- +int GVF::getNumberOfParticles(){ + return parameters.numberParticles; // Return the number of particles +} + +//-------------------------------------------------------------- +void GVF::setActiveGestures(vector<int> activeGestureIds) +{ + int argmax = *std::max_element(activeGestureIds.begin(), activeGestureIds.end()); + if (activeGestureIds[argmax] <= gestureTemplates.size()) + { + activeGestures = activeGestureIds; + } + else + { + activeGestures.resize(gestureTemplates.size()); + std::iota(activeGestures.begin(), activeGestures.end(), 1); + } +} + +//-------------------------------------------------------------- +void GVF::setPredictionSteps(int predictionSteps) +{ + if (predictionSteps<1) + parameters.predictionSteps = 1; + else + parameters.predictionSteps = predictionSteps; +} + +//-------------------------------------------------------------- +int GVF::getPredictionSteps() +{ + return parameters.predictionSteps; // Return the number of particles +} + +//-------------------------------------------------------------- +// Update the resampling threshold used to avoid degeneracy problem +void GVF::setResamplingThreshold(int _resamplingThreshold){ + if (_resamplingThreshold >= parameters.numberParticles) + _resamplingThreshold = floor(parameters.numberParticles/2.0f); + parameters.resamplingThreshold = _resamplingThreshold; +} + +//-------------------------------------------------------------- +// Return the resampling threshold used to avoid degeneracy problem +int GVF::getResamplingThreshold(){ + return parameters.resamplingThreshold; +} + +//-------------------------------------------------------------- +// Update the standard deviation of the observation distribution +// this value acts as a tolerance for the algorithm +// low value: less tolerant so more precise but can diverge +// high value: more tolerant so less precise but converge more easily +void GVF::setTolerance(float _tolerance){ + if (_tolerance <= 0.0) _tolerance = 0.1; + parameters.tolerance = _tolerance; + tolerancesetmanually = true; +} + +//-------------------------------------------------------------- +float GVF::getTolerance(){ + return parameters.tolerance; +} + +////-------------------------------------------------------------- +void GVF::setDistribution(float _distribution){ + //nu = _distribution; + parameters.distribution = _distribution; +} +// +////-------------------------------------------------------------- +//float GVF::getDistribution(){ +// return parameters.distribution; +//} + +//void GVF::setDimWeights(vector<float> dimWeights){ +// if (dimWeights.size()!=parameters.dimWeights.size()) +// parameters.dimWeights.resize(dimWeights.size()); +// parameters.dimWeights = dimWeights; +//} +// +//vector<float> GVF::getDimWeights(){ +// return parameters.dimWeights; +//} + + +//// VARIANCE COEFFICIENTS: PHASE +////-------------------------------------------------------------- +//void GVF::setAlignmentVariance(float alignmentVariance){ +// parameters.alignmentVariance = sqrt(alignmentVariance); +//} +////-------------------------------------------------------------- +//float GVF::getAlignmentVariance(){ +// return parameters.alignmentVariance; +//} + + +// VARIANCE COEFFICIENTS: DYNAMICS +//-------------------------------------------------------------- +//void GVF::setDynamicsVariance(float dynVariance) +//{ +// for (int k=0; k< parameters.dynamicsVariance.size(); k++) +// parameters.dynamicsVariance[k] = dynVariance; +//} +//-------------------------------------------------------------- +void GVF::setDynamicsVariance(float dynVariance, int dim) +{ + if (dim == -1) + { + for (int k=0; k< parameters.dynamicsVariance.size(); k++) + parameters.dynamicsVariance[k] = dynVariance; + } + else + { + if (dim<parameters.dynamicsVariance.size()) + parameters.dynamicsVariance[dim-1] = dynVariance; + } +} + +//-------------------------------------------------------------- +void GVF::setDynamicsVariance(vector<float> dynVariance) +{ + parameters.dynamicsVariance = dynVariance; +} +//-------------------------------------------------------------- +vector<float> GVF::getDynamicsVariance() +{ + return parameters.dynamicsVariance; +} + +//-------------------------------------------------------------- +void GVF::setScalingsVariance(float scaleVariance, int dim) +{ + if (dim == -1) + { + for (int k=0; k< parameters.scalingsVariance.size(); k++) + parameters.scalingsVariance[k] = scaleVariance; + } + else + { + if (dim<parameters.scalingsVariance.size()) + parameters.scalingsVariance[dim-1] = scaleVariance; + } +} + +//-------------------------------------------------------------- +void GVF::setScalingsVariance(vector<float> scaleVariance) +{ + parameters.scaleVariance = scaleVariance; +} + +//-------------------------------------------------------------- +vector<float> GVF::getScalingsVariance() +{ + return parameters.scalingsVariance; +} + +//-------------------------------------------------------------- +void GVF::setRotationsVariance(float rotationVariance, int dim) +{ + if (dim == -1) + { + for (int k=0; k< parameters.rotationsVariance.size(); k++) + parameters.rotationsVariance[k] = rotationVariance; + } + else + { + if (dim<parameters.rotationsVariance.size()) + parameters.scalingsVariance[dim-1] = rotationVariance; + } +} + +//-------------------------------------------------------------- +void GVF::setRotationsVariance(vector<float> rotationVariance) +{ + parameters.scaleVariance = rotationVariance; +} + +//-------------------------------------------------------------- +vector<float> GVF::getRotationsVariance() +{ + return parameters.rotationsVariance; +} + +//-------------------------------------------------------------- +void GVF::setSpreadDynamics(float center, float range, int dim) +{ + parameters.dynamicsSpreadingCenter = center; + parameters.dynamicsSpreadingRange = range; +} + +//-------------------------------------------------------------- +void GVF::setSpreadScalings(float center, float range, int dim) +{ + parameters.scalingsSpreadingCenter = center; + parameters.scalingsSpreadingRange = range; +} + +//-------------------------------------------------------------- +void GVF::setSpreadRotations(float center, float range, int dim) +{ + parameters.rotationsSpreadingCenter = center; + parameters.rotationsSpreadingRange = range; +} + +//-------------------------------------------------------------- +void GVF::translate(bool translateFlag) +{ + config.translate = translateFlag; +} + +//-------------------------------------------------------------- +void GVF::segmentation(bool segmentationFlag) +{ + config.segmentation = segmentationFlag; +} + + +// UTILITIES + +//-------------------------------------------------------------- +// Save function. This function is used by applications to save the +// vocabulary in a text file given by filename (filename is also the complete path + filename) +void GVF::saveTemplates(string filename){ + + std::string directory = filename; + + std::ofstream file_write(directory.c_str()); + + for(int i=0; i < gestureTemplates.size(); i++) // Number of gesture templates + { + file_write << "template " << i << " " << config.inputDimensions << endl; + vector<vector<float> > templateTmp = gestureTemplates[i].getTemplate(); + for(int j = 0; j < templateTmp.size(); j++) + { + for(int k = 0; k < config.inputDimensions; k++) + file_write << templateTmp[j][k] << " "; + file_write << endl; + } + } + file_write.close(); + +} + + + + +//-------------------------------------------------------------- +// Load function. This function is used by applications to load a vocabulary +// given by filename (filename is also the complete path + filename) +void GVF::loadTemplates(string filename){ + // clear(); + // + + GVFGesture loadedGesture; + loadedGesture.clear(); + + ifstream infile; + stringstream doung; + + infile.open (filename.c_str(), ifstream::in); + // + string line; + vector<string> list; + int cl = -1; + while(!infile.eof()) + { + cl++; + infile >> line; + + list.push_back(line); + } + + int k = 0; + int template_id = -1; + int template_dim = 0; + + + while (k < (list.size() - 1)){ // TODO to be changed if dim>2 + + + if (!strcmp(list[k].c_str(),"template")) + { + template_id = atoi(list[k+1].c_str()); + template_dim = atoi(list[k+2].c_str()); + k = k + 3; + + if (loadedGesture.getNumberOfTemplates() > 0){ + addGestureTemplate(loadedGesture); + loadedGesture.clear(); + } + } + + if (template_dim <= 0){ + //post("bug dim = -1"); + } + else{ + + vector<float> vect(template_dim); + + for (int kk = 0; kk < template_dim; kk++) + vect[kk] = (float) atof(list[k + kk].c_str()); + + loadedGesture.addObservation(vect); + } + k += template_dim; + + } + + if (loadedGesture.getTemplateLength() > 0){ + addGestureTemplate(loadedGesture); + loadedGesture.clear(); + } + + infile.close(); +} + + + + + + diff --git a/dependencies/GVF/GVF.h b/dependencies/GVF/GVF.h new file mode 100755 index 0000000..647fe7c --- /dev/null +++ b/dependencies/GVF/GVF.h @@ -0,0 +1,487 @@ +/** + * Gesture Variation Follower class allows for early gesture recognition and variation tracking + * + * @details Original algorithm designed and implemented in 2011 at Ircam Centre Pompidou + * by Baptiste Caramiaux and Nicola Montecchio. The library has been created and is maintained by Baptiste Caramiaux + * + * Copyright (C) 2015 Baptiste Caramiaux, Nicola Montecchio + * STMS lab Ircam-CRNS-UPMC, University of Padova, Goldsmiths College University of London + * + * The library is under the GNU Lesser General Public License (LGPL v3) + */ + + +#ifndef _H_GVF +#define _H_GVF + +#include "GVFUtils.h" +#include "GVFGesture.h" +#include <random> +#include <iostream> +#include <iomanip> +#include <string> +#include <map> +#include <random> +#include <cmath> + + +using namespace std; + +class GVF +{ + +public: + + /** + * GVF possible states + */ + enum GVFState + { + STATE_CLEAR = 0, /**< STATE_CLEAR: clear the GVF and be in standby */ + STATE_LEARNING, /**< STATE_LEARNING: recording mode, input gestures are added to the templates */ + STATE_FOLLOWING, /**< STATE_FOLLOWING: tracking mode, input gestures are classifed and their variations tracked (need the GVF to be trained) */ + STATE_BYPASS /**< STATE_BYPASS: by pass GVF but does not erase templates or training */ + }; + + +#pragma mark - Constructors + + /** + * GVF default constructor + * @details use default configuration and parameters, can be changed using accessors + */ + GVF(); + + /** + * GVF default destructor + */ + ~GVF(); + +#pragma mark - Gesture templates + + /** + * Start a gesture either to be recorded or followed + */ + void startGesture(); + + /** + * Add an observation to a gesture template + * @details + * @param data vector of features + */ + void addObservation(vector<float> data); + + /** + * Add gesture template to the vocabulary + * + * @details a gesture template is a GVFGesture object and can be added directly to the vocabulqry or + * recorded gesture templates by using this method + * @param gestureTemplate the gesture template to be recorded + */ + void addGestureTemplate(GVFGesture & gestureTemplate); + + /** + * Replace a specific gesture template by another + * + * @param gestureTemplate the gesture template to be used + * @param index the gesture index (as integer) to be replaced + */ + void replaceGestureTemplate(GVFGesture & gestureTemplate, int index); + + /** + * Remove a specific template + * + * @param index the gesture index (as integer) to be removed + */ + void removeGestureTemplate(int index); + + /** + * Remove every recorded gesture template + */ + void removeAllGestureTemplates(); + + /** + * Get a specific gesture template a gesture template by another + * + * @param index the index of the template to be returned + * @return the template + */ + GVFGesture & getGestureTemplate(int index); + + /** + * Get every recorded gesture template + * + * @return the vecotr of gesture templates + */ + vector<GVFGesture> & getAllGestureTemplates(); + + /** + * Get number of gesture templates in the vocabulary + * @return the number of templates + */ + int getNumberOfGestureTemplates(); + + /** + * Get gesture classes + */ + vector<int> getGestureClasses(); + + +#pragma mark - Recognition and tracking + + /** + * Set the state of GVF + * @param _state the state to be given to GVF, it is a GVFState + * @param indexes an optional argument providing a list of gesture index. + * In learning mode the index of the gesture being recorded can be given as an argument + * since the type is vector<int>, it should be something like '{3}'. In following mode, the list of indexes + * is the list of active gestures to be considered in the recognition/tracking. + */ + void setState(GVFState _state, vector<int> indexes = vector<int>()); + + /** + * Return the current state of GVF + * @return GVFState the current state + */ + GVFState getState(); + + /** + * Compute the estimated gesture and its potential variations + * + * @details infers the probability that the current observation belongs to + * one of the recorded gesture template and track the variations of this gesture + * according to each template + * + * @param observation vector of the observation data at current time + * @return the estimated probabilities and variaitons relative to each template + */ + GVFOutcomes & update(vector<float> & observation); + + /** + * Define a subset of gesture templates on which to perform the recognition + * and variation tracking + * + * @details By default every recorded gesture template is considered + * @param set of gesture template index to consider + */ + void setActiveGestures(vector<int> activeGestureIds); + + /** + * Restart GVF + * @details re-sample particles at the origin (i.e. initial prior) + */ + void restart(); + + /** + * Clear GVF + * @details delete templates + */ + void clear(); + + /** + * Translate data according to the first point + * @details substract each gesture feature by the first point of the gesture + * @param boolean to activate or deactivate translation + */ + void translate(bool translateFlag); + + /** + * Segment gestures within a continuous gesture stream + * @details if segmentation is true, the method will segment a continuous gesture into a sequence + * of gestures. In other words no need to call the method startGesture(), it is done automatically + * @param segmentationFlag boolean to activate or deactivate segmentation + */ + void segmentation(bool segmentationFlag); + +#pragma mark - [ Accessors ] +#pragma mark > Parameters + /** + * Set tolerance between observation and estimation + * @details tolerance depends on the range of the data + * typially tolerance = (data range)/3.0; + * @param tolerance value + */ + void setTolerance(float tolerance); + + /** + * Get the obervation tolerance value + * @details see setTolerance(float tolerance) + * @return the current toleranc value + */ + float getTolerance(); + + void setDistribution(float _distribution); + + /** + * Set number of particles used in estimation + * @details default valye is 1000, note that the computational + * cost directly depends on the number of particles + * @param new number of particles + */ + void setNumberOfParticles(int numberOfParticles); + + /** + * Get the current number of particles + * @return the current number of particles + */ + int getNumberOfParticles(); + + /** + * Number of prediciton steps + * @details it is possible to leave GVF to perform few steps of prediction + * ahead which can be useful to estimate more fastly the variations. Default value is 1 + * which means no prediction ahead + * @param the number of prediction steps + */ + void setPredictionSteps(int predictionSteps); + + /** + * Get the current number of prediction steps + * @return current number of prediciton steps + */ + int getPredictionSteps(); + + /** + * Set resampling threshold + * @details resampling threshold is the minimum number of active particles + * before resampling all the particles by the estimated posterior distribution. + * in other words, it re-targets particles around the best current estimates + * @param the minimum number of particles (default is (number of particles)/2) + */ + void setResamplingThreshold(int resamplingThreshold); + + /** + * Get the current resampling threshold + * @return resampling threshold + */ + int getResamplingThreshold(); + +#pragma mark > Dynamics + /** + * Change variance of adaptation in dynamics + * @details if dynamics adaptation variance is high the method will adapt faster to + * fast changes in dynamics. Dynamics is 2-dimensional: the first dimension is the speed + * The second dimension is the acceleration. + * + * Typically the variance is the average amount the speed or acceleration can change from + * one sample to another. As an example, if the relative estimated speed can change from 1.1 to 1.2 + * from one sample to another, the variance should allow a change of 0.1 in speed. So the variance + * should be set to 0.1*0.1 = 0.01 + * + * @param dynVariance dynamics variance value + * @param dim optional dimension of the dynamics for which the change of variance is applied (default value is 1) + */ + void setDynamicsVariance(float dynVariance, int dim = -1); + + /** + * Change variance of adaptation in dynamics + * @details See setDynamicsVariance(float dynVariance, int dim) for more details + * @param dynVariance vector of dynamics variances, each vector index is the variance to be applied to + * each dynamics dimension (consequently the vector should be 2-dimensional). + */ + void setDynamicsVariance(vector<float> dynVariance); + + /** + * Get dynamics variances + * @return the vector of variances (the returned vector is 2-dimensional) + */ + vector<float> getDynamicsVariance(); + +#pragma mark > Scalings + /** + * Change variance of adaptation in scalings + * @details if scalings adaptation variance is high the method will adapt faster to + * fast changes in relative sizes. There is one scaling variance for each dimension + * of the input gesture. If the gesture is 2-dimensional, the scalings variances will + * also be 2-dimensional. + * + * Typically the variance is the average amount the size can change from + * one sample to another. As an example, if the relative estimated size changes from 1.1 to 1.15 + * from one sample to another, the variance should allow a change of 0.05 in size. So the variance + * should be set to 0.05*0.05 = 0.0025 + * + * @param scalings variance value + * @param dimension of the scalings for which the change of variance is applied + */ + void setScalingsVariance(float scaleVariance, int dim = -1); + + /** + * Change variance of adaptation in dynamics + * @details See setScalingsVariance(float scaleVariance, int dim) for more details + * @param vector of scalings variances, each vector index is the variance to be applied to + * each scaling dimension. + * @param vector of variances (should be the size of the template gestures dimension) + */ + void setScalingsVariance(vector<float> scaleVariance); + + /** + * Get scalings variances + * @return the vector of variances + */ + vector<float> getScalingsVariance(); + +#pragma mark > Rotations + /** + * Change variance of adaptation in orientation + * @details if rotation adaptation variance is high the method will adapt faster to + * fast changes in relative orientation. If the gesture is 2-dimensional, there is + * one variance value since the rotation can be defined by only one angle of rotation. If + * the gesture is 3-dimensional, there are 3 variance values since the rotation in 3-d is + * defined by 3 rotation angles. For any other dimension, the rotation is not defined. + * + * The variance is the average amount the orientation can change from one sample to another. + * As an example, if the relative orientation in rad changes from 0.1 to 0.2 from one observation + * to another, the variance should allow a change of 0.1 in rotation angle. So the variance + * should be set to 0.1*0.1 = 0.01 + * + * @param rotationsVariance rotation variance value + * @param dim optional dimension of the rotation for which the change of variance is applied + */ + void setRotationsVariance(float rotationsVariance, int dim = -1); + + /** + * Change variance of adaptation in orientation + * @details See setRotationsVariance(float rotationsVariance, int dim) for more details + * @param vector of rotation variances, each vector index is the variance to be applied to + * each rotation angle (1 or 3) + * @param vector of variances (should be 1 if the the template gestures are 2-dim or 3 if + * they are 3-dim) + */ + void setRotationsVariance(vector<float> rotationsVariance); + + /** + * Get rotation variances + * @return the vector of variances + */ + vector<float> getRotationsVariance(); + + +#pragma mark > Others + + /** + * Get particle values + * @return vector of list of estimated particles + */ + const vector<vector<float> > & getParticlesPositions(); + + /** + * Set the interval on which the dynamics values should be spread at the beginning (before adaptation) + * @details this interval can be used to concentrate the potential dynamics value on a narrow interval, + * typically around 1 (the default value), for instance between -0.05 and 0.05, or to allow at the very + * beginning, high changes in dynamics by spreading, for instance between 0.0 and 2.0 + * @param min lower value of the inital values for dynamics + * @param max higher value of the inital values for dynamics + * @param dim the dimension on which the change of initial interval should be applied (optional) + */ + void setSpreadDynamics(float min, float max, int dim = -1); + + /** + * Set the interval on which the scalings values should be spread at the beginning (before adaptation) + * @details this interval can be used to concentrate the potential scalings value on a narrow interval, + * typically around 1.0 (the default value), for instance between 0.95 and 1.05, or to allow at the very + * beginning high changes in dynamics by spreading, for instance, between 0.0 and 2.0 + * @param min lower value of the inital values for scalings + * @param max higher value of the inital values for scalings + * @param dim the dimension on which the change of initial interval should be applied (optional) + */ + void setSpreadScalings(float min, float max, int dim = -1); + + /** + * Set the interval on which the angle of rotation values should be spread at the beginning (before adaptation) + * @details this interval can be used to concentrate the potential angle values on a narrow interval, + * typically around 0.0 (the default value), for instance between -0.05 and 0.05, or to allow at the very + * beginning, high changes in orientation by spreading, for instance, between -0.5 and 0.5 + * @param min lower value of the inital values for angle of rotation + * @param max higher value of the inital values for angle of rotation + * @param dim the dimension on which the change of initial interval should be applied (optional) + */ + void setSpreadRotations(float min, float max, int dim = -1); + +#pragma mark - Import/Export templates + /** + * Export template data in a filename + * @param filename file name as a string + */ + void saveTemplates(string filename); + + /** + * Import template data in a filename + * @details needs to respect a given format provided by saveTemplates() + * @param file name as a string + */ + void loadTemplates(string filename); + +protected: + + GVFConfig config; // Structure storing the configuration of GVF (in GVFUtils.h) + GVFParameters parameters; // Structure storing the parameters of GVF (in GVFUtils.h) + GVFOutcomes outcomes; // Structure storing the outputs of GVF (in GVFUtils.h) + GVFState state; // State (defined above) + GVFGesture theGesture; // GVFGesture object to handle incoming data in learning and following modes + + vector<GVFGesture> gestureTemplates; // vector storing the gesture templates recorded when using the methods addObservation(vector<float> data) or addGestureTemplate(GVFGesture & gestureTemplate) + + vector<float> dimWeights; // TOOD: to be put in parameters? + vector<float> maxRange; + vector<float> minRange; + int dynamicsDim; // dynamics state dimension + int scalingsDim; // scalings state dimension + int rotationsDim; // rotation state dimension + float globalNormalizationFactor; // flagged if normalization + int mostProbableIndex; // cached most probable index + int learningGesture; + + vector<int> classes; // gesture index for each particle [ns x 1] + vector<float > alignment; // alignment index (between 0 and 1) [ns x 1] + vector<vector<float> > dynamics; // dynamics estimation [ns x 2] + vector<vector<float> > scalings; // scalings estimation [ns x D] + vector<vector<float> > rotations; // rotations estimation [ns x A] + vector<float> weights; // weight of each particle [ns x 1] + vector<float> prior; // prior of each particle [ns x 1] + vector<float> posterior; // poserior of each particle [ns x 1] + vector<float> likelihood; // likelihood of each particle [ns x 1] + + // estimations + vector<float> estimatedGesture; // .. + vector<float> estimatedAlignment; // .. + vector<vector<float> > estimatedDynamics; // .. + vector<vector<float> > estimatedScalings; // .. + vector<vector<float> > estimatedRotations; // .. + vector<float> estimatedProbabilities; // .. + vector<float> estimatedLikelihoods; // .. + vector<float> absoluteLikelihoods; // .. + + bool tolerancesetmanually; + + + vector<vector<float> > offsets; // translation offset + + vector<int> activeGestures; + + vector<float> gestureProbabilities; + vector< vector<float> > particles; + +private: + + // random number generator + std::random_device rd; + std::mt19937 normgen; + std::normal_distribution<float> *rndnorm; + std::default_random_engine unifgen; + std::uniform_real_distribution<float> *rndunif; + +#pragma mark - Private methods for model mechanics + void initPrior(); + void initNoiseParameters(); + void updateLikelihood(vector<float> obs, int n); + void updatePrior(int n); + void updatePosterior(int n); + void resampleAccordingToWeights(vector<float> obs); + void estimates(); // update estimated outcome + void train(); + + +}; + + +#endif \ No newline at end of file diff --git a/dependencies/GVF/GVFGesture.h b/dependencies/GVF/GVFGesture.h new file mode 100644 index 0000000..d556fb0 --- /dev/null +++ b/dependencies/GVF/GVFGesture.h @@ -0,0 +1,240 @@ +// +// GVFGesture.h +// gvf +// +// Created by Baptiste Caramiaux on 22/01/16. +// +// + +#ifndef GVFGesture_h +#define GVFGesture_h + +#ifndef MAX +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) +#endif + +#ifndef MIN +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) +#endif + + +class GVFGesture +{ +public: + + GVFGesture() + { + inputDimensions = 2; + setAutoAdjustRanges(true); + templatesRaw = vector<vector<vector<float > > >(); + templatesNormal = vector<vector<vector<float > > >(); + clear(); + } + + GVFGesture(int inputDimension){ + inputDimensions = inputDimension; + setAutoAdjustRanges(true); + templatesRaw = vector<vector<vector<float > > >(); + templatesNormal = vector<vector<vector<float > > >(); + clear(); + } + + ~GVFGesture(){ + clear(); + } + + void setNumberDimensions(int dimensions){ + assert(dimensions > 0); + inputDimensions = dimensions; + } + + void setAutoAdjustRanges(bool b){ + // if(b) bIsRangeMinSet = bIsRangeMaxSet = false; + bAutoAdjustNormalRange = b; + } + + void setMax(float x, float y){ + assert(inputDimensions == 2); + vector<float> r(2); + r[0] = x; r[1] = y; + setMaxRange(r); + } + + void setMin(float x, float y){ + assert(inputDimensions == 2); + vector<float> r(2); + r[0] = x; r[1] = y; + setMinRange(r); + } + + void setMax(float x, float y, float z){ + assert(inputDimensions == 3); + vector<float> r(3); + r[0] = x; r[1] = y; r[2] = z; + setMaxRange(r); + } + + void setMin(float x, float y, float z){ + assert(inputDimensions == 3); + vector<float> r(3); + r[0] = x; r[1] = y; r[2] = z; + setMinRange(r); + } + + void setMaxRange(vector<float> observationRangeMax){ + this->observationRangeMax = observationRangeMax; + // bIsRangeMaxSet = true; + normalise(); + } + + void setMinRange(vector<float> observationRangeMin){ + this->observationRangeMin = observationRangeMin; + // bIsRangeMinSet = true; + normalise(); + } + + vector<float>& getMaxRange(){ + return observationRangeMax; + } + + vector<float>& getMinRange(){ + return observationRangeMin; + } + + void autoAdjustMinMax(vector<float> & observation){ + if(observationRangeMax.size() < inputDimensions){ + observationRangeMax.assign(inputDimensions, -INFINITY); + observationRangeMin.assign(inputDimensions, INFINITY); + } + for(int i = 0; i < inputDimensions; i++){ + observationRangeMax[i] = MAX(observationRangeMax[i], observation[i]); + observationRangeMin[i] = MIN(observationRangeMin[i], observation[i]); + } + } + + void addObservation(vector<float> observation, int templateIndex = 0){ + if (observation.size() != inputDimensions) + inputDimensions = observation.size(); + + // check we have a valid templateIndex and correct number of input dimensions + assert(templateIndex <= templatesRaw.size()); + assert(observation.size() == inputDimensions); + + // if the template index is same as the number of temlates make a new template + if(templateIndex == templatesRaw.size()){ // make a new template + + // reserve space in raw and normal template storage + templatesRaw.resize(templatesRaw.size() + 1); + templatesNormal.resize(templatesNormal.size() + 1); + + } + + if(templatesRaw[templateIndex].size() == 0) + { + templateInitialObservation = observation; + templateInitialNormal = observation; + } + + for(int j = 0; j < observation.size(); j++) + observation[j] = observation[j] - templateInitialObservation[j]; + + // store the raw observation + templatesRaw[templateIndex].push_back(observation); + + autoAdjustMinMax(observation); + + normalise(); + } + + + + void normalise() + { + templatesNormal.resize(templatesRaw.size()); + for(int t = 0; t < templatesRaw.size(); t++) + { + templatesNormal[t].resize(templatesRaw[t].size()); + for(int o = 0; o < templatesRaw[t].size(); o++) + { + templatesNormal[t][o].resize(inputDimensions); + for(int d = 0; d < inputDimensions; d++) + { + templatesNormal[t][o][d] = templatesRaw[t][o][d] / (observationRangeMax[d] - observationRangeMin[d]); + templateInitialNormal[d] = templateInitialObservation[d] / (observationRangeMax[d] - observationRangeMin[d]); + } + } + } + } + + void setTemplate(vector< vector<float> > & observations, int templateIndex = 0){ + for(int i = 0; i < observations.size(); i++){ + addObservation(observations[i], templateIndex); + } + } + + vector< vector<float> > & getTemplate(int templateIndex = 0){ + assert(templateIndex < templatesRaw.size()); + return templatesRaw[templateIndex]; + } + + int getNumberOfTemplates(){ + return templatesRaw.size(); + } + + int getNumberDimensions(){ + return inputDimensions; + } + + int getTemplateLength(int templateIndex = 0){ + return templatesRaw[templateIndex].size(); + } + + int getTemplateDimension(int templateIndex = 0){ + return templatesRaw[templateIndex][0].size(); + } + + vector<float>& getLastObservation(int templateIndex = 0){ + return templatesRaw[templateIndex][templatesRaw[templateIndex].size() - 1]; + } + + vector< vector< vector<float> > >& getTemplates(){ + return templatesRaw; + } + + vector<float>& getInitialObservation(){ + return templateInitialObservation; + } + + void deleteTemplate(int templateIndex = 0) + { + assert(templateIndex < templatesRaw.size()); + templatesRaw[templateIndex].clear(); + templatesNormal[templateIndex].clear(); + } + + void clear() + { + templatesRaw.clear(); + templatesNormal.clear(); + observationRangeMax.assign(inputDimensions, -INFINITY); + observationRangeMin.assign(inputDimensions, INFINITY); + } + +private: + + int inputDimensions; + bool bAutoAdjustNormalRange; + + vector<float> observationRangeMax; + vector<float> observationRangeMin; + + vector<float> templateInitialObservation; + vector<float> templateInitialNormal; + + vector< vector< vector<float> > > templatesRaw; + vector< vector< vector<float> > > templatesNormal; + + vector<vector<float> > gestureDataFromFile; +}; + +#endif /* GVFGesture_h */ diff --git a/dependencies/GVF/GVFUtils.h b/dependencies/GVF/GVFUtils.h new file mode 100755 index 0000000..125676c --- /dev/null +++ b/dependencies/GVF/GVFUtils.h @@ -0,0 +1,309 @@ +// +// GVFTypesAndUtils.h +// +// +// + +#ifndef __H_GVFTYPES +#define __H_GVFTYPES + +#include <map> +#include <vector> +#include <iostream> +#include <random> +#include <iostream> +#include <math.h> +#include <assert.h> + +using namespace std; + +/** + * Configuration structure + */ +typedef struct +{ + int inputDimensions; /**< input dimesnion */ + bool translate; /**< translate flag */ + bool segmentation; /**< segmentation flag */ +} GVFConfig; + +/** + * Parameters structure + */ +typedef struct +{ + float tolerance; /**< input dimesnion */ + float distribution; + int numberParticles; + int resamplingThreshold; + float alignmentVariance; + float speedVariance; + vector<float> scaleVariance; + vector<float> dynamicsVariance; + vector<float> scalingsVariance; + vector<float> rotationsVariance; + // spreadings + float alignmentSpreadingCenter; + float alignmentSpreadingRange; + float dynamicsSpreadingCenter; + float dynamicsSpreadingRange; + float scalingsSpreadingCenter; + float scalingsSpreadingRange; + float rotationsSpreadingCenter; + float rotationsSpreadingRange; + + int predictionSteps; + vector<float> dimWeights; +} GVFParameters; + +// Outcomes structure +typedef struct +{ + int likeliestGesture; + vector<float> likelihoods; + vector<float> alignments; + vector<vector<float> > dynamics; + vector<vector<float> > scalings; + vector<vector<float> > rotations; +} GVFOutcomes; + + +//-------------------------------------------------------------- +// init matrix by allocating memory +template <typename T> +inline void initMat(vector< vector<T> > & M, int rows, int cols){ + M.resize(rows); + for (int n=0; n<rows; n++){ + M[n].resize(cols); + } +} + +//-------------------------------------------------------------- +// init matrix and copy values from another matrix +template <typename T> +inline void setMat(vector< vector<T> > & C, vector< vector<float> > & M){ + int rows = M.size(); + int cols = M[0].size(); + //C.resize(rows); + C = vector<vector<T> >(rows); + for (int n=0; n<rows; n++){ + //C[n].resize(cols); + C[n] = vector<T>(cols); + for (int m=0;m<cols;m++){ + C[n][m] = M[n][m]; + } + } +} + +//-------------------------------------------------------------- +// init matrix by allocating memory and fill with T value +template <typename T> +inline void setMat(vector< vector<T> > & M, T value, int rows, int cols){ + M.resize(rows); + for (int n=0; n<rows; n++){ + M[n].resize(cols); + for (int m=0; m<cols; m++){ + M[n][m] = value; + } + } +} + +//-------------------------------------------------------------- +// set matrix filled with T value +template <typename T> +inline void setMat(vector< vector<T> > & M, T value){ + for (int n=0; n<M.size(); n++){ + for (int m=0; m<M[n].size(); m++){ + M[n][m] = value; + } + } +} + +//-------------------------------------------------------------- +template <typename T> +inline void printMat(vector< vector<T> > & M){ + for (int k=0; k<M.size(); k++){ + cout << k << ": "; + for (int l=0; l<M[0].size(); l++){ + cout << M[k][l] << " "; + } + cout << endl; + } + cout << endl; +} + +//-------------------------------------------------------------- +template <typename T> +inline void printVec(vector<T> & V){ + for (int k=0; k<V.size(); k++){ + cout << k << ": " << V[k] << (k == V.size() - 1 ? "" : " ,"); + } + cout << endl; +} + +//-------------------------------------------------------------- +template <typename T> +inline void initVec(vector<T> & V, int rows){ + V.resize(rows); +} + +//-------------------------------------------------------------- +template <typename T> +inline void setVec(vector<T> & C, vector<int> &V){ + int rows = V.size(); + C = vector<T>(rows); + //C.resize(rows); + for (int n=0; n<rows; n++){ + C[n] = V[n]; + } +} + +//-------------------------------------------------------------- +template <typename T> +inline void setVec(vector<T> & C, vector<float> & V){ + int rows = V.size(); + C.resize(rows); + for (int n=0; n<rows; n++){ + C[n] = V[n]; + } +} + +//-------------------------------------------------------------- +template <typename T> +inline void setVec(vector<T> & V, T value){ + for (int n=0; n<V.size(); n++){ + V[n] = value; + } +} + +//-------------------------------------------------------------- +template <typename T> +inline void setVec(vector<T> & V, T value, int rows){ + V.resize(rows); + setVec(V, value); +} + +//-------------------------------------------------------------- +template <typename T> +inline vector< vector<T> > dotMat(vector< vector<T> > & M1, vector< vector<T> > & M2){ + // TODO(Baptiste) +} + +//-------------------------------------------------------------- +template <typename T> +inline vector< vector<T> > multiplyMatf(vector< vector<T> > & M1, T v){ + vector< vector<T> > multiply; + initMat(multiply, M1.size(), M1[0].size()); + for (int i=0; i<M1.size(); i++){ + for (int j=0; j<M1[i].size(); j++){ + multiply[i][j] = M1[i][j] * v; + } + } + return multiply; +} + +//-------------------------------------------------------------- +template <typename T> +inline vector< vector<T> > multiplyMatf(vector< vector<T> > & M1, vector< vector<T> > & M2){ + assert(M1[0].size() == M2.size()); // columns in M1 == rows in M2 + vector< vector<T> > multiply; + initMat(multiply, M1.size(), M2[0].size()); // rows in M1 x cols in M2 + for (int i=0; i<M1.size(); i++){ + for (int j=0; j<M2[i].size(); j++){ + multiply[i][j] = 0.0f; + for(int k=0; k<M1[0].size(); k++){ + multiply[i][j] += M1[i][k] * M2[k][j]; + } + + } + } + return multiply; +} + +//-------------------------------------------------------------- +template <typename T> +inline vector<T> multiplyMat(vector< vector<T> > & M1, vector< T> & Vect){ + assert(Vect.size() == M1[0].size()); // columns in M1 == rows in Vect + vector<T> multiply; + initVec(multiply, Vect.size()); + for (int i=0; i<M1.size(); i++){ + multiply[i] = 0.0f; + for (int j=0; j<M1[i].size(); j++){ + multiply[i] += M1[i][j] * Vect[j]; + } + } + return multiply; +} + +//-------------------------------------------------------------- +template <typename T> +inline float getMeanVec(vector<T>& V){ + float tSum = 0.0f; + for (int n=0; n<V.size(); n++){ + tSum += V[n]; + } + return tSum / (float)V.size(); +} + +template <typename T> +inline vector<vector<float> > getRotationMatrix3d(T phi, T theta, T psi) +{ + vector< vector<float> > M; + initMat(M,3,3); + + M[0][0] = cos(theta)*cos(psi); + M[0][1] = -cos(phi)*sin(psi)+sin(phi)*sin(theta)*cos(psi); + M[0][2] = sin(phi)*sin(psi)+cos(phi)*sin(theta)*cos(psi); + + M[1][0] = cos(theta)*sin(psi); + M[1][1] = cos(phi)*cos(psi)+sin(phi)*sin(theta)*sin(psi); + M[1][2] = -sin(phi)*cos(psi)+cos(phi)*sin(theta)*sin(psi); + + M[2][0] = -sin(theta); + M[2][1] = sin(phi)*cos(theta); + M[2][2] = cos(phi)*cos(theta); + + return M; +} + +template <typename T> +float distance_weightedEuclidean(vector<T> x, vector<T> y, vector<T> w) +{ + int count = x.size(); + if (count <= 0) return 0; + float dist = 0.0; + for(int k = 0; k < count; k++) dist += w[k] * pow((x[k] - y[k]), 2); + return dist; +} + +////-------------------------------------------------------------- +//vector<vector<float> > getRotationMatrix3d(float phi, float theta, float psi) +//{ +// vector< vector<float> > M; +// initMat(M,3,3); +// +// M[0][0] = cos(theta)*cos(psi); +// M[0][1] = -cos(phi)*sin(psi)+sin(phi)*sin(theta)*cos(psi); +// M[0][2] = sin(phi)*sin(psi)+cos(phi)*sin(theta)*cos(psi); +// +// M[1][0] = cos(theta)*sin(psi); +// M[1][1] = cos(phi)*cos(psi)+sin(phi)*sin(theta)*sin(psi); +// M[1][2] = -sin(phi)*cos(psi)+cos(phi)*sin(theta)*sin(psi); +// +// M[2][0] = -sin(theta); +// M[2][1] = sin(phi)*cos(theta); +// M[2][2] = cos(phi)*cos(theta); +// +// return M; +//} + +//float distance_weightedEuclidean(vector<float> x, vector<float> y, vector<float> w) +//{ +// int count = x.size(); +// if (count <= 0) return 0; +// float dist = 0.0; +// for(int k = 0; k < count; k++) dist += w[k] * pow((x[k] - y[k]), 2); +// return dist; +//} + +#endif -- GitLab