PageRenderTime 66ms CodeModel.GetById 39ms app.highlight 22ms RepoModel.GetById 1ms app.codeStats 0ms

/src/org/mt4j/input/inputProcessors/componentProcessors/unistrokeProcessor/UnistrokeUtils.java

http://mt4j.googlecode.com/
Java | 612 lines | 317 code | 98 blank | 197 comment | 33 complexity | abb1640277b4abdb95007f44637d119f MD5 | raw file
  1package org.mt4j.input.inputProcessors.componentProcessors.unistrokeProcessor;
  2
  3import java.util.ArrayList;
  4import java.util.List;
  5import java.util.Stack;
  6
  7
  8import org.mt4j.input.inputProcessors.componentProcessors.unistrokeProcessor.UnistrokeTemplates.Template;
  9import org.mt4j.util.math.Vector3D;
 10
 11/**
 12 * The Class MTDollarUtils, all calculations for the Gesture Recognizer
 13 * Based on the Code from 
 14 * http://depts.washington.edu/aimgroup/proj/dollar/
 15 * http://www.openprocessing.org/visuals/?visualID=600
 16 */
 17public class UnistrokeUtils {
 18	
 19	/** The Infinity Constant. */
 20	private final float Infinity = 1e9f;
 21	
 22	/** The Number of points after resampling */
 23	private final int NumPoints = 128;
 24	
 25	/** The Square size. */
 26	private final float SquareSize = 250;
 27	
 28	/** The Half diagonal. */
 29	private final float HalfDiagonal = (float)(0.5 * Math.sqrt(250.0 * 250.0 + 250.0 * 250.0));
 30	
 31	/** The Angle range. */
 32	private final float AngleRange = 45;
 33	
 34	/** The Angle precision. */
 35	private final float AnglePrecision = 2;
 36	
 37	/** The Phi Constant (Golden Ratio) */
 38	private final float Phi = (float)(0.5 * (-1.0 + Math.sqrt(5.0))); // Golden Ratio
 39	
 40	/** The recognizer. */
 41	private final Recognizer recognizer;
 42	
 43	/** The thisclass. */
 44	private final UnistrokeUtils thisclass;
 45
 46	/**
 47	 * Instantiates a new mT dollar utils.
 48	 */
 49	public UnistrokeUtils () {
 50		this.thisclass = this;
 51		this.recognizer = new Recognizer();
 52
 53	}
 54
 55
 56
 57	/**
 58	 * The Enum Direction.
 59	 */
 60	public enum Direction {
 61		
 62			/** The CLOCKWISE. */
 63			CLOCKWISE, 
 64		 /** The COUNTERCLOCKWISE. */
 65		 COUNTERCLOCKWISE;
 66	}
 67
 68	/**
 69	 * The Enum DollarGesture.
 70	 */
 71	public enum UnistrokeGesture {
 72		
 73		/** The TRIANGLE. */
 74		TRIANGLE, 
 75 /** The X. */
 76 X, 
 77 /** The RECTANGLE. */
 78 RECTANGLE, 
 79 /** The CIRCLE. */
 80 CIRCLE, 
 81 /** The CHECK. */
 82 CHECK, 
 83 /** The CARET. */
 84 CARET, 
 85 /** The QUESTION. */
 86 QUESTION, 
 87 /** The ARROW. */
 88 ARROW, 
 89 /** The LEFTSQUAREBRACKET. */
 90 LEFTSQUAREBRACKET, 
 91 /** The RIGHTSQUAREBRACKET. */
 92 RIGHTSQUAREBRACKET, 
 93 /** The V. */
 94 V, 
 95 /** The DELETE. */
 96 DELETE, 
 97 /** The LEFTCURLYBRACE. */
 98 LEFTCURLYBRACE, 
 99 /** The RIGHTCURLYBRACE. */
100 RIGHTCURLYBRACE, 
101 /** The STAR. */
102 STAR, 
103 /** The PIGTAIL. */
104 PIGTAIL, 
105 /** The NOGESTURE. */
106 NOGESTURE, 
107 /** The CUSTOMGESTURE. */
108 CUSTOMGESTURE,
109 PACKAGE;
110	}
111
112	/**
113	 * The Class Rectangle.
114	 */
115	class Rectangle
116	{
117	  
118  	/** The X. */
119  	float X;
120	  
121  	/** The Y. */
122  	float Y;
123	  
124  	/** The Width. */
125  	float Width;
126	  
127  	/** The Height. */
128  	float Height;
129	  
130  	/**
131  	 * Instantiates a new rectangle.
132  	 * 
133  	 * @param x the x value
134  	 * @param y the y value
135  	 * @param width the width
136  	 * @param height the height
137  	 */
138  	Rectangle( float x, float y, float width, float height)
139	  {
140	    X = x;
141	    Y = y;
142	    Width = width;
143	    Height = height;
144	  }
145	}
146
147	/**
148	 * Gets the recognizer.
149	 * 
150	 * @return the recognizer
151	 */
152	public Recognizer getRecognizer() {
153		return recognizer;
154	}
155
156	public Recorder getRecorder() {
157		return new Recorder();
158	}
159	
160	public class Recorder {
161		
162		public Recorder(){
163			
164		}
165		
166		public void record(List<Vector3D> points) {
167			points = Resample(points, 64, Direction.CLOCKWISE);
168
169			
170			System.out.println("Begin Gesture");
171			int position = 1;
172			for (Vector3D point: points) {
173				if (position < 4) {
174					position++;
175					System.out.print("new Vector3D(" + (int)point.getX() + "," + (int)point.getY() + "),");
176				} else {
177					position = 1;
178					System.out.println("new Vector3D(" + (int)point.getX() + "," + (int)point.getY() + "),");
179				}
180				
181				
182			}
183			System.out.println("End Gesture");
184		}
185		
186	}
187	
188	
189	/**
190	 * The Class Recognizer.
191	 */
192	public class Recognizer {
193		
194		/** The List of registered Templates. */
195		List<Template> Templates = new ArrayList<Template>();
196		
197		/** The available dollar template class. */
198		UnistrokeTemplates dollarTemplates;
199
200		/**
201		 * Instantiates a new recognizer.
202		 */
203		public Recognizer() {
204			dollarTemplates = new UnistrokeTemplates(Templates, thisclass);
205
206		}
207
208		/**
209		 * Adds a template.
210		 * 
211		 * @param gesture the gesture
212		 * @param direction the direction
213		 */
214		public void addTemplate(UnistrokeGesture gesture, Direction direction) {
215			dollarTemplates.addTemplate(gesture, direction);
216		}
217		
218
219		/**
220		 * Recognize.
221		 * 
222		 * @param points the points
223		 * @return the dollar gesture
224		 */
225		UnistrokeGesture Recognize(List<Vector3D> points) {
226				points = Resample(points, getNumPoints(), Direction.CLOCKWISE);
227			points = RotateToZero(points);
228			points = ScaleToSquare(points, getSquareSize());
229			points = TranslateToOrigin(points);
230			float best = getInfinity();
231			float sndBest = getInfinity();
232			UnistrokeGesture g = null;
233			Direction di = null;
234
235			for (Template template : Templates) {
236				float d = DistanceAtBestAngle(points, template, -getAngleRange(), getAngleRange(), getAnglePrecision());
237				if (d < best) {
238					sndBest = best;
239					best = d;
240					g = template.gesture;
241					di = template.direction;
242				} else if (d < sndBest) {
243					sndBest = d;
244				}
245			}
246			float score = 1.0f - (best / getHalfDiagonal());
247			float otherScore = 1.0f - (sndBest / getHalfDiagonal());
248			float ratio = otherScore / score;
249
250			System.out.println("Gesture recognition score: " + score);
251			if (g != null && score > 0.7) {
252				
253				return g;
254			} else {
255				
256				return UnistrokeGesture.NOGESTURE;
257			}
258		}
259
260	}
261	
262	/**
263	 * Resample the points so they are evenly distributed
264	 * 
265	 * @param points the points before resampling
266	 * @param n the number of points after resampling
267	 * @param dir the direction
268	 * @return the resampled list of points
269	 */
270	List<Vector3D> Resample(List<Vector3D> points, int n, Direction dir)
271	{
272	   float I = PathLength(points) / ( (float)n -1.0f );
273	   float D = 0.0f;
274	   List<Vector3D> newpoints = new ArrayList<Vector3D>();
275	   Stack<Vector3D> stack = new Stack<Vector3D>();
276
277
278
279	   if (dir == Direction.CLOCKWISE) {
280		   for(Vector3D point: points)
281		   {
282		     stack.insertElementAt(point, 0);
283		   }
284	   } else {
285		   for(Vector3D point: points)
286		   {
287		     stack.push(point);
288		   }
289	   }
290
291	   while( !stack.isEmpty())
292	   {
293	       Vector3D pt1 = stack.pop();
294
295	       if( stack.isEmpty())
296	       {
297	         newpoints.add(pt1);
298	         continue;
299	       }
300	       Vector3D pt2 = stack.peek();
301	       float d = pt1.distance2D(pt2);
302	       if( (D + d) >= I)
303	       {
304	          float qx = pt1.getX() + (( I - D ) / d ) * (pt2.getX() - pt1.getX());
305	          float qy = pt1.getY() + (( I - D ) / d ) * (pt2.getY() - pt1.getY());
306	          Vector3D q = new Vector3D( qx, qy);
307	          newpoints.add(q);
308	          stack.push( q );
309	          D = 0.0f;
310	       } else {
311	         D += d;
312	       }
313	   }
314
315	   if( newpoints.size() == (n -1) )
316	   {
317	     newpoints.add(points.get(points.size() - 1));
318	   }
319	   return newpoints;
320
321	}
322
323
324	/**
325	 * Scale to square.
326	 * 
327	 * @param points the points
328	 * @param sz the size
329	 * @return the modified list
330	 */
331	List<Vector3D> ScaleToSquare( List<Vector3D> points, float sz)
332	{
333	    Rectangle B = BoundingBox( points );
334	    List<Vector3D> newpoints = new ArrayList<Vector3D>();
335	    for(Vector3D point: points)
336	    {
337	       float qx = point.getX() * (sz / B.Width);
338	       float qy = point.getY() * (sz / B.Height);
339	       newpoints.add(new Vector3D(qx, qy));
340	    }
341	    return  newpoints;
342	}
343
344	/**
345	 * Distance at best angle.
346	 * 
347	 * @param points the points
348	 * @param T the Template to compare to
349	 * @param a the Theta a
350	 * @param b the Theta b
351	 * @param threshold the threshold
352	 * @return the Distance at best Angle
353	 */
354	float DistanceAtBestAngle( List<Vector3D> points, Template T, float a, float b, float threshold)
355	{
356	   float x1 = Phi * a + (1 - Phi) * b;
357	   float f1 = DistanceAtAngle(points, T, x1);
358	   float x2 = (1 - Phi) * a + Phi * b;
359	   float f2 = DistanceAtAngle(points, T, x2);
360	   while( Math.abs( b - a ) > threshold)
361	   {
362	     if( f1 < f2 )
363	     {
364	       b = x2;
365	       x2 = x1;
366	       f2 = f1;
367	       x1 = Phi * a + (1.0f - Phi) * b;
368	       f1 = DistanceAtAngle(points, T, x1);
369	     }
370	     else
371	     {
372	       a = x1;
373	       x1 = x2;
374	       f1 = f2;
375	       x2 = (1.0f - Phi) * a + Phi * b;
376	       f2 = DistanceAtAngle(points, T, x2);
377	     }
378	   }
379	   return Math.min(f1, f2);
380	}
381
382
383	/**
384	 * Distance at angle.
385	 * 
386	 * @param points the points
387	 * @param T the t
388	 * @param theta the angle theta
389	 * @return the distance at angle theta
390	 */
391	float DistanceAtAngle( List<Vector3D> points, Template T, float theta)
392	{
393	  RotateBy( points, theta);
394	  return PathDistance( points, T.Points);
395	}
396
397
398	/**
399	 * Translate to origin.
400	 * 
401	 * @param points the points
402	 * @return the translated points
403	 */
404	List<Vector3D> TranslateToOrigin( List<Vector3D> points)
405	{
406	   Vector3D c = Centroid( points);
407	   List<Vector3D> newpoints = new ArrayList<Vector3D>();
408	   for(Vector3D point: points)
409	   {
410	     float qx = point.getX() - c.getX();
411	     float qy = point.getY() - c.getY();
412	     newpoints.add(new Vector3D(qx, qy));
413	   }
414	   return newpoints;
415	}
416
417
418
419
420
421	/**
422	 * Path length.
423	 * 
424	 * @param points the points
425	 * @return the path length
426	 */
427	private float PathLength (List<Vector3D> points) {
428		float length = 0;
429		Vector3D lastPosition = null;
430		for (Vector3D v: points) {
431			if (lastPosition == null) lastPosition = v;
432			length += v.distance2D(lastPosition);
433			lastPosition = v;
434		}
435
436		return length;
437	}
438
439	/**
440	 * Path distance.
441	 * 
442	 * @param pts1 the first set of points
443	 * @param pts2 the second set of points
444	 * @return the Path distance
445	 */
446	float PathDistance( List<Vector3D> pts1, List<Vector3D> pts2)
447	{
448	   if( pts1.size() != pts2.size())
449	   {
450	     
451	     return Infinity;
452	   }
453	   float d = 0.0f;
454	   for( int i = 0; i < pts1.size(); i++)
455	   {
456	     d += pts1.get(i).distance2D( pts2.get(i));
457	   }
458	   return d / (float)pts1.size();
459	}
460	
461	/**
462	 * Bounding box.
463	 * 
464	 * @param points the points inside the Bounding Box
465	 * @return the rectangle
466	 */
467	Rectangle BoundingBox( List<Vector3D> points)
468	{
469	  float minX = Infinity;
470	  float maxX = -Infinity;
471	  float minY = Infinity;
472	  float maxY = -Infinity;
473
474	  for(Vector3D point: points)
475	  {
476	    minX = Math.min( point.getX(), minX);
477	    maxX = Math.max( point.getX(), maxX);
478	    minY = Math.min( point.getY(), minY);
479	    maxY = Math.max( point.getY(), maxY);
480	  }
481	  return new Rectangle( minX, minY, maxX - minX, maxY - minY);
482	}
483
484
485
486	/**
487	 * Centroid.
488	 * 
489	 * @param points the points
490	 * @return the Centroid
491	 */
492	Vector3D Centroid(List<Vector3D> points)
493	{
494	  Vector3D centroid = new Vector3D(0, 0);
495	  for(Vector3D point: points)
496	  {
497		  centroid.setX(centroid.getX() + point.getX());
498		  centroid.setY(centroid.getY() + point.getY());
499	  }
500	  centroid.setX(centroid.getX() / points.size());
501	  centroid.setY(centroid.getY() / points.size());
502	  return centroid;
503	}
504
505	/**
506	 * Rotate by.
507	 * 
508	 * @param points the points
509	 * @param theta the theta
510	 * @return rotated list of points
511	 */
512	List<Vector3D>  RotateBy( List<Vector3D> points, float theta)
513	{
514	   Vector3D c = Centroid( points );
515	   float Cos = (float)Math.cos( theta );
516	   float Sin = (float)Math.sin( theta );
517
518	   List<Vector3D> newpoints = new ArrayList<Vector3D>();
519	   for(Vector3D point: points)
520	   {
521	     float qx = (point.getX() - c.getX()) * Cos - (point.getY() - c.getY()) * Sin + c.getX();
522	     float qy = (point.getX() - c.getX()) * Sin + (point.getY() - c.getY()) * Cos + c.getY();
523	     newpoints.add(new Vector3D( qx, qy ));
524	   }
525	   return newpoints;
526	}
527
528	/**
529	 * Rotate to zero.
530	 * 
531	 * @param points the points
532	 * @return rotated list of points
533	 */
534	List<Vector3D> RotateToZero( List<Vector3D>  points)
535	{
536	   //FIXME: Check for empty list
537		Vector3D c = Centroid( points );
538	   float theta = (float)Math.atan2( c.getY() - points.get(0).getY(), c.getX() - points.get(0).getX());
539	   return RotateBy( points, -theta);
540
541	}
542
543
544	/**
545	 * Gets the infinity.
546	 * 
547	 * @return the infinity
548	 */
549	public float getInfinity() {
550		return Infinity;
551	}
552
553
554	/**
555	 * Gets the num points.
556	 * 
557	 * @return the num points
558	 */
559	public int getNumPoints() {
560		return NumPoints;
561	}
562
563
564	/**
565	 * Gets the square size.
566	 * 
567	 * @return the square size
568	 */
569	public float getSquareSize() {
570		return SquareSize;
571	}
572
573
574	/**
575	 * Gets the half diagonal.
576	 * 
577	 * @return the half diagonal
578	 */
579	public float getHalfDiagonal() {
580		return HalfDiagonal;
581	}
582
583
584	/**
585	 * Gets the angle range.
586	 * 
587	 * @return the angle range
588	 */
589	public float getAngleRange() {
590		return AngleRange;
591	}
592
593
594	/**
595	 * Gets the angle precision.
596	 * 
597	 * @return the angle precision
598	 */
599	public float getAnglePrecision() {
600		return AnglePrecision;
601	}
602
603
604	/**
605	 * Gets the phi.
606	 * 
607	 * @return the phi
608	 */
609	public float getPhi() {
610		return Phi;
611	}
612}