Commit cc772060 authored by Taddeüs Kroes's avatar Taddeüs Kroes

Added hand tracker and implemented it in the test application.

parent adeaa561
from basic import BasicEventTracker from basic import BasicEventTracker
from tap import TapTracker from tap import TapTracker
from transform import TransformationTracker from transform import TransformationTracker
from hand import HandTracker
# Map of gesture type to tracker type # Map of gesture type to tracker type
...@@ -31,3 +32,4 @@ def create_tracker(gesture_type, widget): ...@@ -31,3 +32,4 @@ def create_tracker(gesture_type, widget):
_register_tracker(BasicEventTracker) _register_tracker(BasicEventTracker)
_register_tracker(TapTracker) _register_tracker(TapTracker)
_register_tracker(TransformationTracker) _register_tracker(TransformationTracker)
_register_tracker(HandTracker)
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))
...@@ -88,7 +88,7 @@ class Polygon(BoundingBoxArea): ...@@ -88,7 +88,7 @@ class Polygon(BoundingBoxArea):
fullscreen = False fullscreen = False
draw_bounding_boxes = draw_touch_points = True draw_bounding_boxes = draw_touch_objects = True
W, H = mt.screen.screen_size W, H = mt.screen.screen_size
...@@ -107,8 +107,8 @@ def create_context_window(w, h, callback): ...@@ -107,8 +107,8 @@ def create_context_window(w, h, callback):
refresh() refresh()
def handle_key(win, event): def handle_key(win, event):
"""Handle key event. 'f' toggles fullscreen, 'q' exits the program, 'b' """Handle key event. 'f' toggles fullscreen, 'b' toggles bounding
toggles bounding boxes, 'p' toggles touch points.""" boxes, 'i' toggles input points, 'q' exits the program."""
if event.keyval >= 256: if event.keyval >= 256:
return return
...@@ -118,16 +118,16 @@ def create_context_window(w, h, callback): ...@@ -118,16 +118,16 @@ def create_context_window(w, h, callback):
global fullscreen global fullscreen
(win.unfullscreen if fullscreen else win.fullscreen)() (win.unfullscreen if fullscreen else win.fullscreen)()
fullscreen = not fullscreen fullscreen = not fullscreen
elif key == 'q':
quit()
elif key == 'b': elif key == 'b':
global draw_bounding_boxes global draw_bounding_boxes
draw_bounding_boxes = not draw_bounding_boxes draw_bounding_boxes = not draw_bounding_boxes
refresh() refresh()
elif key == 'p': elif key == 'i':
global draw_touch_points global draw_touch_objects
draw_touch_points = not draw_touch_points draw_touch_objects = not draw_touch_objects
refresh() refresh()
elif key == 'q':
quit()
# Root area (will be synchronized with GTK window) # Root area (will be synchronized with GTK window)
global root, overlay global root, overlay
...@@ -156,7 +156,7 @@ def create_context_window(w, h, callback): ...@@ -156,7 +156,7 @@ def create_context_window(w, h, callback):
window.show() window.show()
def draw(*args): def draw():
if not cr: if not cr:
return return
...@@ -171,26 +171,44 @@ def draw(*args): ...@@ -171,26 +171,44 @@ def draw(*args):
obj.draw(cr) obj.draw(cr)
cr.restore() cr.restore()
if draw_touch_points: if draw_touch_objects:
ox, oy = root.get_position() ox, oy = root.get_position()
cr.set_source_rgb(*WHITE) cr.set_source_rgb(*WHITE)
for x, y in touch_points.itervalues(): for hand in touch_hands:
x -= ox cx, cy = hand.get_centroid()
y -= oy
cr.set_line_width(3) # Filled centroid circle
cr.arc(x, y, 20, 0, 2 * pi) if len(hand) > 1:
cr.stroke() 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() window.queue_draw()
...@@ -198,10 +216,10 @@ def quit(*args): ...@@ -198,10 +216,10 @@ def quit(*args):
gtk.main_quit() gtk.main_quit()
# Initialization # Global variables
window = cr = root = overlay = None window = cr = root = overlay = None
draw_objects = [] draw_objects = []
touch_points = {} touch_hands = []
def triangle_height(width): def triangle_height(width):
...@@ -231,22 +249,22 @@ def on_show(window): ...@@ -231,22 +249,22 @@ def on_show(window):
# Overlay catches basic events # Overlay catches basic events
def handle_down(gesture): def handle_down(gesture):
point = gesture.get_event().get_touch_object() if gesture.is_first():
touch_points[point.get_id()] = point.get_position() touch_hands.append(gesture.get_hand())
if draw_touch_points: if draw_touch_objects:
refresh() refresh()
def handle_up(gesture): def handle_up(gesture):
point = gesture.get_event().get_touch_object() if gesture.is_last():
del touch_points[point.get_id()] touch_hands.remove(gesture.get_hand())
if draw_touch_points: if draw_touch_objects:
refresh() refresh()
overlay.on_point_down(handle_down) overlay.on_finger_down(handle_down)
overlay.on_point_move(handle_down) overlay.on_finger_move(lambda g: draw_touch_objects and refresh())
overlay.on_point_up(handle_up) overlay.on_finger_up(handle_up)
root.add_area(overlay) root.add_area(overlay)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment