View Javadoc

1   /*
2    * @(#)$Id: Rubine3DAlgorithm.java
3    *
4    * Author		:	Arthur Vogels, arthur.vogels@gmail.com
5    *                  
6    *
7    * Purpose		:   3 Dimensional use of the Rubine algorithm.
8    *
9    * -----------------------------------------------------------------------
10   *
11   * Revision Information:
12   *
13   * Date				Who			Reason
14   *
15   * 05.01.2009		vogelsar	Initial Release
16   * 22.05.2010		bpuype		Code cleanup and bug fixes
17   *
18   * -----------------------------------------------------------------------
19   *
20   * Copyright 1999-2009 ETH Zurich. All Rights Reserved.
21   *
22   * This software is the proprietary information of ETH Zurich.
23   * Use is subject to license terms.
24   * 
25   */
26  
27  package org.ximtec.igesture.algorithm.rubine3d;
28  
29  import java.util.ArrayList;
30  import java.util.HashMap;
31  import java.util.Iterator;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.Vector;
35  import java.util.logging.Logger;
36  
37  import org.sigtec.ink.Note;
38  import org.sigtec.util.Constant;
39  import org.ximtec.igesture.Recogniser;
40  import org.ximtec.igesture.algorithm.AlgorithmException;
41  import org.ximtec.igesture.algorithm.SampleBasedAlgorithm;
42  import org.ximtec.igesture.algorithm.AlgorithmException.ExceptionType;
43  import org.ximtec.igesture.algorithm.feature.Feature;
44  import org.ximtec.igesture.algorithm.rubine.RubineAlgorithm;
45  import org.ximtec.igesture.algorithm.rubine.RubineConfiguration;
46  import org.ximtec.igesture.configuration.Configuration;
47  import org.ximtec.igesture.core.Gesture;
48  import org.ximtec.igesture.core.GestureClass;
49  import org.ximtec.igesture.core.GestureSample3D;
50  import org.ximtec.igesture.core.GestureSet;
51  import org.ximtec.igesture.core.Result;
52  import org.ximtec.igesture.core.ResultSet;
53  import org.ximtec.igesture.core.SampleDescriptor;
54  import org.ximtec.igesture.core.SampleDescriptor3D;
55  import org.ximtec.igesture.util.additions3d.RecordedGesture3D;
56  
57  public class Rubine3DAlgorithm extends SampleBasedAlgorithm/*implements Algorithm */{
58  
59  	private static final Logger LOGGER = Logger.getLogger(Rubine3DAlgorithm.class.getName());
60  	
61  	private Rubine3DConfiguration rubine3dConfig;
62  	
63  	private static final int PLANE_XY = 0;
64  	private static final int PLANE_YZ = 1;
65  	private static final int PLANE_ZX = 2;
66  
67  	private Map<GestureClass, GestureClass> gestureClassMapping;
68  	
69  	// Plane gesture sets
70  	private GestureSet setXY;
71  	private GestureSet setYZ;
72  	private GestureSet setZX;
73  
74  	/**
75  	 * Constructor
76  	 */
77  	public Rubine3DAlgorithm() {
78  		setXY = new GestureSet();
79  		setYZ = new GestureSet();
80  		setZX = new GestureSet();
81  		gestureClassMapping = new HashMap<GestureClass,GestureClass>();
82  	}
83  
84  	@Override
85  	public Enum<?>[] getConfigParameters() {
86  		return Rubine3DConfiguration.Config.values();
87  	}
88  
89  	@Override
90  	public String getDefaultParameterValue(String parameterName) {
91  		return Rubine3DConfiguration.getDefaultConfiguration().get(
92  				parameterName);
93  	}
94  
95  	@Override
96  	public void init(Configuration configuration) throws AlgorithmException {
97  		LOGGER.info("Rubine3DAlgorithm.init()");
98  		this.rubine3dConfig = new Rubine3DConfiguration(configuration);
99  		// Split all gesture sets up into planes
100 		Iterator<GestureSet> i = configuration.getGestureSets().iterator();
101 		while (i.hasNext()) {
102 			GestureSet tempSet = i.next();
103 			splitGestureSet(tempSet);
104 		}
105 	}
106 
107 	/**
108 	 * Recognizes a Gesture<?> if it is a GestureSample3D, otherwise it throws
109 	 * an AlgorithmException
110 	 */
111 	public ResultSet recognise(Gesture<?> gesture) throws AlgorithmException {
112 
113 		if (gesture instanceof GestureSample3D) {
114 			return this.recognise((GestureSample3D) gesture);
115 		} else {
116 			throw new AlgorithmException(ExceptionType.Recognition);
117 		}
118 	}
119 
120 	/**
121 	 * Recognizes a GestureSample3D by splitting it up into three 2D-planes (XY,
122 	 * YZ, ZX) and recognising these three 2D planes with the standard
123 	 * RubineAlgorithm. It returns a combined ResultSet, made up of the three
124 	 * ResultSets of the three planes, combined using weight factors.
125 	 * 
126 	 * @param gesture
127 	 *            The GestureSample3D to be recognized
128 	 * @return The ResultSet after recognising the gesture
129 	 * @throws AlgorithmException
130 	 */
131 	public ResultSet recognise(GestureSample3D gesture)
132 			throws AlgorithmException {
133 		// Split gesture into planes
134 		List<Gesture<Note>> planes = gesture.splitToPlanes();
135 		// Determine the weights of the planes
136 		List<Double> weights = determinePlaneWeights();
137 
138 		Configuration configXY = createConfiguration(PLANE_XY);
139 		Configuration configYZ = createConfiguration(PLANE_YZ);
140 		Configuration configZX = createConfiguration(PLANE_ZX);
141 		// Start recognition process
142 		Recogniser recogniserXY = new Recogniser(configXY);
143 		Recogniser recogniserYZ = new Recogniser(configYZ);
144 		Recogniser recogniserZX = new Recogniser(configZX);
145 		// Recognise and add a ResultSet to sets per plane
146 		List<ResultSet> sets = new ArrayList<ResultSet>();
147 		sets.add(recogniserXY.recognise(planes.get(0)));
148 		sets.add(recogniserYZ.recognise(planes.get(1)));
149 		sets.add(recogniserZX.recognise(planes.get(2)));
150 		// Combine sets to one ResultSet and return
151 		try {
152 			// return sets.get(0);
153 			ResultSet rs = combineResultSets(sets, weights); 
154 			List<Result> results = rs.getResults();
155 			for (Iterator iterator = results.iterator(); iterator.hasNext();) {
156 				Result result = (Result) iterator.next();
157 				result.setGestureClass(gestureClassMapping.get(result.getGestureClass()));
158 			}
159 			return rs; 
160 		} catch (Exception e) {
161 			e.printStackTrace();
162 		}
163 		return null;
164 	}
165 
166 	/**
167 	 * Creates a configuration for the recogniser, based on the plane name
168 	 * provided
169 	 * 
170 	 * @param plane
171 	 *            The name of the plane for which the configuration should be
172 	 *            created
173 	 * @return The configuration object for the recogniser of the plane
174 	 */
175 	private Configuration createConfiguration(int plane) {
176 		// Configuration objects
177 		Configuration recogniserConfig = new Configuration();
178 		RubineConfiguration rubineConfig = null;
179 
180 		// Include the Rubine Algorithm
181 		recogniserConfig.addAlgorithm(RubineAlgorithm.class.getName());
182 		// Check for which plane the configuration should be
183 		switch(plane)
184 		{
185 		case PLANE_XY:
186 			// Add Gesture Set
187 			recogniserConfig.addGestureSet(this.setXY);
188 			rubineConfig = rubine3dConfig.getXyConfiguration();
189 			break;
190 		case PLANE_YZ:
191 			// Add Gesture Set
192 			recogniserConfig.addGestureSet(this.setYZ);
193 			rubineConfig = rubine3dConfig.getYzConfiguration();
194 			break;
195 		case PLANE_ZX:
196 			// Add Gesture Set
197 			recogniserConfig.addGestureSet(this.setZX);
198 			rubineConfig = rubine3dConfig.getZxConfiguration();
199 			break;
200 		default:
201 			//LOGGER.severe("Rubine3DAlgorithm.createConfiguration(): Please provide a valid plane name.");
202 			//return null;
203 		}
204 
205 		// LOGGER.info("RUBINE CONFIG: " + rubineConfig);
206 
207 		// Put parameters from rubineConfig into recogniserConfig
208 		recogniserConfig.addParameter(RubineAlgorithm.class.getName(),
209 				RubineConfiguration.Config.MAHALANOBIS_DISTANCE.name(), String
210 						.valueOf(rubineConfig.getMahalanobisDistance()));
211 		recogniserConfig.addParameter(RubineAlgorithm.class.getName(),
212 				RubineConfiguration.Config.MIN_DISTANCE.name(), String
213 						.valueOf(rubineConfig.getMinDistance()));
214 		recogniserConfig.addParameter(RubineAlgorithm.class.getName(),
215 				RubineConfiguration.Config.PROBABILITY.name(), String
216 						.valueOf(rubineConfig.getProbability()));
217 		
218 		Feature[] features = rubineConfig.getFeatureList();
219 		StringBuilder sb = new StringBuilder();
220 		for (int i = 0; i < features.length; i++) {
221 			sb.append(features[i].getClass().getName());
222 			sb.append(Constant.COMMA);
223 		}
224 
225 		recogniserConfig.addParameter(RubineAlgorithm.class.getName(), 
226 				RubineConfiguration.Config.FEATURE_LIST.name(), sb.toString());
227 
228 		// Return the configuration
229 		return recogniserConfig;
230 	}
231 
232 	/**
233 	 * Determines the weights of the planes from the Rubine3DConfiguration
234 	 * 
235 	 * @return A list with 3 weights. The first for the XY plane, second for the
236 	 *         YZ plane and third for the ZX plane
237 	 * @throws Exception
238 	 */
239 	private List<Double> determinePlaneWeights() throws AlgorithmException {
240 		List<Double> weights = new Vector<Double>();
241 		// Check if weights add up to 1
242 		if ((rubine3dConfig.getXyWeight() + rubine3dConfig.getYzWeight() + rubine3dConfig
243 				.getZxWeight()) > 1.01
244 				|| (rubine3dConfig.getXyWeight() + rubine3dConfig.getYzWeight() + rubine3dConfig
245 						.getZxWeight()) < 0.99) {
246 			System.err.println("Weights do not add up to 1");
247 			throw new AlgorithmException(
248 					AlgorithmException.ExceptionType.Initialisation);
249 		}
250 		// Fill
251 		weights.add(rubine3dConfig.getXyWeight());
252 		weights.add(rubine3dConfig.getYzWeight());
253 		weights.add(rubine3dConfig.getZxWeight());
254 		// Return
255 		return weights;
256 	}
257 
258 	/**
259 	 * Combines multiple ResultSet objects into one, using weight factors.
260 	 * 
261 	 * @param sets
262 	 *            The ResultSets to be combined
263 	 * @param weights
264 	 *            The weights per ResultSet
265 	 * @return The combined ResultSet
266 	 * @throws Exception
267 	 *             When the number of resultsets does not match the number of
268 	 *             weights
269 	 */
270 	private ResultSet combineResultSets(List<ResultSet> sets,
271 			List<Double> weights) throws Exception {
272 		// First check if the number of resultsets matches the number of weights
273 		if (sets.size() != weights.size()) {
274 			throw new Exception(
275 					"Rubine3DAlgorithm.combineResultSets(): The number of ResultSets does not match the number of weights.");
276 		}
277 		// Create a Resultset with combined Results
278 		ResultSet returnSet = combine(sets, weights);
279 		// Sort the results in the resultset by accuracy
280 		returnSet = sortByAccuray(returnSet);
281 		// Return the set
282 		return returnSet;
283 	}
284 
285 	/**
286 	 * Combines Resultsets
287 	 * 
288 	 * @param sets
289 	 *            The ResultSets to be combined
290 	 * @param weights
291 	 *            The weights of the sets
292 	 * @param foundClasses
293 	 *            The GestureClasses that have been found in the sets before
294 	 * @return The combined ResultSet
295 	 */
296 	private ResultSet combine(List<ResultSet> sets, List<Double> weights) {
297 		// Multiply each plane result by its weight
298 		// XY plane
299 		for (int i = 0; i < sets.get(0).getResults().size(); i++) {
300 			sets.get(0).getResults().get(i).setAccuracy(
301 					sets.get(0).getResults().get(i).getAccuracy()
302 							* weights.get(0));
303 		}
304 		// YZ plane
305 		for (int i = 0; i < sets.get(1).getResults().size(); i++) {
306 			sets.get(1).getResults().get(i).setAccuracy(
307 					sets.get(1).getResults().get(i).getAccuracy()
308 							* weights.get(1));
309 		}
310 		// ZX plane
311 		for (int i = 0; i < sets.get(2).getResults().size(); i++) {
312 			sets.get(2).getResults().get(i).setAccuracy(
313 					sets.get(2).getResults().get(i).getAccuracy()
314 							* weights.get(2));
315 		}
316 		// Create return variable
317 		ResultSet returnSet = new ResultSet();
318 		// Add results up to make final resultset
319 		// XY plane
320 		returnSet = sets.get(0);
321 		// YZ plane
322 		// Loop through results
323 		for (int i = 0; i < sets.get(1).getResults().size(); i++) {
324 			// if the returnset already contains a result for this gesture class
325 			if (returnSet.contains(sets.get(1).getResult(i).getGestureClass())) {
326 				// Find the result with this gesture class
327 				for (int j = 0; j < returnSet.getResults().size(); j++) {
328 					if (returnSet.getResults().get(j).getGestureClass()
329 							.getName().equals(
330 									sets.get(1).getResult(i).getGestureClass()
331 											.getName())) {
332 						returnSet.getResults().get(j).setAccuracy(
333 								returnSet.getResults().get(j).getAccuracy()
334 										+ sets.get(1).getResult(i)
335 												.getAccuracy());
336 					}
337 				}
338 			} else { // Add a new result for this gesture class to the returnset
339 				returnSet.addResult(sets.get(1).getResult(i));
340 			}
341 		}
342 		// ZX plane
343 		// Loop through results
344 		for (int i = 0; i < sets.get(2).getResults().size(); i++) {
345 			// if the returnset already contains a result for this gesture class
346 			if (returnSet.contains(sets.get(2).getResult(i).getGestureClass())) {
347 				// Find the result with this gesture class
348 				for (int j = 0; j < returnSet.getResults().size(); j++) {
349 					if (returnSet.getResults().get(j).getGestureClass()
350 							.getName().equals(
351 									sets.get(2).getResult(i).getGestureClass()
352 											.getName())) {
353 						returnSet.getResults().get(j).setAccuracy(
354 								returnSet.getResults().get(j).getAccuracy()
355 										+ sets.get(2).getResult(i)
356 												.getAccuracy());
357 					}
358 				}
359 			} else { // Add a new result for this gesture class to the returnset
360 				returnSet.addResult(sets.get(2).getResult(i));
361 			}
362 		}
363 		// Make the accuracies in the set add up to 1
364 		double totalAccuracy = 0;
365 		for (int i = 0; i < returnSet.getResults().size(); i++) {
366 			totalAccuracy = totalAccuracy
367 					+ returnSet.getResult(i).getAccuracy();
368 		}
369 		// System.err.println("TOTAL ACCURACY: " + totalAccuracy);
370 		if (totalAccuracy != 0 && totalAccuracy < 1) {
371 			double factor = 1 / totalAccuracy;
372 			for (int i = 0; i < returnSet.getResults().size(); i++) {
373 				returnSet.getResults().get(i).setAccuracy(
374 						returnSet.getResults().get(i).getAccuracy() * factor);
375 			}
376 		}
377 		// Return the set
378 		return returnSet;
379 	}
380 
381 	/**
382 	 * Sorts the results in a ResultSet such that the result with the highest
383 	 * accuracy comes first
384 	 * 
385 	 * @param inputSet
386 	 *            The ResulSet that needs to be sorted by accuracy of the
387 	 *            Results it contains
388 	 * @return The sorted ResultSet
389 	 */
390 	private ResultSet sortByAccuray(ResultSet inputSet) {
391 		// Create output resultset and give it its first result
392 		ResultSet outputSet = new ResultSet();
393 		if (inputSet.getResults().size() > 0) {
394 			outputSet.addResult(inputSet.getResult(0));
395 			// Loop through inputSet and take accuracy
396 			for (int j = 0; j < inputSet.getResults().size(); j++) {
397 				boolean added = false;
398 				// Loop through output set and compare accuracies
399 				for (int k = 0; k < outputSet.getResults().size(); k++) {
400 					// If the accuracy is higher than the accuracy of the result
401 					// at
402 					// position k in outputset
403 					if (inputSet.getResult(j).getAccuracy() > outputSet
404 							.getResult(k).getAccuracy()) {
405 						// Insert the result and break the loop to prevent from
406 						// inserting multiple times
407 						outputSet.getResults().add(k, inputSet.getResult(j));
408 						added = true;
409 					}
410 				}
411 				// If not added because the accuracy is smaller than the
412 				// existing
413 				// ones, add at the end
414 				if (!added) {
415 					outputSet.getResults().add(inputSet.getResult(j));
416 				}
417 			}
418 		}
419 		// Return output variable
420 		return outputSet;
421 	}
422 
423 	/**
424 	 * Splits GestureSet inputSet (which contains 3D gestures) into three
425 	 * separate gesture sets (containing 2D gestures) for the three planes
426 	 * 
427 	 * @param inputSet
428 	 *            The gesture set with 3D gestures
429 	 */
430 	private void splitGestureSet(GestureSet inputSet) {
431 		// Set names
432 		setXY.setName(inputSet.getName());
433 		setYZ.setName(inputSet.getName());
434 		setZX.setName(inputSet.getName());
435 		// Iterate through gesture classes in set
436 		Iterator<GestureClass> classIter = inputSet.getGestureClasses()
437 				.iterator();
438 		while (classIter.hasNext()) {
439 			GestureClass tempClass = classIter.next();
440 			GestureClass classXY = new GestureClass(tempClass.getName());
441 			gestureClassMapping.put(classXY, tempClass);
442 			GestureClass classYZ = new GestureClass(tempClass.getName());
443 			gestureClassMapping.put(classYZ, tempClass);
444 			GestureClass classZX = new GestureClass(tempClass.getName());
445 			gestureClassMapping.put(classZX, tempClass);
446 			// If the gesture class contains a sample descriptor
447 			if (tempClass.getDescriptor(SampleDescriptor3D.class) != null) {		
448 				SampleDescriptor descXY = new SampleDescriptor();
449 				SampleDescriptor descYZ = new SampleDescriptor();
450 				SampleDescriptor descZX = new SampleDescriptor();
451 				// Iterate through samples
452 				Iterator<Gesture<RecordedGesture3D>> sampleIter = tempClass
453 						.getDescriptor(SampleDescriptor3D.class).getSamples()
454 						.iterator();
455 				while (sampleIter.hasNext()) {
456 					GestureSample3D tempSample = (GestureSample3D) sampleIter
457 							.next();
458 					// Split to planes
459 					List<Gesture<Note>> planes = tempSample.splitToPlanes();
460 					// Add each plane to its own descriptor
461 					descXY.addSample(planes.get(0));
462 					descYZ.addSample(planes.get(1));
463 					descZX.addSample(planes.get(2));
464 				}
465 				// Add descriptors to classes
466 				classXY.addDescriptor(descXY);
467 				classYZ.addDescriptor(descYZ);
468 				classZX.addDescriptor(descZX);
469 			}
470 			// Add classes to sets
471 			setXY.addGestureClass(classXY);
472 			setYZ.addGestureClass(classYZ);
473 			setZX.addGestureClass(classZX);
474 		}
475 
476 	}
477 
478 	/* (non-Javadoc)
479 	 * @see org.ximtec.igesture.algorithm.Algorithm#getType()
480 	 */
481 	@Override
482 	public int getType() {
483 		return org.ximtec.igesture.util.Constant.TYPE_3D;
484 	}
485 
486 }