/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.chromium.latency.walt; import android.content.Context; import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.support.annotation.StringRes; import com.github.mikephil.charting.data.Entry; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * Kitchen sink for small utility functions */ public class Utils { public static double median(ArrayList<Double> arrList) { ArrayList<Double> lst = new ArrayList<>(arrList); Collections.sort(lst); int len = lst.size(); if (len == 0) { return Double.NaN; } if (len % 2 == 1) { return lst.get(len / 2); } else { return 0.5 * (lst.get(len / 2) + lst.get(len / 2 - 1)); } } public static double mean(double[] x) { double s = 0; for (double v: x) s += v; return s / x.length; } /** * Linear interpolation styled after numpy.interp() * returns values at points x interpolated using xp, yp data points * Both x and xp must be monotonically increasing. */ public static double[] interp(double[] x, double[] xp, double[] yp) { // assuming that x and xp are already sorted. // go over x and xp as if we are merging them double[] y = new double[x.length]; int i = 0; int ip = 0; // skip x points that are outside the data while (i < x.length && x[i] < xp[0]) i++; while (ip < xp.length && i < x.length) { // skip until we see an xp >= current x while (ip < xp.length && xp[ip] < x[i]) ip++; if (ip >= xp.length) break; if (xp[ip] == x[i]) { y[i] = yp[ip]; } else { double dy = yp[ip] - yp[ip-1]; double dx = xp[ip] - xp[ip-1]; y[i] = yp[ip-1] + dy/dx * (x[i] - xp[ip-1]); } i++; } return y; } public static double stdev(double[] a) { double m = mean(a); double sumsq = 0; for (double v : a) sumsq += (v-m)*(v-m); return Math.sqrt(sumsq / a.length); } /** * Similar to numpy.extract() * returns a shorter array with values taken from x at indices where indicator == value */ public static double[] extract(int[] indicator, int value, double[] arr) { if (arr.length != indicator.length) { throw new IllegalArgumentException("Length of arr and indicator must be the same."); } int newLen = 0; for (int v: indicator) if (v == value) newLen++; double[] newx = new double[newLen]; int j = 0; for (int i=0; i<arr.length; i++) { if (indicator[i] == value) { newx[j] = arr[i]; j++; } } return newx; } public static String array2string(double[] a, String format) { StringBuilder sb = new StringBuilder(); sb.append("array(["); for (double x: a) { sb.append(String.format(format, x)); sb.append(", "); } sb.append("])"); return sb.toString(); } public static int argmax(double[] a) { int imax = 0; for (int i=1; i<a.length; i++) if (a[i] > a[imax]) imax = i; return imax; } public static int argmin(double[] a) { int imin = 0; for (int i=1; i<a.length; i++) if (a[i] < a[imin]) imin = i; return imin; } private static double getShiftError(double[] laserT, double[] touchT, double[] touchY, double shift) { double[] T = new double[laserT.length]; for (int j=0; j<T.length; j++) { T[j] = laserT[j] + shift; } double [] laserY = Utils.interp(T, touchT, touchY); // TODO: Think about throwing away a percentile of most distanced points for noise reduction return Utils.stdev(laserY); } /** * Simplified Java re-implementation or py/qslog/minimization.py. * This is very specific to the drag latency algorithm. * * tl;dr: Shift laser events by some time delta and see how well they fit on a horizontal line. * Delta that results in the best looking straight line is the latency. */ public static double findBestShift(double[] laserT, double[] touchT, double[] touchY) { int steps = 1500; double[] shiftSteps = new double[]{0.1, 0.01}; // milliseconds double[] stddevs = new double[steps]; double bestShift = shiftSteps[0]*steps/2; for (final double shiftStep : shiftSteps) { for (int i = 0; i < steps; i++) { stddevs[i] = getShiftError(laserT, touchT, touchY, bestShift + shiftStep * i - shiftStep * steps / 2); } bestShift = argmin(stddevs) * shiftStep + bestShift - shiftStep * steps / 2; } return bestShift; } static byte[] char2byte(char c) { return new byte[]{(byte) c}; } static int getIntPreference(Context context, @StringRes int keyId, int defaultValue) { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); return preferences.getInt(context.getString(keyId), defaultValue); } static boolean getBooleanPreference(Context context, @StringRes int keyId, boolean defaultValue) { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); return preferences.getBoolean(context.getString(keyId), defaultValue); } static String getStringPreference(Context context, @StringRes int keyId, String defaultValue) { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); return preferences.getString(context.getString(keyId), defaultValue); } static float min(List<Entry> entries) { float min = Float.MAX_VALUE; for (Entry e : entries) { min = Math.min(min, e.getY()); } return min; } static float max(List<Entry> entries) { float max = Float.MIN_VALUE; for (Entry e : entries) { max = Math.max(max, e.getY()); } return max; } static float mean(List<Entry> entries) { float mean = 0; for (Entry e : entries) { mean += e.getY()/entries.size(); } return mean; } public enum ListenerState { RUNNING, STARTING, STOPPED, STOPPING } }