Home | History | Annotate | Download | only in walt
      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 {
     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 
     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      */
     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 
     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      */
     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 
    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 
    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 
    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      */
    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 
    161     static byte[] char2byte(char c) {
    162         return new byte[]{(byte) c};
    163     }
    164 
    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 
    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 
    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