/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

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