Bläddra i källkod

Added hand tracker and implemented it in the test application.

Taddeus Kroes 13 år sedan
förälder
incheckning
cc772060ab
3 ändrade filer med 181 tillägg och 34 borttagningar
  1. 2 0
      src/trackers/__init__.py
  2. 127 0
      src/trackers/hand.py
  3. 52 34
      tests/testapp.py

+ 2 - 0
src/trackers/__init__.py

@@ -1,6 +1,7 @@
 from basic import BasicEventTracker
 from tap import TapTracker
 from transform import TransformationTracker
+from hand import HandTracker
 
 
 # Map of gesture type to tracker type
@@ -31,3 +32,4 @@ def create_tracker(gesture_type, widget):
 _register_tracker(BasicEventTracker)
 _register_tracker(TapTracker)
 _register_tracker(TransformationTracker)
+_register_tracker(HandTracker)

+ 127 - 0
src/trackers/hand.py

@@ -0,0 +1,127 @@
+from ..tracker import GestureTracker, Gesture
+
+
+class HandGesture(Gesture):
+    def __init__(self, hand, finger):
+        self.hand = hand
+        self.finger = finger
+
+    def get_hand(self):
+        return self.hand
+
+    def get_finger(self):
+        return self.finger
+
+    def is_first(self):
+        return len(self.hand) == 1
+
+    def is_last(self):
+        return not len(self.hand)
+
+
+class FingerDownGesture(HandGesture):
+    _type = 'finger_down'
+
+
+class FingerMoveGesture(HandGesture):
+    _type = 'finger_move'
+
+
+class FingerUpGesture(HandGesture):
+    _type = 'finger_up'
+
+
+class Hand(object):
+    def __init__(self):
+        self.fingers = []
+
+    def __len__(self):
+        return len(self.fingers)
+
+    def __iter__(self):
+        return iter(self.fingers)
+
+    def __str__(self):
+        return '<Hand fingers=%s centroid=%s>' \
+               % (self.fingers, self.get_centroid())
+
+    def __repr__(self):
+        return str(self)
+
+    def contains(self, finger, max_distance):
+        for other_finger in self.fingers:
+            if other_finger.distance_to(finger) <= max_distance:
+                return True
+
+        return False
+
+    def add_finger(self, finger):
+        self.fingers.append(finger)
+
+    def remove_finger(self, finger):
+        self.fingers.remove(finger)
+
+    def get_centroid(self):
+        l = len(self.fingers)
+        coords = [f.get_position() for f in self.fingers]
+        all_x, all_y = zip(*coords)
+        return sum(all_x) / l, sum(all_y) / l
+
+
+class HandTracker(GestureTracker):
+    supported_gestures = [FingerDownGesture, FingerMoveGesture,
+                          FingerUpGesture]
+
+    configurable = ['max_finger_distance']
+
+    def __init__(self, area):
+        super(HandTracker, self).__init__(area)
+
+        # Map of finger id's to corresponding hand objects
+        self.finger_hands = {}
+
+        # All hands being tracked
+        self.hands = []
+
+        # Maximum distance between two fingers to be assigned to the same hand
+        self.max_finger_distance = 400
+
+    def find_hand(self, finger):
+        for hand in self.hands:
+            if hand.contains(finger, self.max_finger_distance):
+                return hand
+
+        self.hands.append(Hand())
+        return self.hands[-1]
+
+    def on_point_down(self, event):
+        finger = event.get_touch_object()
+        hand = self.find_hand(finger)
+        hand.add_finger(finger)
+        self.finger_hands[finger.get_id()] = hand
+        self.trigger(FingerDownGesture(hand, finger))
+
+    def on_point_move(self, event):
+        finger = event.get_touch_object()
+
+        if finger.get_id() not in self.finger_hands:
+            return
+
+        hand = self.finger_hands[finger.get_id()]
+        self.trigger(FingerMoveGesture(hand, finger))
+
+    def on_point_up(self, event):
+        finger = event.get_touch_object()
+        finger_id = finger.get_id()
+
+        if finger_id not in self.finger_hands:
+            return
+
+        hand = self.finger_hands[finger_id]
+        del self.finger_hands[finger_id]
+        hand.remove_finger(finger)
+
+        if not len(hand):
+            self.hands.remove(hand)
+
+        self.trigger(FingerUpGesture(hand, finger))

+ 52 - 34
tests/testapp.py

@@ -88,7 +88,7 @@ class Polygon(BoundingBoxArea):
 
 
 fullscreen = False
-draw_bounding_boxes = draw_touch_points = True
+draw_bounding_boxes = draw_touch_objects = True
 W, H = mt.screen.screen_size
 
 
@@ -107,8 +107,8 @@ def create_context_window(w, h, callback):
         refresh()
 
     def handle_key(win, event):
-        """Handle key event. 'f' toggles fullscreen, 'q' exits the program, 'b'
-        toggles bounding boxes, 'p' toggles touch points."""
+        """Handle key event. 'f' toggles fullscreen, 'b' toggles bounding
+        boxes, 'i' toggles input points, 'q' exits the program."""
         if event.keyval >= 256:
             return
 
@@ -118,16 +118,16 @@ def create_context_window(w, h, callback):
             global fullscreen
             (win.unfullscreen if fullscreen else win.fullscreen)()
             fullscreen = not fullscreen
-        elif key == 'q':
-            quit()
         elif key == 'b':
             global draw_bounding_boxes
             draw_bounding_boxes = not draw_bounding_boxes
             refresh()
-        elif key == 'p':
-            global draw_touch_points
-            draw_touch_points = not draw_touch_points
+        elif key == 'i':
+            global draw_touch_objects
+            draw_touch_objects = not draw_touch_objects
             refresh()
+        elif key == 'q':
+            quit()
 
     # Root area (will be synchronized with GTK window)
     global root, overlay
@@ -156,7 +156,7 @@ def create_context_window(w, h, callback):
     window.show()
 
 
-def draw(*args):
+def draw():
     if not cr:
         return
 
@@ -171,26 +171,44 @@ def draw(*args):
         obj.draw(cr)
         cr.restore()
 
-    if draw_touch_points:
+    if draw_touch_objects:
         ox, oy = root.get_position()
         cr.set_source_rgb(*WHITE)
 
-        for x, y in touch_points.itervalues():
-            x -= ox
-            y -= oy
+        for hand in touch_hands:
+            cx, cy = hand.get_centroid()
 
-            cr.set_line_width(3)
-            cr.arc(x, y, 20, 0, 2 * pi)
-            cr.stroke()
+            # Filled centroid circle
+            if len(hand) > 1:
+                cr.arc(cx - ox, cy - oy, 20, 0, 2 * pi)
+                cr.fill()
+
+            for x, y in hand:
+                x -= ox
+                y -= oy
+
+                # Circle outline
+                cr.set_line_width(3)
+                cr.arc(x, y, 20, 0, 2 * pi)
+                cr.stroke()
+
+                # Line to centroid
+                if len(hand) > 1:
+                    cr.move_to(x, y)
+                    cr.line_to(cx - ox, cy - oy)
+                    cr.set_line_width(2)
+                    cr.stroke()
+
+                # Cross
+                cr.set_line_width(1)
+                cr.move_to(x - 8, y)
+                cr.line_to(x + 8, y)
+                cr.move_to(x, y - 8)
+                cr.line_to(x, y + 8)
+                cr.stroke()
 
-            cr.set_line_width(1)
-            cr.move_to(x - 8, y)
-            cr.line_to(x + 8, y)
-            cr.move_to(x, y - 8)
-            cr.line_to(x, y + 8)
-            cr.stroke()
 
-def refresh():
+def refresh(*args):
     window.queue_draw()
 
 
@@ -198,10 +216,10 @@ def quit(*args):
     gtk.main_quit()
 
 
-# Initialization
+# Global variables
 window = cr = root = overlay = None
 draw_objects = []
-touch_points = {}
+touch_hands = []
 
 
 def triangle_height(width):
@@ -231,22 +249,22 @@ def on_show(window):
 
     # Overlay catches basic events
     def handle_down(gesture):
-        point = gesture.get_event().get_touch_object()
-        touch_points[point.get_id()] = point.get_position()
+        if gesture.is_first():
+            touch_hands.append(gesture.get_hand())
 
-        if draw_touch_points:
+        if draw_touch_objects:
             refresh()
 
     def handle_up(gesture):
-        point = gesture.get_event().get_touch_object()
-        del touch_points[point.get_id()]
+        if gesture.is_last():
+            touch_hands.remove(gesture.get_hand())
 
-        if draw_touch_points:
+        if draw_touch_objects:
             refresh()
 
-    overlay.on_point_down(handle_down)
-    overlay.on_point_move(handle_down)
-    overlay.on_point_up(handle_up)
+    overlay.on_finger_down(handle_down)
+    overlay.on_finger_move(lambda g: draw_touch_objects and refresh())
+    overlay.on_finger_up(handle_up)
     root.add_area(overlay)