blob: b477fd7ff437a6921e23fae0e098c7ef41438940 [file] [log] [blame]
# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# This module contains unit tests for the classes in the mtb module
import glob
import os
import sys
import unittest
import common_unittest_utils
import fuzzy
import mtb
import test_conf as conf
from common_unittest_utils import create_mocked_devices
from firmware_constants import AXIS, GV, MTB, PLATFORM, UNIT, VAL
from mtb import FingerPath, TidPacket
from geometry.elements import Point, about_eq
unittest_path_lumpy = os.path.join(os.getcwd(), 'tests/logs/lumpy')
mocked_device = create_mocked_devices()
def get_mtb_packets(gesture_filename):
"""Get mtb_packets object by reading the gesture file."""
parser = mtb.MtbParser()
packets = parser.parse_file(gesture_filename)
mtb_packets = mtb.Mtb(packets=packets)
return mtb_packets
class FakeMtb(mtb.Mtb):
"""A fake MTB class to set up x and y positions directly."""
def __init__(self, list_x, list_y):
self.list_x = list_x
self.list_y = list_y
def get_x_y(self, target_slot):
"""Return list_x, list_y directly."""
return (self.list_x, self.list_y)
class MtbTest(unittest.TestCase):
"""Unit tests for mtb.Mtb class."""
def setUp(self):
self.test_dir = os.path.join(os.getcwd(), 'tests')
self.data_dir = os.path.join(self.test_dir, 'data')
def _get_filepath(self, filename, gesture_dir=''):
return os.path.join(self.data_dir, gesture_dir, filename)
def _get_range_middle(self, criteria):
"""Get the middle range of the criteria."""
fc = fuzzy.FuzzyCriteria(criteria)
range_min , range_max = fc.get_criteria_value_range()
range_middle = (range_min + range_max) / 2.0
return range_middle
def _call_get_reversed_motions(self, list_x, list_y, expected_x,
expected_y, direction):
mtb = FakeMtb(list_x, list_y)
displacement = mtb.get_reversed_motions(0, direction, ratio=0.1)
self.assertEqual(displacement[AXIS.X], expected_x)
self.assertEqual(displacement[AXIS.Y], expected_y)
def test_get_reversed_motions_no_reversed(self):
list_x = (10, 22 ,36, 54, 100)
list_y = (1, 2 ,6, 10, 22)
self._call_get_reversed_motions(list_x, list_y, 0, 0, GV.TLBR)
def test_get_reversed_motions_reversed_x_y(self):
list_x = (10, 22 ,36, 154, 100)
list_y = (1, 2 ,6, 30, 22)
self._call_get_reversed_motions(list_x, list_y, -54, -8, GV.TLBR)
def _test_get_x_y(self, filename, slot, expected_value):
gesture_filename = self._get_filepath(filename)
mtb_packets = get_mtb_packets(gesture_filename)
list_x, list_y = mtb_packets.get_x_y(slot)
points = zip(list_x, list_y)
self.assertEqual(len(points), expected_value)
def test_get_x_y(self):
self._test_get_x_y('one_finger_with_slot_0.dat', 0, 12)
self._test_get_x_y('one_finger_without_slot_0.dat', 0, 9)
self._test_get_x_y('two_finger_with_slot_0.dat', 0, 121)
self._test_get_x_y('two_finger_with_slot_0.dat', 1, 59)
self._test_get_x_y('two_finger_without_slot_0.dat', 0, 104)
self._test_get_x_y('two_finger_without_slot_0.dat', 1, 10)
def test_get_pressure(self):
"""Test get pressure"""
filename = 'one_finger_with_slot_0.dat'
gesture_filename = self._get_filepath(filename)
mtb_packets = get_mtb_packets(gesture_filename)
finger_paths = mtb_packets.get_ordered_finger_paths()
# There is only one tracking ID in the file.
self.assertEqual(len(finger_paths), 1)
# Verify some of the pressure values
finger_path = finger_paths.values()[0]
list_z = finger_path.get('pressure')
self.assertEqual(list_z[0:5], [59, 57, 56, 58, 60])
def test_get_x_y_multiple_slots(self):
filename = 'x_y_multiple_slots.dat'
filepath = self._get_filepath(filename)
mtb_packets = get_mtb_packets(filepath)
slots = (0, 1)
list_x, list_y = mtb_packets.get_x_y_multiple_slots(slots)
expected_list_x = {}
expected_list_y = {}
expected_list_x[0] = [1066, 1068, 1082, 1183, 1214, 1285, 1322, 1351,
1377, 1391]
expected_list_y[0] = [561, 559, 542, 426, 405, 358, 328, 313, 304, 297]
expected_list_x[1] = [770, 769, 768, 758, 697, 620, 585, 565, 538, 538]
expected_list_y[1] = [894, 894, 895, 898, 927, 968, 996, 1003, 1013,
1013]
for slot in slots:
self.assertEqual(list_x[slot], expected_list_x[slot])
self.assertEqual(list_y[slot], expected_list_y[slot])
def test_get_x_y_multiple_slots2(self):
"""Test slot state machine.
When the last slot in the previous packet is slot 0, and the first
slot in the current packet is also slot 0, the slot 0 will not be
displayed explicitly. This test ensures that the slot stat machine
is tracked properly.
"""
filename = 'pinch_to_zoom.zoom_in.dat'
filepath = self._get_filepath(filename)
mtb_packets = get_mtb_packets(filepath)
slots = (0, 1)
list_x, list_y = mtb_packets.get_x_y_multiple_slots(slots)
expected_final_x = {}
expected_final_y = {}
expected_final_x[0] = 1318
expected_final_y[0] = 255
expected_final_x[1] = 522
expected_final_y[1] = 1232
for slot in slots:
self.assertEqual(list_x[slot][-1], expected_final_x[slot])
self.assertEqual(list_y[slot][-1], expected_final_y[slot])
def _test_get_all_finger_paths_about_numbers_of_packets(
self, filename, expected_numbers):
mtb_packets = get_mtb_packets(self._get_filepath(filename))
finger_paths = mtb_packets.get_ordered_finger_paths()
for tid, expected_len in expected_numbers.items():
self.assertEqual(len(finger_paths[tid].tid_packets), expected_len)
def test_get_ordered_finger_paths_about_number_of_packets(self):
self._test_get_all_finger_paths_about_numbers_of_packets(
'two_finger_with_slot_0.dat', {2101: 122, 2102: 60})
self._test_get_all_finger_paths_about_numbers_of_packets(
'two_finger_without_slot_0.dat', {2097: 105, 2098: 11})
def test_data_ready(self):
"""Test data_ready flag when point.x could be 0."""
filename = ('20130506_030025-fw_11.27-robot_sim/'
'one_finger_to_edge.center_to_left.slow-lumpy-fw_11.27-'
'robot_sim-20130506_031554.dat')
filepath = os.path.join(unittest_path_lumpy, filename)
mtb_packets = get_mtb_packets(filepath)
points = mtb_packets.get_ordered_finger_path(0, 'point')
# Note:
# 1. In the first packet, there exists the event ABS_PRESSURE
# but no ABS_MT_PRESSURE.
# 2. The last packet with ABS_MT_TRACKING_ID = -1 is also counted.
self.assertEqual(len(points), 78)
def _test_drumroll(self, filename, expected_max_distance):
"""expected_max_distance: unit in pixel"""
gesture_filename = self._get_filepath(filename)
mtb_packets = get_mtb_packets(gesture_filename)
actual_max_distance = mtb_packets.get_max_distance_of_all_tracking_ids()
self.assertTrue(about_eq(actual_max_distance, expected_max_distance))
def test_drumroll(self):
expected_max_distance = 52.0216301167
self._test_drumroll('drumroll_lumpy.dat', expected_max_distance)
def test_drumroll1(self):
expected_max_distance = 43.5660418216
self._test_drumroll('drumroll_lumpy_1.dat', expected_max_distance)
def test_drumroll_link(self):
expected_max_distance = 25.6124969497
self._test_drumroll('drumroll_link.dat', expected_max_distance)
def test_no_drumroll_link(self):
expected_max_distance = 2.91547594742
self._test_drumroll('no_drumroll_link.dat', expected_max_distance)
def test_no_drumroll_link(self):
expected_max_distance = 24.8243831746
self._test_drumroll('drumroll_link_2.dat', expected_max_distance)
def _test_finger_path(self, filename, tid, expected_slot, expected_data,
request_data_ready=True):
"""Test the data in a finger path"""
# Instantiate the expected finger_path
expected_finger_path = FingerPath(expected_slot,
[TidPacket(time, Point(*xy), z)
for time, xy, z in expected_data])
# Derive the actual finger_path for the specified tid
mtb_packets = get_mtb_packets(self._get_filepath(filename))
finger_paths = mtb_packets.get_ordered_finger_paths(request_data_ready)
actual_finger_path = finger_paths[tid]
# Assert that the packet lengths are the same.
self.assertEqual(len(expected_finger_path.tid_packets),
len(actual_finger_path.tid_packets))
# Assert that all tid data (including syn_time, point, pressure, etc.)
# in the tid packets are the same.
for i in range(len(actual_finger_path.tid_packets)):
expected_packet = expected_finger_path.tid_packets[i]
actual_packet = actual_finger_path.tid_packets[i]
self.assertEqual(expected_packet.syn_time, actual_packet.syn_time)
self.assertTrue(expected_packet.point == actual_packet.point)
self.assertEqual(expected_packet.pressure, actual_packet.pressure)
def test_get_ordered_finger_paths(self):
"""Test get_ordered_finger_paths
Tracking ID 95: slot 0 (no explicit slot 0 assigned).
This is the only slot in the packet.
"""
filename = 'drumroll_link_2.dat'
tid = 95
expected_slot = 0
expected_data = [# (syn_time, (x, y), z)
(238154.686034, (789, 358), 59),
(238154.691606, (789, 358), 60),
(238154.697058, (789, 358), 57),
(238154.702576, (789, 358), 59),
(238154.713731, (789, 358), 57),
(238154.719160, (789, 359), 57),
(238154.724791, (789, 359), 56),
(238154.730111, (789, 359), 58),
(238154.735588, (788, 359), 53),
(238154.741068, (788, 360), 53),
(238154.746569, (788, 360), 49),
(238154.752108, (787, 360), 40),
(238154.757705, (787, 361), 27),
(238154.763075, (490, 903), 46),
(238154.768532, (486, 892), 61),
(238154.774695, (484, 895), 57),
(238154.780192, (493, 890), 56),
(238154.785651, (488, 893), 55),
(238154.791140, (488, 893), 56),
(238154.802080, (489, 893), 55),
(238154.807578, (490, 893), 50),
(238154.818573, (490, 893), 46),
(238154.824066, (491, 893), 36),
(238154.829525, (492, 893), 22),
(238154.849958, (492, 893), 22),
]
self._test_finger_path(filename, tid, expected_slot, expected_data)
def test_get_ordered_finger_paths2(self):
"""Test get_ordered_finger_paths
Tracking ID 104: slot 0 (explicit slot 0 assigned).
This is the 2nd slot in the packet.
A slot 1 has already existed.
"""
filename = 'drumroll_link_2.dat'
tid = 104
expected_slot = 0
expected_data = [# (syn_time, (x, y), z)
(238157.994296, (780, 373), 75),
(238158.001110, (780, 372), 75),
(238158.007128, (780, 372), 76),
(238158.012617, (780, 372), 73),
(238158.018112, (780, 373), 69),
(238158.023600, (780, 373), 68),
(238158.029542, (781, 373), 51),
(238158.049605, (781, 373), 51),
]
self._test_finger_path(filename, tid, expected_slot, expected_data)
def test_get_ordered_finger_paths2b(self):
"""Test get_ordered_finger_paths
Tracking ID 103: slot 1 (explicit slot 1 assigned).
This tracking ID overlaps with two distinct
tracking IDs of which the slot is the same slot 0.
This is a good test as a multiple-finger case.
tid 102, slot 0 arrived
tid 103, slot 1 arrived
tid 102, slot 0 left
tid 104, slot 0 arrived
tid 103, slot 1 left
tid 104, slot 0 left
"""
filename = 'drumroll_link_2.dat'
tid = 103
expected_slot = 1
expected_data = [# (syn_time, (x, y), z)
(238157.906405, (527, 901), 71),
(238157.911749, (527, 901), 74),
(238157.917247, (527, 901), 73),
(238157.923152, (527, 902), 71),
(238157.928317, (527, 902), 72),
(238157.934492, (527, 902), 71),
(238157.939984, (527, 902), 69),
(238157.945485, (527, 902), 65),
(238157.950984, (527, 902), 66),
(238157.956482, (527, 902), 70),
(238157.961976, (527, 902), 65),
(238157.973768, (527, 902), 64),
(238157.980491, (528, 901), 61),
(238157.987140, (529, 899), 60),
(238157.994296, (531, 896), 52),
(238158.001110, (534, 892), 34),
(238158.007128, (534, 892), 34),
(238158.012617, (534, 892), 34),
(238158.018112, (534, 892), 34),
(238158.023600, (534, 892), 34),
(238158.029542, (534, 892), 34),
]
self._test_finger_path(filename, tid, expected_slot, expected_data)
def test_get_ordered_finger_paths3(self):
"""Test get_ordered_finger_paths
This is a good test sample.
- An unusual slot 9
- This is the 2nd slot in the packet. A slot 8 has already existed.
- Its ABS_MT_PRESSURE is missing in the first packet.
- Slot 8 terminates a few packets earlier than this slot.
- Some of the ABS_MT_POSITION_X/Y and ABS_MT_PRESSURE are not shown.
"""
filename = 'drumroll_3.dat'
tid = 582
expected_slot = 9
expected_data = [# (syn_time, (x, y), z)
(6411.371613, (682, 173), None),
(6411.382541, (667, 186), 35),
(6411.393355, (664, 189), 37),
(6411.404310, (664, 190), 38),
(6411.413015, (664, 189), 38),
(6411.422118, (665, 189), 38),
(6411.430792, (665, 189), 37),
(6411.439764, (667, 188), 36),
(6411.448484, (675, 185), 29),
(6411.457212, (683, 181), 17),
(6411.465843, (693, 172), 5),
(6411.474749, (469, 381), 6),
(6411.483702, (471, 395), 26),
(6411.492369, (471, 396), 13),
(6411.499916, (471, 396), 13),
]
self._test_finger_path(filename, tid, expected_slot, expected_data,
request_data_ready=False)
def test_get_slot_data(self):
"""Test if it can get the data from the correct slot.
slot 0 and slot 1 start at the same packet. This test verifies if the
method uses the correct corresponding slot numbers.
"""
filename = 'two_finger_tracking.diagonal.slow.dat'
gesture_filename = self._get_filepath(filename)
mtb_packets = get_mtb_packets(gesture_filename)
# There are more packets. Use just a few of them to verify.
xy_pairs = {
# Slot 0
0: [(1142, 191), (1144, 201), (1144, 200)],
# Slot 1
1: [(957, 105), (966, 106), (960, 104)],
}
number_packets = {
# Slot 0
0: 190,
# Slot 1
1: 189,
}
slots = [0, 1]
for slot in slots:
points = mtb_packets.get_slot_data(slot, 'point')
# Verify the number of packets in each slot
self.assertEqual(len(points), number_packets[slot])
# Verify a few packets in each slot
for i, xy_pair in enumerate(xy_pairs[slot]):
self.assertTrue(Point(*xy_pair) == points[i])
def test_convert_to_evemu_format(self):
evemu_filename = self._get_filepath('one_finger_swipe.evemu.dat')
mtplot_filename = self._get_filepath('one_finger_swipe.dat')
packets = mtb.MtbParser().parse_file(mtplot_filename)
evemu_converted_iter = iter(mtb.convert_to_evemu_format(packets))
with open(evemu_filename) as evemuf:
for line_evemu_original in evemuf:
evemu_original = line_evemu_original.split()
evemu_converted_str = next(evemu_converted_iter, None)
self.assertNotEqual(evemu_converted_str, None)
if evemu_converted_str:
evemu_converted = evemu_converted_str.split()
self.assertEqual(len(evemu_original), 5)
self.assertEqual(len(evemu_converted), 5)
# Skip the timestamps for they are different in both formats.
# Prefix, type, code, and value should be the same.
for i in [0, 2, 3, 4]:
self.assertEqual(evemu_original[i], evemu_converted[i])
def test_get_largest_gap_ratio(self):
"""Test get_largest_gap_ratio for one-finger and two-finger gestures."""
# The following files come with noticeable large gaps.
list_large_ratio = [
'one_finger_tracking.left_to_right.slow_1.dat',
'two_finger_gaps.vertical.dat',
'two_finger_gaps.horizontal.dat',
'resting_finger_2nd_finger_moving_segment_gaps.dat',
'gap_new_finger_arriving_or_departing.dat',
'one_stationary_finger_2nd_finger_moving_gaps.dat',
'resting_finger_2nd_finger_moving_gaps.dat',
]
gesture_slots = {
'one_finger': [0,],
'two_finger': [0, 1],
'resting_finger': [1,],
'gap_new_finger': [0,],
'one_stationary_finger': [1,],
}
range_middle = self._get_range_middle(conf.no_gap_criteria)
gap_data_dir = self._get_filepath('gaps')
gap_data_filenames = glob.glob(os.path.join(gap_data_dir, '*.dat'))
for filename in gap_data_filenames:
mtb_packets = get_mtb_packets(filename)
base_filename = os.path.basename(filename)
# What slots to check are based on the gesture name.
slots = []
for gesture in gesture_slots:
if base_filename.startswith(gesture):
slots = gesture_slots[gesture]
break
for slot in slots:
largest_gap_ratio = mtb_packets.get_largest_gap_ratio(slot)
if base_filename in list_large_ratio:
self.assertTrue(largest_gap_ratio >= range_middle)
else:
self.assertTrue(largest_gap_ratio < range_middle)
def test_get_largest_accumulated_level_jumps(self):
"""Test get_largest_accumulated_level_jumps."""
dir_level_jumps = 'drag_edge_thumb'
filenames = [
# filenames with level jumps
# ----------------------------------
'drag_edge_thumb.horizontal.dat',
'drag_edge_thumb.horizontal_2.dat',
# test no points in some tracking ID
'drag_edge_thumb.horizontal_3.no_points.dat',
'drag_edge_thumb.vertical.dat',
'drag_edge_thumb.vertical_2.dat',
'drag_edge_thumb.diagonal.dat',
# Change tracking IDs quickly.
'drag_edge_thumb.horizontal_4.change_ids_quickly.dat',
# filenames without level jumps
# ----------------------------------
'drag_edge_thumb.horizontal.curvy.dat',
'drag_edge_thumb.horizontal_2.curvy.dat',
'drag_edge_thumb.vertical.curvy.dat',
'drag_edge_thumb.vertical_2.curvy.dat',
# Rather small level jumps
'drag_edge_thumb.horizontal_5.small_level_jumps.curvy.dat',
]
largest_level_jumps = {
# Large jumps
'drag_edge_thumb.horizontal.dat': {AXIS.X: 0, AXIS.Y: 97},
# Smaller jumps
'drag_edge_thumb.horizontal_2.dat': {AXIS.X: 0, AXIS.Y: 24},
# test no points in some tracking ID
'drag_edge_thumb.horizontal_3.no_points.dat':
{AXIS.X: 97, AXIS.Y: 88},
# Change tracking IDs quickly.
'drag_edge_thumb.horizontal_4.change_ids_quickly.dat':
{AXIS.X: 0, AXIS.Y: 14},
# Large jumps
'drag_edge_thumb.vertical.dat': {AXIS.X: 54, AXIS.Y: 0},
# The first slot 0 comes with smaller jumps only.
'drag_edge_thumb.vertical_2.dat': {AXIS.X: 20, AXIS.Y: 0},
# Large jumps
'drag_edge_thumb.diagonal.dat': {AXIS.X: 84, AXIS.Y: 58},
}
target_slot = 0
for filename in filenames:
filepath = self._get_filepath(filename, gesture_dir=dir_level_jumps)
packets = get_mtb_packets(filepath)
displacements = packets.get_displacements_for_slots(target_slot)
# There are no level jumps in a curvy line.
file_with_level_jump = 'curvy' not in filename
# Check the first slot only
tids = displacements.keys()
tids.sort()
tid = tids[0]
# Check both axis X and axis Y
for axis in AXIS.LIST:
disp = displacements[tid][axis]
jump = packets.get_largest_accumulated_level_jumps(disp)
# Verify that there are no jumps in curvy files, and
# that there are jumps in the other files.
expected_jump = (0 if not file_with_level_jump
else largest_level_jumps[filename][axis])
self.assertTrue(jump == expected_jump)
def test_get_max_distance_from_points(self):
"""Test get_max_distance_from_points"""
# Two farthest points: (15, 16) and (46, 70)
list_coordinates_pairs = [
(20, 25), (21, 35), (15, 16), (25, 22), (30, 32), (46, 70),
(35, 68), (42, 53), (50, 30), (43, 69), (16, 17), (14, 30),
]
points = [Point(*pairs) for pairs in list_coordinates_pairs]
mtb_packets = mtb.Mtb(device=mocked_device[PLATFORM.LUMPY])
# Verify the max distance in pixels
max_distance_px = mtb_packets.get_max_distance_from_points(points,
UNIT.PIXEL)
expected_max_distance_px = ((46 - 15) ** 2 + (70 - 16) ** 2) ** 0.5
self.assertAlmostEqual(max_distance_px, expected_max_distance_px)
# Verify the max distance in mms
max_distance_mm = mtb_packets.get_max_distance_from_points(points,
UNIT.MM)
expected_max_distance_mm = (((46 - 15) / 12.0) ** 2 +
((70 - 16) / 10.0) ** 2) ** 0.5
self.assertAlmostEqual(max_distance_mm, expected_max_distance_mm)
def _test_get_segments(self, list_t, list_coord, expected_segments, ratio):
"""Test get_segments
@param expected_segments: a dictionary of
{segment_flag: expected_segment_indexes}
"""
mtb_packets = mtb.Mtb(device=mocked_device[PLATFORM.LUMPY])
for segment_flag, (expected_segment_t, expected_segment_coord) in \
expected_segments.items():
segment_t, segment_coord = mtb_packets.get_segments(
list_t, list_coord, segment_flag, ratio)
self.assertEqual(segment_t, expected_segment_t)
self.assertEqual(segment_coord, expected_segment_coord)
def test_get_segments_by_distance(self):
"""Test get_segments_by_distance
In the test case below,
min_coord = 100
max_coord = 220
max_distance = max_coord - min_coord = 220 - 100 = 120
ratio = 0.1
120 * 0.1 = 12
begin segment: 100 ~ 112
end segment: 208 ~ 220
"""
list_coord = [102, 101, 101, 100, 100, 103, 104, 110, 118, 120,
122, 124, 131, 140, 150, 160, 190, 210, 217, 220]
list_t = [1000 + 0.012 * i for i in range(len(list_coord))]
ratio = 0.1
expected_segments= {
VAL.WHOLE: (list_t, list_coord),
VAL.MIDDLE: (list_t[8:17], list_coord[8:17]),
VAL.BEGIN: (list_t[:8], list_coord[:8]),
VAL.END: (list_t[17:], list_coord[17:]),
}
self._test_get_segments(list_t, list_coord, expected_segments, ratio)
def test_get_segments_by_length(self):
"""Test get_segments_by_length"""
list_coords = [
[105, 105, 105, 105, 105, 105, 105, 105, 105, 105],
[104, 105, 105, 105, 105, 105, 105, 105, 105, 105],
[105, 105, 105, 105, 105, 105, 105, 105, 105, 106],
]
ratio = 0.1
for list_c in list_coords:
list_t = [1000 + 0.012 * i for i in range(len(list_c))]
expected_segments= {
VAL.WHOLE: (list_t, list_c),
VAL.MIDDLE: (list_t[1:9], list_c[1:9]),
VAL.BEGIN: (list_t[:1], list_c[:1]),
VAL.END: (list_t[9:], list_c[9:]),
}
self._test_get_segments(list_t, list_c, expected_segments, ratio)
if __name__ == '__main__':
unittest.main()