• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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