blob: a85af2ba2763c492bb1320b285a15659d27dc0a8 [file] [log] [blame]
# Copyright (c) 2014 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.
import numpy as np
import os
import unittest
from optofidelity.detection.trace import (Trace, Event, FingerEvent,
LineDrawEvent, LEDEvent,
ScreenDrawEvent)
script_dir = os.path.dirname(os.path.realpath(__file__))
class TraceBuilder(object):
"""Helper class to build fake traces.
Every Add* method will add events at a fixed report rate (1 event every 10
time units.
"""
report_rate = 1.0 / 10.0
def __init__(self, seed, sigma):
np.random.seed(seed)
self.events = []
self.time = 0
self.noise = lambda: np.random.normal(0, sigma) if sigma > 0 else 0.0
@property
def trace(self):
return Trace(self.events)
def AddLEDEvent(self, state):
self.time += int(1.0 / self.report_rate)
self.events.append(LEDEvent(self.time, state=state))
def AddScreenDraw(self, state):
self.time += int(1.0 / self.report_rate)
self.events.append(ScreenDrawEvent(self.time, state=state,
start_time = self.time - 5))
def AddFingerStationary(self, location, samples):
location = float(location)
for i in range(samples):
self.time += int(1.0 / self.report_rate)
self.events.append(FingerEvent(self.time, location + self.noise()))
def AddFingerTransition(self, start, end, samples):
for i in range(samples):
self.time += int(1.0 / self.report_rate)
location = float(start + (end - start) * i / (samples - 1))
self.events.append(FingerEvent(self.time, location + self.noise()))
def AddLineDraws(self, start, end, samples):
for i in range(samples):
self.time += int(1.0 / self.report_rate)
location = float(start + (end - start) * i / (samples - 1))
self.events.append(LineDrawEvent(self.time, location + self.noise(),
start_time = self.time - 5))
def AddLineReset(self):
self.time += int(1.0 / self.report_rate)
self.events.append(LineDrawEvent(self.time, None, start_time = self.time))
def event_time(i):
"""Returns time of event number i."""
return (i + 1) / TraceBuilder.report_rate
class TraceTests(unittest.TestCase):
def assertArrayAlmostEqual(self, a, b):
"""Checks if all array values are (nearly) equal. Including NaNs."""
a_nan = np.isnan(a)
b_nan = np.isnan(b)
self.assertTrue(np.allclose(a_nan, b_nan))
self.assertTrue(np.allclose(a[~a_nan], b[~b_nan]))
def TapTrace(self):
"""Trace that we expect from a tap gesture.
LED on, Screen to black, LED off, screen to white.
"""
builder = TraceBuilder(0, 0)
builder.AddLEDEvent(Event.STATE_ON)
builder.AddScreenDraw(Event.STATE_BLACK)
builder.AddLEDEvent(Event.STATE_OFF)
builder.AddScreenDraw(Event.STATE_WHITE)
return builder.trace
def LEDCalibTrace(self):
"""Trace that we expect from an LED calibration.
LED1 on, LED2 on, LED1 off, LED2 off.
"""
builder = TraceBuilder(0, 0)
builder.AddLEDEvent(Event.STATE_ON)
builder.AddLEDEvent(Event.STATE_ON)
builder.AddLEDEvent(Event.STATE_OFF)
builder.AddLEDEvent(Event.STATE_OFF)
return builder.trace
def FingerMotionTrace(self, seed, sigma):
"""Simple finger motion trace.
The finger moves in the following pattern:
wait at 0
move from 0 to 200
wait at 200
move from 200 to 0
wait at 0
"""
start = 0
end = 200
builder = TraceBuilder(seed, sigma)
builder.AddFingerStationary(start, 10)
builder.AddFingerTransition(start, end, 20)
builder.AddFingerStationary(end, 10)
builder.AddFingerTransition(end, start, 20)
builder.AddFingerStationary(start, 10)
return builder.trace
def LineDrawTrace(self, seed, sigma):
"""Simple trace of line draws.
Line drawing from 0 to 200, a line reset, then drawing from 200 to 0.
"""
start = 0
end = 200
builder = TraceBuilder(seed, sigma)
builder.AddLineDraws(start, end, 10)
builder.AddLineReset()
builder.AddLineDraws(end, start, 10)
return builder.trace
def assert_steady_rise_and_fall(self, time_series):
mid_point = (len(time_series) / 2)
# The first event always comes at time 10, before the values should
# be NaN
self.assertTrue(np.all(np.isnan(time_series[0:4])))
# Check if the time series is going from 0 to 200 and back to 0 again
self.assertAlmostEqual(time_series[10], 0)
self.assertAlmostEqual(time_series[mid_point], 200)
self.assertAlmostEqual(time_series[-1], 0)
# Make sure the graph is steadily growing towards the midpoint, then
# steadily falling
deriv1 = np.diff(time_series)
deriv1[np.isnan(deriv1)] = 0
self.assertTrue(np.all(deriv1[:mid_point] >= 0))
self.assertTrue(np.all(deriv1[mid_point:] <= 0))
def test_finger_time_series(self):
noiseless_finger_trace = self.FingerMotionTrace(0, 0)
time_series = noiseless_finger_trace.finger
deriv1 = np.diff(time_series)
deriv2 = np.diff(deriv1)
deriv1[np.isnan(deriv1)] = 0
deriv2[np.isnan(deriv2)] = 0
self.assert_steady_rise_and_fall(time_series)
# Make sure there are no sudden jumps, i.e. we are interpolating.
self.assertTrue(np.all(np.abs(deriv1) < 1.5))
# Count number of edges in graph, there should only be 4.
self.assertEqual(np.sum(np.abs(deriv2) > 0.5), 4)
def test_finger_interpolation(self):
events = [
FingerEvent(1, 100.0),
FingerEvent(5, 200.0),
FingerEvent(7, 300.0),
]
# Expected time series
target = np.asarray([np.nan, 100, 125, 150, 175, 200, 250, 300])
trace = Trace(events)
self.assertArrayAlmostEqual(trace.finger, target)
def test_line_draw_steps(self):
events = [
LineDrawEvent(2, 100.0, start_time=1),
LineDrawEvent(4, 200.0, start_time=3),
LineDrawEvent(6, None, start_time=5),
LineDrawEvent(8, 300.0, start_time=7),
]
trace = Trace(events)
# Expected time series
nan = np.nan
start = np.asarray([nan, 100, 100, 200, 200, nan, nan, 300, 300])
end = np.asarray([nan, nan, 100, 100, 200, 200, nan, nan, 300])
self.assertArrayAlmostEqual(trace.line_draw_start, start)
self.assertArrayAlmostEqual(trace.line_draw_end, end)
def test_line_draw_time_series(self):
noiseless_line_trace = self.LineDrawTrace(0, 0)
start_time_series = noiseless_line_trace.line_draw_start
end_time_series = noiseless_line_trace.line_draw_end
# After a line reset, the time series should show NaNs until a new line draw
# has happened
self.assertTrue(np.isnan(end_time_series[115]))
# Before any line draw, the time series should show NaNs.
self.assertTrue(np.all(np.isnan(end_time_series[0:4])))
self.assert_steady_rise_and_fall(start_time_series)
self.assert_steady_rise_and_fall(end_time_series)
# Derive and get rid of NaNs so we can calculate the number of steps
start_deriv1 = np.diff(start_time_series)
end_deriv1 = np.diff(end_time_series)
start_deriv1[np.isnan(start_deriv1)] = 0
end_deriv1[np.isnan(end_deriv1)] = 0
# Make sure we have 18 steps, i.e. non-interpolated values.
self.assertEqual(np.sum(np.abs(start_deriv1) > 1), 18)
self.assertEqual(np.sum(np.abs(end_deriv1) > 1), 18)
def test_led_event_time_series(self):
time_series = self.LEDCalibTrace().led
# start condition
self.assertAlmostEqual(time_series[0], 0)
# 1st led on
self.assertAlmostEqual(time_series[event_time(0)], 1)
# 2nd led on
self.assertAlmostEqual(time_series[event_time(1)], 2)
# 1st led off
self.assertAlmostEqual(time_series[event_time(2)], 1)
# 2nd led off
self.assertAlmostEqual(time_series[event_time(3)], 0)
def test_tap_event_time_series(self):
tap_trace = self.TapTrace()
led_ts = tap_trace.led
screen_ts = tap_trace.screen_draw_end
# start condition
self.assertAlmostEqual(led_ts[0], 0)
self.assertAlmostEqual(screen_ts[0], 0)
# led on
self.assertAlmostEqual(led_ts[event_time(0)], 1)
self.assertAlmostEqual(screen_ts[event_time(0)], 0)
# screen draws black
self.assertAlmostEqual(led_ts[event_time(1)], 1)
self.assertAlmostEqual(screen_ts[event_time(1)], 1)
# led off
self.assertAlmostEqual(led_ts[event_time(2)], 0)
self.assertAlmostEqual(screen_ts[event_time(2)], 1)
# screen draws white
self.assertAlmostEqual(led_ts[event_time(3)], 0)
self.assertAlmostEqual(screen_ts[event_time(3)], 0)
def test_find(self):
trace = self.FingerMotionTrace(0, 0)
prev_event = trace[0]
target_event = trace[1]
next_event = trace[2]
target_time = target_event.time
def assert_correct_find(time, algorithm, target):
result = trace.Find(FingerEvent, time, algorithm)
self.assertEqual(result, target)
assert_correct_find(target_time - 6, "closest", prev_event)
assert_correct_find(target_time - 4, "closest", target_event)
assert_correct_find(target_time , "closest", target_event)
assert_correct_find(target_time + 4, "closest", target_event)
assert_correct_find(target_time + 6, "closest", next_event)
assert_correct_find(target_time - 6, "before", prev_event)
assert_correct_find(target_time - 4, "before", prev_event)
assert_correct_find(target_time , "before", target_event)
assert_correct_find(target_time + 4, "before", target_event)
assert_correct_find(target_time + 6, "before", target_event)
assert_correct_find(target_time - 6, "after", target_event)
assert_correct_find(target_time - 4, "after", target_event)
assert_correct_find(target_time , "after", target_event)
assert_correct_find(target_time + 4, "after", next_event)
assert_correct_find(target_time + 6, "after", next_event)
def test_line_reset_segmentation(self):
trace = self.LineDrawTrace(0, 0)
segments = list(trace.SegmentedByLineReset())
self.assertEqual(len(segments), 2)
def test_linear_finger_motion(self):
for seed in range(20):
trace = self.FingerMotionTrace(seed, 0.2)
trace = trace.Trimmed(0, 350)
start, end = trace.FindLinearFingerMotion()
self.assertLessEqual(np.abs(start.time - 120), 10)
self.assertLessEqual(np.abs(end.time - 310), 10)
def test_stationary_finger(self):
for seed in range(20):
trace = self.FingerMotionTrace(seed, 0.2)
start, end = trace.FindStationaryFinger()
self.assertLessEqual(np.abs(start.time - 300), 10)
self.assertLessEqual(np.abs(end.time - 410), 10)
def test_finger_time_at_location(self):
trace = self.FingerMotionTrace(0, 0)
# Find finger crossing on first transition (time 100-300)
for location in range(0, 200, 5):
finger_event = trace.FindFingerCrossing(float(location), 200)
finger_time = finger_event.time
# check if detected time is during first transition
self.assertLess(finger_time, 350)
# check if the location at that time is close to the target location
self.assertLess(np.abs(trace.finger[finger_time] - location), 2.0)
# Find finger crossing on second transition (time 400-600)
for location in range(0, 200, 5):
finger_event = trace.FindFingerCrossing(float(location), 500)
finger_time = finger_event.time
# check if detected time is during second transition
self.assertGreater(finger_time, 350)
# check if the location at that time is close to the target location
self.assertLess(np.abs(trace.finger[finger_time] - location), 2.0)