blob: ab6c4957e8c69cb8164cdf62943ddca5ee4565a1 [file] [log] [blame]
 # Copyright 2016 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. # # Module for computing drag latency given logs of touchpad positions and # QuickStep laser crossing timestamps import numpy import latency_measurement as lm debug_mode = False def load_laser_data(fname_laser): laser_data = numpy.loadtxt(fname_laser) t = laser_data[:, 0] transition = laser_data[:, 1].astype(int) if transition[0] != 0: print('WARNING: First laser transition should be from light to dark') return t, transition def calc_ssr(x, y): """Return sum of squared residuals (SSR) of a linear least square fit""" p = numpy.polyfit(x, y, 1, full=True) r = p[1][0] return r def minimize_lsq(t, x, y, tl, min_shift, max_shift, step): """Find best time shift so that the shifted laser crossing events fit nicely on a straight line. Upper and lower side are treated separately. """ # generate an array of all shifts to try shifts = numpy.arange(min_shift, max_shift, step) # side = [0, 1, 1, 0, 0, 1, 1 ... # this is an indicator of which side of the beam the crossing belongs to side = ((numpy.arange(len(tl)) + 1) / 2) % 2 residuals0 = [] residuals1 = [] for shift in shifts: # Find the locations of the finger at the shifted laser timestamps yl = numpy.interp(tl + shift, t, y) xl = numpy.interp(tl + shift, t, x) # Fit a line to each side separately and save the SSR for this fit residuals0.append(calc_ssr(xl[side == 0], yl[side == 0])) residuals1.append(calc_ssr(xl[side == 1], yl[side == 1])) # Find the shift with lower SSR for each side best_shift0 = shifts[numpy.argmin(residuals0)] best_shift1 = shifts[numpy.argmin(residuals1)] # Use average of the two sides best_shift = (best_shift0 + best_shift1) / 2 return best_shift def minimize(fname_touch, fname_laser): # Load all the data tl, transition = load_laser_data(fname_laser) positions = lm.get_finger_positions(fname_touch) t = numpy.array([p.timestamp for p in positions]) x = numpy.array([p.x for p in positions]) y = numpy.array([p.y for p in positions]) # Shift time so that first time point is 0 t0 = t[0] t = t - t0 tl = tl - t0 # Sanity checks if numpy.std(x)*2 < numpy.std(y): print('WARNING: Not enough motion in X axis') # Search for minimum with coarse step of 1 ms in range of 0 to 200 ms coarse_step = 1e-3 # Seconds best_shift_coarse = minimize_lsq(t, x, y, tl, 0, 0.2, coarse_step) # Run another search with 0.02 ms step within +-3 ms of the previous result lmts = numpy.array([-1, 1]) * 3 * coarse_step + best_shift_coarse fine_step = 2e-5 # seconds best_shift_fine = minimize_lsq(t, x, y, tl, lmts[0], lmts[1], fine_step) print("Drag latency (min method) = %.2f ms" % (best_shift_fine*1000)) if debug_mode: debug_plot(t, x, y, tl, best_shift_fine) return best_shift_fine def debug_plot(t, x, y, tl, shift): """Plot the XY data with time-shifted laser events Note: this is a utility function used for offline debugging. It needs matplotlib which is not installed on CrOS images. """ import matplotlib.pyplot as plt plt.plot(x, y, '.b') yl = numpy.interp(tl + shift, t, y) xl = numpy.interp(tl + shift, t, x) sides = (((numpy.arange(len(tl)) + 1) / 2) % 2) colors = ['g', 'm'] x_linear = numpy.array([min(x), max(x)]) for side in [0, 1]: xls = xl[sides == side] yls = yl[sides == side] plt.plot(xls, yls, 'o' + colors[side]) a, c = numpy.polyfit(xls, yls, 1) plt.plot(x_linear, a * x_linear + c, colors[side]) plt.xlabel('X') plt.ylabel('Y') plt.title('Laser events shifted %.2f ms' % (shift*1000)) plt.show()