validation / src /gesturedetection /utils /action_controller.py
tommulder's picture
Deploy gesture detection & validation API
95db528
from scipy.spatial import distance
from collections import deque
from .enums import Event, HandPosition, targets
from .hand import Hand
class Deque:
def __init__(self, maxlen=30, min_frames=20):
self.maxlen = maxlen
self._deque = []
self.action = None
self.min_absolute_distance = 1.5
self.min_frames = min_frames
self.action_deque = deque(maxlen=5)
def __len__(self):
return len(self._deque)
def index_position(self, x):
for i in range(len(self._deque)):
if self._deque[i].position == x:
return i
def index_gesture(self, x):
for i in range(len(self._deque)):
if self._deque[i].gesture == x:
return i
def __getitem__(self, index):
return self._deque[index]
def __setitem__(self, index, value):
self._deque[index] = value
def __delitem__(self, index):
del self._deque[index]
def __iter__(self):
return iter(self._deque)
def __reversed__(self):
return reversed(self._deque)
def append(self, x):
if self.maxlen is not None and len(self) >= self.maxlen:
self._deque.pop(0)
self.set_hand_position(x)
self._deque.append(x)
self.check_is_action(x)
def check_duration(self, start_index, min_frames=None):
"""
Check duration of swipe.
Parameters
----------
start_index : int
Index of start position of swipe.
Returns
-------
bool
True if duration of swipe is more than min_frames.
"""
if min_frames == None:
min_frames = self.min_frames
if len(self) - start_index >= min_frames:
return True
else:
return False
def check_duration_max(self, start_index, max_frames=10):
"""
Check duration of swipe.
Parameters
----------
start_index : int
Index of start position of swipe.
Returns
-------
bool
True if duration of swipe is more than min_frames.
"""
if len(self) - start_index <= max_frames:
return True
else:
return False
def check_is_action(self, x):
"""
Check if gesture is action.
Parameters
----------
x : Hand
Hand object.
Returns
-------
bool
True if gesture is action.
"""
if x.position == HandPosition.LEFT_END and HandPosition.RIGHT_START in self:
start_index = self.index_position(HandPosition.RIGHT_START)
if (
self.swipe_distance(self._deque[start_index], x)
and self.check_duration(start_index)
and self.check_horizontal_swipe(self._deque[start_index], x)
):
self.action = Event.SWIPE_LEFT
self.clear()
return True
elif x.position == HandPosition.RIGHT_END and HandPosition.LEFT_START in self:
start_index = self.index_position(HandPosition.LEFT_START)
if (
self.swipe_distance(self._deque[start_index], x)
and self.check_duration(start_index)
and self.check_horizontal_swipe(self._deque[start_index], x)
):
self.action = Event.SWIPE_RIGHT
self.clear()
return True
else:
self.clear()
elif x.position == HandPosition.UP_END and HandPosition.DOWN_START in self:
start_index = self.index_position(HandPosition.DOWN_START)
if (
self.swipe_distance(self._deque[start_index], x)
and self.check_duration(start_index)
and self.check_vertical_swipe(self._deque[start_index], x)
):
self.action = Event.SWIPE_UP
self.clear()
return True
else:
self.clear()
elif x.position == HandPosition.DOWN_END and HandPosition.UP_START in self:
start_index = self.index_position(HandPosition.UP_START)
if (
self.swipe_distance(self._deque[start_index], x)
and self.check_duration(start_index)
and self.check_vertical_swipe(self._deque[start_index], x)
):
self.action = Event.SWIPE_DOWN
self.clear()
return True
else:
self.clear()
elif x.gesture == 18: # grip
if self.action is None:
start_index = self.index_gesture(18)
if self.check_duration(start_index):
self.action = Event.DRAG2
return True
elif self.action == Event.DRAG2 and x.gesture in [11, 12]: # hand heart
self.action = Event.DROP2
self.clear()
return True
elif x.gesture == 29: # ok
if self.action is None:
start_index = self.index_gesture(29)
if self.check_duration(start_index):
self.action = Event.DRAG3
return True
elif self.action == Event.DRAG3 and x.gesture in [11, 12]: # hand heart
self.action = Event.DROP3
self.clear()
return True
elif x.position == HandPosition.FAST_SWIPE_UP_END and HandPosition.FAST_SWIPE_UP_START in self:
start_index = self.index_position(HandPosition.FAST_SWIPE_UP_START)
if (
self.check_duration(start_index, min_frames=20)
and self.check_vertical_swipe(self._deque[start_index], x)
):
self.action = Event.FAST_SWIPE_UP
self.clear()
return True
else:
self.clear()
elif x.position == HandPosition.FAST_SWIPE_DOWN_END and HandPosition.FAST_SWIPE_DOWN_START in self:
start_index = self.index_position(HandPosition.FAST_SWIPE_DOWN_START)
if (
self.check_duration(start_index, min_frames=20)
and self.check_vertical_swipe(self._deque[start_index], x)
):
self.action = Event.FAST_SWIPE_DOWN
self.clear()
return True
elif x.position == HandPosition.ZOOM_IN_END and HandPosition.ZOOM_IN_START in self:
start_index = self.index_position(HandPosition.ZOOM_IN_START)
if (
self.check_duration(start_index, min_frames=20)
and self.check_vertical_swipe(self._deque[start_index], x)
and self.check_horizontal_swipe(self._deque[start_index], x)
):
self.action = Event.ZOOM_IN
self.clear()
return True
elif x.position == HandPosition.ZOOM_OUT_END and HandPosition.ZOOM_OUT_START in self:
start_index = self.index_position(HandPosition.ZOOM_OUT_START)
if (
self.check_duration(start_index, min_frames=20)
and self.check_vertical_swipe(self._deque[start_index], x)
and self.check_horizontal_swipe(self._deque[start_index], x)
):
self.action = Event.ZOOM_OUT
self.clear()
return True
else:
self.clear()
elif x.position == HandPosition.LEFT_END2 and HandPosition.RIGHT_START2 in self:
start_index = self.index_position(HandPosition.RIGHT_START2)
if (
self.swipe_distance(self._deque[start_index], x)
and self.check_duration(start_index)
and self.check_horizontal_swipe(self._deque[start_index], x)
):
self.action = Event.SWIPE_LEFT2
self.clear()
return True
else:
self.clear()
elif x.position == HandPosition.RIGHT_END2 and HandPosition.LEFT_START2 in self:
start_index = self.index_position(HandPosition.LEFT_START2)
if (
self.swipe_distance(self._deque[start_index], x)
and self.check_duration(start_index)
and self.check_horizontal_swipe(self._deque[start_index], x)
):
self.action = Event.SWIPE_RIGHT2
self.clear()
return True
else:
self.clear()
elif x.position == HandPosition.UP_END2 and HandPosition.DOWN_START2 in self:
start_index = self.index_position(HandPosition.DOWN_START2)
if (
self.swipe_distance(self._deque[start_index], x)
and self.check_duration(start_index)
and self.check_vertical_swipe(self._deque[start_index], x)
):
self.action = Event.SWIPE_UP2
self.clear()
return True
else:
self.clear()
elif x.position == HandPosition.LEFT_END3 and HandPosition.RIGHT_START3 in self:
start_index = self.index_position(HandPosition.RIGHT_START3)
if (
self.swipe_distance(self._deque[start_index], x)
and self.check_duration(start_index)
and self.check_horizontal_swipe(self._deque[start_index], x)
):
self.action = Event.SWIPE_LEFT3 # two
self.clear()
return True
else:
self.clear()
elif x.position == HandPosition.RIGHT_END3 and HandPosition.LEFT_START3 in self:
start_index = self.index_position(HandPosition.LEFT_START3)
if (
self.swipe_distance(self._deque[start_index], x)
and self.check_duration(start_index)
and self.check_horizontal_swipe(self._deque[start_index], x)
):
self.action = Event.SWIPE_RIGHT3
self.clear()
return True
else:
self.clear()
elif x.position == HandPosition.UP_END3 and HandPosition.DOWN_START3 in self:
start_index = self.index_position(HandPosition.DOWN_START3)
if (
self.check_duration(start_index, min_frames=15)
and self.check_vertical_swipe(self._deque[start_index], x)
):
self.action = Event.SWIPE_UP3
self.clear()
return True
else:
self.clear()
elif x.position == HandPosition.DOWN_END3 and HandPosition.UP_START3 in self:
start_index = self.index_position(HandPosition.UP_START3)
if (
self.check_duration(start_index, min_frames=15)
and self.check_vertical_swipe(self._deque[start_index], x)
):
self.action = Event.SWIPE_DOWN3
self.clear()
return True
else:
self.clear()
elif HandPosition.DRAG_START in self and x.gesture == 25: # fist
if self.action is None:
start_index = self.index_gesture(17) # grabbing
if self.check_duration(start_index, min_frames=3):
self.action = Event.DRAG
return True
else:
self.clear()
elif HandPosition.ZOOM_IN_START in self and x.gesture == 19: # point
start_index = self.index_position(HandPosition.ZOOM_IN_START)
if (
self.check_duration(start_index, min_frames=8)
and self.check_vertical_swipe(self._deque[start_index], x)
and self.check_horizontal_swipe(self._deque[start_index], x)
):
self.action = Event.TAP
self.clear()
return True
elif (
self.check_duration(start_index, min_frames=2)
and self.check_duration_max(start_index, max_frames=8)
and self.check_vertical_swipe(self._deque[start_index], x)
and self.check_horizontal_swipe(self._deque[start_index], x)
):
self.action_deque.append(Event.TAP)
if len(self.action_deque) >= 2 and self.action_deque[-1] == Event.TAP and self.action_deque[-2] == Event.TAP:
self.action_deque.pop()
self.action_deque.pop()
self.action = Event.DOUBLE_TAP
self.clear()
return True
else:
self.clear()
elif x.position == HandPosition.DOWN_END2 and HandPosition.ZOOM_OUT_START in self:
start_index = self.index_position(HandPosition.ZOOM_OUT_START)
if (
self.swipe_distance(self._deque[start_index], x)
and self.check_vertical_swipe(self._deque[start_index], x)
):
self.action = Event.SWIPE_DOWN2
self.clear()
return True
else:
self.clear()
elif x.position == HandPosition.ZOOM_OUT_START and HandPosition.UP_START2 in self:
start_index = self.index_position(HandPosition.UP_START2)
if (
self.swipe_distance(self._deque[start_index], x)
and self.check_vertical_swipe(self._deque[start_index], x)
):
self.action = Event.SWIPE_UP2
self.clear()
return True
else:
self.clear()
elif self.action == Event.DRAG and x.gesture in [35, 31, 36, 17]: # [stop, palm, stop_inverted, grabbing]
self.action = Event.DROP
self.clear()
return True
return False
@staticmethod
def check_horizontal_swipe(start_hand, x):
"""
Check if swipe is horizontal.
Parameters
----------
start_hand : Hand
Hand object of start position of swipe.
x : Hand
Hand object of end position of swipe.
Returns
-------
bool
True if swipe is horizontal.
"""
boundary = [start_hand.bbox[1], start_hand.bbox[3]]
if boundary[0] < x.center[1] < boundary[1]:
return True
else:
return False
@staticmethod
def check_vertical_swipe(start_hand, x):
"""
Check if swipe is vertical.
Parameters
----------
start_hand : Hand
Hand object of start position of swipe.
x : Hand
Hand object of end position of swipe.
Returns
-------
bool
True if swipe is vertical.
"""
boundary = [start_hand.bbox[0], start_hand.bbox[2]]
if boundary[0] < x.center[0] < boundary[1]:
return True
else:
return False
def __contains__(self, item):
for x in self._deque:
if x.position == item:
return True
def set_hand_position(self, hand: Hand):
"""
Set hand position.
Parameters
----------
hand : Hand
Hand object.
"""
if hand.gesture in [31, 35, 36]: # [palm, stop, stop_inv]
if HandPosition.DOWN_START in self:
hand.position = HandPosition.UP_END
else:
hand.position = HandPosition.UP_START
elif hand.gesture == 0: # hand_down
if HandPosition.UP_START in self:
hand.position = HandPosition.DOWN_END
else:
hand.position = HandPosition.DOWN_START
elif hand.gesture == 1: # hand_right
if HandPosition.LEFT_START in self:
hand.position = HandPosition.RIGHT_END
else:
hand.position = HandPosition.RIGHT_START
elif hand.gesture == 2: # hand_left
if HandPosition.RIGHT_START in self:
hand.position = HandPosition.LEFT_END
else:
hand.position = HandPosition.LEFT_START
elif hand.gesture == 30: # one
if HandPosition.FAST_SWIPE_UP_START in self:
hand.position = HandPosition.FAST_SWIPE_UP_END
else:
hand.position = HandPosition.FAST_SWIPE_DOWN_START
elif hand.gesture == 19: # point
if HandPosition.FAST_SWIPE_DOWN_START in self:
hand.position = HandPosition.FAST_SWIPE_DOWN_END
else:
hand.position = HandPosition.FAST_SWIPE_UP_START
elif hand.gesture == 17: # grabbing
hand.position = HandPosition.DRAG_START
elif hand.gesture == 25: # fist
if HandPosition.ZOOM_OUT_START in self:
hand.position = HandPosition.ZOOM_OUT_END
else:
hand.position = HandPosition.ZOOM_IN_START
elif hand.gesture == 3: # thumb_index
if HandPosition.ZOOM_IN_START in self:
hand.position = HandPosition.ZOOM_IN_END
else:
hand.position = HandPosition.ZOOM_OUT_START
elif hand.gesture == 38: # three2
if HandPosition.ZOOM_IN_START in self:
hand.position = HandPosition.ZOOM_IN_END
else:
hand.position = HandPosition.ZOOM_OUT_START
elif hand.gesture == 5: # thumb_right
if HandPosition.LEFT_START2 in self:
hand.position = HandPosition.RIGHT_END2
else:
hand.position = HandPosition.RIGHT_START2
elif hand.gesture == 4: # thumb_left
if HandPosition.RIGHT_START2 in self:
hand.position = HandPosition.LEFT_END2
else:
hand.position = HandPosition.LEFT_START2
elif hand.gesture == 15: # two_right
if HandPosition.LEFT_START3 in self:
hand.position = HandPosition.RIGHT_END3
else:
hand.position = HandPosition.RIGHT_START3
elif hand.gesture == 14: # two_left
if HandPosition.RIGHT_START3 in self:
hand.position = HandPosition.LEFT_END3
else:
hand.position = HandPosition.LEFT_START3
elif hand.gesture == 39: # two_up
if HandPosition.DOWN_START3 in self:
hand.position = HandPosition.UP_END3
else:
hand.position = HandPosition.UP_START3
elif hand.gesture == 16: # two_down
if HandPosition.UP_START3 in self:
hand.position = HandPosition.DOWN_END3
else:
hand.position = HandPosition.DOWN_START3
elif hand.gesture == 6: # thumb_down
if HandPosition.ZOOM_OUT_START in self:
hand.position = HandPosition.DOWN_END2
else:
hand.position = HandPosition.UP_START2
else:
hand.position = HandPosition.UNKNOWN
def swipe_distance(
self,
first_hand: Hand,
last_hand: Hand,
):
"""
Check if swipe distance is more than min_distance.
Parameters
----------
first_hand : Hand
Hand object of start position of swipe.
last_hand : Hand
Hand object of end position of swipe.
Returns
-------
bool
True if swipe distance is more than min_distance.
"""
hand_dist = distance.euclidean(first_hand.center, last_hand.center)
hand_size = (first_hand.size + last_hand.size) / 2
return hand_dist / hand_size > self.min_absolute_distance
def clear(self):
self._deque.clear()
def copy(self):
return self._deque.copy()
def count(self, x):
return self._deque.count(x)
def extend(self, iterable):
self._deque.extend(iterable)
def insert(self, i, x):
self._deque.insert(i, x)
def pop(self):
return self._deque.pop()
def remove(self, value):
self._deque.remove(value)
def reverse(self):
self._deque.reverse()
def __str__(self):
return f"Deque({[hand.gesture for hand in self._deque]})"