/*
* wiigee - accelerometerbased gesture recognition
* Copyright (C) 2007, 2008, 2009 Benjamin Poppinga
*
* Developed at University of Oldenburg
* Contact: wiigee@benjaminpoppinga.de
*
* This file is part of wiigee.
*
* wiigee is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.wiigee.logic;
import java.util.Vector;
import org.wiigee.event.AccelerationEvent;
import org.wiigee.util.Log;
/**
* This class implements a quantization component. In this case a
* k-mean-algorithm is used. In this case the initial values of the algorithm
* are ordered as two intersected circles, representing an abstract globe with
* k=14 elements. As a special feature the radius of this globe would be
* calculated dynamically before the training of this component.
*
* @author Benjamin 'BePo' Poppinga
*/
public class Quantizer {
/** This is the initial radius of this model. */
private double radius;
/** Number of states from the following Hidden Markov Model */
private int numStates;
/** The representation of the so called Centeroids */
private double[][] map;
/** True, if map is already trained. */
private boolean maptrained;
/**
* Initialize a empty quantizer. The states variable is necessary since some
* algorithms need this value to calculate their values correctly.
*
* @param numStates
* number of hidden markov model states
*/
public Quantizer(int numStates) {
this.numStates = numStates;
this.map = new double[14][3];
this.maptrained = false;
}
/**
* Trains this Quantizer with a specific gesture. This means that the
* positions of the centeroids would adapt to this training gesture. In our
* case this would happen with a summarized virtual gesture, containing all
* the other gestures.
*
* @param gesture
* the summarized virtual gesture
*/
public void trainCenteroids(Gesture gesture) {
Vector<AccelerationEvent> data = gesture.getData();
double pi = Math.PI;
this.radius = (gesture.getMaxAcceleration() + gesture
.getMinAcceleration()) / 2;
Log.write("Using radius: " + this.radius);
// x , z , y
if (!this.maptrained) {
this.maptrained = true;
this.map[0] = new double[] { this.radius, 0.0, 0.0 };
this.map[1] = new double[] { Math.cos(pi / 4) * this.radius, 0.0,
Math.sin(pi / 4) * this.radius };
this.map[2] = new double[] { 0.0, 0.0, this.radius };
this.map[3] = new double[] { Math.cos(pi * 3 / 4) * this.radius,
0.0, Math.sin(pi * 3 / 4) * this.radius };
this.map[4] = new double[] { -this.radius, 0.0, 0.0 };
this.map[5] = new double[] { Math.cos(pi * 5 / 4) * this.radius,
0.0, Math.sin(pi * 5 / 4) * this.radius };
this.map[6] = new double[] { 0.0, 0.0, -this.radius };
this.map[7] = new double[] { Math.cos(pi * 7 / 4) * this.radius,
0.0, Math.sin(pi * 7 / 4) * this.radius };
this.map[8] = new double[] { 0.0, this.radius, 0.0 };
this.map[9] = new double[] { 0.0, Math.cos(pi / 4) * this.radius,
Math.sin(pi / 4) * this.radius };
this.map[10] = new double[] { 0.0,
Math.cos(pi * 3 / 4) * this.radius,
Math.sin(pi * 3 / 4) * this.radius };
this.map[11] = new double[] { 0.0, -this.radius, 0.0 };
this.map[12] = new double[] { 0.0,
Math.cos(pi * 5 / 4) * this.radius,
Math.sin(pi * 5 / 4) * this.radius };
this.map[13] = new double[] { 0.0,
Math.cos(pi * 7 / 4) * this.radius,
Math.sin(pi * 7 / 4) * this.radius };
}
int[][] g_alt = new int[this.map.length][data.size()];
int[][] g = new int[this.map.length][data.size()];
do {
// Derive new Groups...
g_alt = this.copyarray(g);
g = this.deriveGroups(gesture);
// calculate new centeroids
for (int i = 0; i < this.map.length; i++) {
double zaehlerX = 0;
double zaehlerY = 0;
double zaehlerZ = 0;
int nenner = 0;
for (int j = 0; j < data.size(); j++) {
if (g[i][j] == 1) {
zaehlerX += data.elementAt(j).getX();
zaehlerY += data.elementAt(j).getY();
zaehlerZ += data.elementAt(j).getZ();
nenner++;
}
}
if (nenner > 1) { // nur wenn der nenner>0 oder >1??? ist muss
// was
// geaendert werden
// Log.write("Setze neuen Centeroid!");
this.map[i] = new double[] {(zaehlerX / (double) nenner),
(zaehlerY / (double) nenner),
(zaehlerZ / (double) nenner) };
// Log.write("Centeroid: "+i+": "+newcenteroid[0]+":"+newcenteroid[1]);
}
} // new centeroids
} while (!equalarrays(g_alt, g));
// Debug: Printout groups
/*
* for (int i = 0; i < n; i++) { for (int j = 0; j < this.data.size();
* j++) { Log.write(g[i][j] + "|"); } Log.write(""); }
*/
}
/**
* This methods looks up a Gesture to a group matrix, used by the
* k-mean-algorithm (traincenteroid method) above.
*
* @param gesture
* the gesture
*/
public int[][] deriveGroups(Gesture gesture) {
Vector<AccelerationEvent> data = gesture.getData();
int[][] groups = new int[this.map.length][data.size()];
// Calculate cartesian distance
double[][] d = new double[this.map.length][data.size()];
double[] curr = new double[3];
double[] vector = new double[3];
for (int i = 0; i < this.map.length; i++) { // zeilen
double[] ref = this.map[i];
for (int j = 0; j < data.size(); j++) { // spalten
curr[0] = data.elementAt(j).getX();
curr[1] = data.elementAt(j).getY();
curr[2] = data.elementAt(j).getZ();
vector[0] = ref[0] - curr[0];
vector[1] = ref[1] - curr[1];
vector[2] = ref[2] - curr[2];
d[i][j] = Math.sqrt((vector[0] * vector[0])
+ (vector[1] * vector[1]) + (vector[2] * vector[2]));
// Log.write(d[i][j] + "|");
}
// Log.write("");
}
// look, to which group a value belongs
for (int j = 0; j < data.size(); j++) {
double smallest = Double.MAX_VALUE;
int row = 0;
for (int i = 0; i < this.map.length; i++) {
if (d[i][j] < smallest) {
smallest = d[i][j];
row = i;
}
groups[i][j] = 0;
}
groups[row][j] = 1; // guppe gesetzt
}
// Debug output
/*
* for (int i = 0; i < groups.length; i++) { // zeilen for (int j = 0; j
* < groups[i].length; j++) { Log.write(groups[i][j] + "|"); }
* Log.write(""); }
*/
return groups;
}
/**
* With this method you can transform a gesture to a discrete symbol
* sequence with values between 0 and granularity (number of observations).
*
* @param gesture
* Gesture to get the observationsequence to.
*/
public int[] getObservationSequence(Gesture gesture) {
int[][] groups = this.deriveGroups(gesture);
Vector<Integer> sequence = new Vector<Integer>();
// Log.write("Visible symbol sequence: ");
for (int j = 0; j < groups[0].length; j++) { // spalten
for (int i = 0; i < groups.length; i++) { // zeilen
if (groups[i][j] == 1) {
// Log.write(" "+ i);
sequence.add(i);
break;
}
}
}
// die sequenz darf nicht zu kurz sein... mindestens so lang
// wie die anzahl der zustände. weil sonst die formeln nicht klappen.
// english: this is very dirty! it have to be here because if not
// too short sequences would cause an error. i've to think about a
// better resolution than copying the old value a few time.
while (sequence.size() < this.numStates) {
sequence.add(sequence.elementAt(sequence.size() - 1));
// Log.write(" "+sequence.elementAt(sequence.size()-1));
}
// Log.write("");
int[] out = new int[sequence.size()];
for (int i = 0; i < sequence.size(); i++) {
out[i] = sequence.elementAt(i);
}
return out;
}
/**
* Prints out the current centeroids-map. Its for debug or technical
* interests.
*/
public void printMap() {
Log.write(Log.DEBUG, "Centeroids:", this);
for (int i = 0; i < this.map.length; i++) {
Log.write(Log.DEBUG, i + ". :" + this.map[i][0] + ":"
+ this.map[i][1] + ":" + this.map[i][2], this);
}
}
/**
* Function to deepcopy an array.
*/
private int[][] copyarray(int[][] alt) {
int[][] neu = new int[alt.length][alt[0].length];
for (int i = 0; i < alt.length; i++) {
for (int j = 0; j < alt[i].length; j++) {
neu[i][j] = alt[i][j];
}
}
return neu;
}
/**
* Function to look if the two arrays containing the same values.
*/
private boolean equalarrays(int[][] one, int[][] two) {
for (int i = 0; i < one.length; i++) {
for (int j = 0; j < one[i].length; j++) {
if (!(one[i][j] == two[i][j])) {
return false;
}
}
}
return true;
}
public double getRadius() {
return this.radius;
}
public double[][] getHashMap() {
return this.map;
}
public void setUpManually(double[][] map, double radius) {
this.map = map;
this.radius = radius;
}
}