1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.chromium.latency.walt; 18 19 import android.content.Context; 20 import android.content.SharedPreferences; 21 import android.preference.PreferenceManager; 22 import android.support.annotation.StringRes; 23 24 import java.util.ArrayList; 25 import java.util.Collections; 26 27 /** 28 * Kitchen sink for small utility functions 29 */ 30 public class Utils { median(ArrayList<Double> arrList)31 public static double median(ArrayList<Double> arrList) { 32 ArrayList<Double> lst = new ArrayList<>(arrList); 33 Collections.sort(lst); 34 int len = lst.size(); 35 if (len == 0) { 36 return Double.NaN; 37 } 38 39 if (len % 2 == 1) { 40 return lst.get(len / 2); 41 } else { 42 return 0.5 * (lst.get(len / 2) + lst.get(len / 2 - 1)); 43 } 44 } 45 mean(double[] x)46 public static double mean(double[] x) { 47 double s = 0; 48 for (double v: x) s += v; 49 return s / x.length; 50 } 51 52 /** 53 * Linear interpolation styled after numpy.interp() 54 * returns values at points x interpolated using xp, yp data points 55 * Both x and xp must be monotonically increasing. 56 */ interp(double[] x, double[] xp, double[] yp)57 public static double[] interp(double[] x, double[] xp, double[] yp) { 58 // assuming that x and xp are already sorted. 59 // go over x and xp as if we are merging them 60 double[] y = new double[x.length]; 61 int i = 0; 62 int ip = 0; 63 64 // skip x points that are outside the data 65 while (i < x.length && x[i] < xp[0]) i++; 66 67 while (ip < xp.length && i < x.length) { 68 // skip until we see an xp >= current x 69 while (ip < xp.length && xp[ip] < x[i]) ip++; 70 if (ip >= xp.length) break; 71 if (xp[ip] == x[i]) { 72 y[i] = yp[ip]; 73 } else { 74 double dy = yp[ip] - yp[ip-1]; 75 double dx = xp[ip] - xp[ip-1]; 76 y[i] = yp[ip-1] + dy/dx * (x[i] - xp[ip-1]); 77 } 78 i++; 79 } 80 return y; 81 } 82 stdev(double[] a)83 public static double stdev(double[] a) { 84 double m = mean(a); 85 double sumsq = 0; 86 for (double v : a) sumsq += (v-m)*(v-m); 87 return Math.sqrt(sumsq / a.length); 88 } 89 90 /** 91 * Similar to numpy.extract() 92 * returns a shorter array with values taken from x at indices where indicator == value 93 */ extract(int[] indicator, int value, double[] arr)94 public static double[] extract(int[] indicator, int value, double[] arr) { 95 if (arr.length != indicator.length) { 96 throw new IllegalArgumentException("Length of arr and indicator must be the same."); 97 } 98 int newLen = 0; 99 for (int v: indicator) if (v == value) newLen++; 100 double[] newx = new double[newLen]; 101 102 int j = 0; 103 for (int i=0; i<arr.length; i++) { 104 if (indicator[i] == value) { 105 newx[j] = arr[i]; 106 j++; 107 } 108 } 109 return newx; 110 } 111 array2string(double[] a, String format)112 public static String array2string(double[] a, String format) { 113 StringBuilder sb = new StringBuilder(); 114 sb.append("array(["); 115 for (double x: a) { 116 sb.append(String.format(format, x)); 117 sb.append(", "); 118 } 119 sb.append("])"); 120 return sb.toString(); 121 } 122 123 argmin(double[] a)124 public static int argmin(double[] a) { 125 int imin = 0; 126 for (int i=1; i<a.length; i++) if (a[i] < a[imin]) imin = i; 127 return imin; 128 } 129 getShiftError(double[] laserT, double[] touchT, double[] touchY, double shift)130 private static double getShiftError(double[] laserT, double[] touchT, double[] touchY, double shift) { 131 double[] T = new double[laserT.length]; 132 for (int j=0; j<T.length; j++) { 133 T[j] = laserT[j] + shift; 134 } 135 double [] laserY = Utils.interp(T, touchT, touchY); 136 // TODO: Think about throwing away a percentile of most distanced points for noise reduction 137 return Utils.stdev(laserY); 138 } 139 140 /** 141 * Simplified Java re-implementation or py/qslog/minimization.py. 142 * This is very specific to the drag latency algorithm. 143 * 144 * tl;dr: Shift laser events by some time delta and see how well they fit on a horizontal line. 145 * Delta that results in the best looking straight line is the latency. 146 */ findBestShift(double[] laserT, double[] touchT, double[] touchY)147 public static double findBestShift(double[] laserT, double[] touchT, double[] touchY) { 148 int steps = 1500; 149 double[] shiftSteps = new double[]{0.1, 0.01}; // milliseconds 150 double[] stddevs = new double[steps]; 151 double bestShift = shiftSteps[0]*steps/2; 152 for (final double shiftStep : shiftSteps) { 153 for (int i = 0; i < steps; i++) { 154 stddevs[i] = getShiftError(laserT, touchT, touchY, bestShift + shiftStep * i - shiftStep * steps / 2); 155 } 156 bestShift = argmin(stddevs) * shiftStep + bestShift - shiftStep * steps / 2; 157 } 158 return bestShift; 159 } 160 char2byte(char c)161 static byte[] char2byte(char c) { 162 return new byte[]{(byte) c}; 163 } 164 getIntPreference(Context context, @StringRes int keyId, int defaultValue)165 static int getIntPreference(Context context, @StringRes int keyId, int defaultValue) { 166 SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); 167 return preferences.getInt(context.getString(keyId), defaultValue); 168 } 169 getBooleanPreference(Context context, @StringRes int keyId, boolean defaultValue)170 static boolean getBooleanPreference(Context context, @StringRes int keyId, boolean defaultValue) { 171 SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); 172 return preferences.getBoolean(context.getString(keyId), defaultValue); 173 } 174 getStringPreference(Context context, @StringRes int keyId, String defaultValue)175 static String getStringPreference(Context context, @StringRes int keyId, String defaultValue) { 176 SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); 177 return preferences.getString(context.getString(keyId), defaultValue); 178 } 179 180 public enum ListenerState { 181 RUNNING, 182 STARTING, 183 STOPPED, 184 STOPPING 185 } 186 187 } 188