Commit 5e9b2fba authored by Kris Giesing's avatar Kris Giesing Committed by kgiesing

Style pass on velocity tracker and event recorder

parent 24ea0f0d
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import "dart:math" as math; import "dart:math" as math;
import "dart:typed_data"; import "dart:typed_data";
class Vector { class _Vector {
Vector(int size) _Vector(int size)
: _offset = 0, _length = size, _elem = new Float64List(size); : _offset = 0, _length = size, _elements = new Float64List(size);
Vector.fromValues(List<double> values) _Vector.fromValues(List<double> values)
: _offset = 0, _length = values.length, _elem = values; : _offset = 0, _length = values.length, _elements = values;
Vector.fromVOL(List<double> values, int offset, int length) _Vector.fromVOL(List<double> values, int offset, int length)
: _offset = offset, _length = length, _elem = values; : _offset = offset, _length = length, _elements = values;
int get length => _length; int get length => _length;
operator [](int i) => _elem[i + _offset]; operator [](int i) => _elements[i + _offset];
operator []=(int i, double value) => _elem[i + _offset] = value; operator []=(int i, double value) => _elements[i + _offset] = value;
operator *(Vector a) { operator *(_Vector a) {
double result = 0.0; double result = 0.0;
for (int i = 0; i < _length; i++) { for (int i = 0; i < _length; i++) {
result += this[i] * a[i]; result += this[i] * a[i];
...@@ -38,28 +42,32 @@ class Vector { ...@@ -38,28 +42,32 @@ class Vector {
final int _offset; final int _offset;
final int _length; final int _length;
final List<double> _elem; final List<double> _elements;
} }
class Matrix { class _Matrix {
Matrix(int rows, int cols) _Matrix(int rows, int cols)
: _rows = rows, : _rows = rows,
_cols = cols, _columns = cols,
_elem = new Float64List(rows * cols); _elements = new Float64List(rows * cols);
double get(int row, int col) => _elem[row * _cols + col]; double get(int row, int col) => _elements[row * _columns + col];
void set(int row, int col, double value) { void set(int row, int col, double value) {
_elem[row * _cols + col] = value; _elements[row * _columns + col] = value;
} }
Vector getRow(int row) => new Vector.fromVOL(_elem, row * _cols, _cols); _Vector getRow(int row) => new _Vector.fromVOL(
_elements,
row * _columns,
_columns
);
String toString() { String toString() {
String result = ""; String result = "";
for (int i = 0; i < _rows; i++) { for (int i = 0; i < _rows; i++) {
if (i > 0) if (i > 0)
result += "; "; result += "; ";
for (int j = 0; j < _cols; j++) { for (int j = 0; j < _columns; j++) {
if (j > 0) if (j > 0)
result += ", "; result += ", ";
result += get(i, j).toString(); result += get(i, j).toString();
...@@ -69,8 +77,8 @@ class Matrix { ...@@ -69,8 +77,8 @@ class Matrix {
} }
final int _rows; final int _rows;
final int _cols; final int _columns;
final List<double> _elem; final List<double> _elements;
} }
class PolynomialFit { class PolynomialFit {
...@@ -99,10 +107,9 @@ class LeastSquaresSolver { ...@@ -99,10 +107,9 @@ class LeastSquaresSolver {
// Shorthands for the purpose of notation equivalence to original C++ code // Shorthands for the purpose of notation equivalence to original C++ code
final int m = x.length; final int m = x.length;
final int n = degree + 1; final int n = degree + 1;
final List<double> out_b = result.coefficients;
// Expand the X vector to a matrix A, pre-multiplied by the weights. // Expand the X vector to a matrix A, pre-multiplied by the weights.
Matrix a = new Matrix(n, m); _Matrix a = new _Matrix(n, m);
for (int h = 0; h < m; h++) { for (int h = 0; h < m; h++) {
a.set(0, h, w[h]); a.set(0, h, w[h]);
for (int i = 1; i < n; i++) { for (int i = 1; i < n; i++) {
...@@ -113,15 +120,15 @@ class LeastSquaresSolver { ...@@ -113,15 +120,15 @@ class LeastSquaresSolver {
// Apply the Gram-Schmidt process to A to obtain its QR decomposition. // Apply the Gram-Schmidt process to A to obtain its QR decomposition.
// Orthonormal basis, column-major ordVectorer. // Orthonormal basis, column-major ordVectorer.
Matrix q = new Matrix(n, m); _Matrix q = new _Matrix(n, m);
// Upper triangular matrix, row-major order. // Upper triangular matrix, row-major order.
Matrix r = new Matrix(n, n); _Matrix r = new _Matrix(n, n);
for (int j = 0; j < n; j++) { for (int j = 0; j < n; j++) {
for (int h = 0; h < m; h++) { for (int h = 0; h < m; h++) {
q.set(j, h, a.get(j, h)); q.set(j, h, a.get(j, h));
} }
for (int i = 0; i < j; i++) { for (int i = 0; i < j; i++) {
double dot = q.getRow(j)*q.getRow(i); double dot = q.getRow(j) * q.getRow(i);
for (int h = 0; h < m; h++) { for (int h = 0; h < m; h++) {
q.set(j, h, q.get(j, h) - dot * q.get(i, h)); q.set(j, h, q.get(j, h) - dot * q.get(i, h));
} }
...@@ -133,56 +140,57 @@ class LeastSquaresSolver { ...@@ -133,56 +140,57 @@ class LeastSquaresSolver {
return null; return null;
} }
double invNorm = 1.0 / norm; double inverseNorm = 1.0 / norm;
for (int h = 0; h < m; h++) { for (int h = 0; h < m; h++) {
q.set(j, h, q.get(j, h) * invNorm); q.set(j, h, q.get(j, h) * inverseNorm);
} }
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
r.set(j, i, i < j ? 0.0 : q.getRow(j)*a.getRow(i)); r.set(j, i, i < j ? 0.0 : q.getRow(j) * a.getRow(i));
} }
} }
// Solve R B = Qt W Y to find B. This is easy because R is upper triangular. // Solve R B = Qt W Y to find B. This is easy because R is upper triangular.
// We just work from bottom-right to top-left calculating B's coefficients. // We just work from bottom-right to top-left calculating B's coefficients.
Vector wy = new Vector(m); _Vector wy = new _Vector(m);
for (int h = 0; h < m; h++) { for (int h = 0; h < m; h++) {
wy[h] = y[h] * w[h]; wy[h] = y[h] * w[h];
} }
for (int i = n; i-- != 0;) { for (int i = n; i-- != 0;) {
out_b[i] = q.getRow(i) * wy; result.coefficients[i] = q.getRow(i) * wy;
for (int j = n - 1; j > i; j--) { for (int j = n - 1; j > i; j--) {
out_b[i] -= r.get(i, j) * out_b[j]; result.coefficients[i] -= r.get(i, j) * result.coefficients[j];
} }
out_b[i] /= r.get(i, i); result.coefficients[i] /= r.get(i, i);
} }
// Calculate the coefficient of determination as 1 - (SSerr / SStot) where // Calculate the coefficient of determination (confidence) as:
// SSerr is the residual sum of squares (variance of the error), // 1 - (sumSquaredError / sumSquaredTotal)
// and SStot is the total sum of squares (variance of the data) where each // where sumSquaredError is the residual sum of squares (variance of the
// has been weighted. // error), and sumSquaredTotal is the total sum of squares (variance of the
double ymean = 0.0; // data) where each has been weighted.
double yMean = 0.0;
for (int h = 0; h < m; h++) { for (int h = 0; h < m; h++) {
ymean += y[h]; yMean += y[h];
} }
ymean /= m; yMean /= m;
double sserr = 0.0; double sumSquaredError = 0.0;
double sstot = 0.0; double sumSquaredTotal = 0.0;
for (int h = 0; h < m; h++) { for (int h = 0; h < m; h++) {
double err = y[h] - out_b[0]; double err = y[h] - result.coefficients[0];
double term = 1.0; double term = 1.0;
for (int i = 1; i < n; i++) { for (int i = 1; i < n; i++) {
term *= x[h]; term *= x[h];
err -= term * out_b[i]; err -= term * result.coefficients[i];
} }
sserr += w[h] * w[h] * err * err; sumSquaredError += w[h] * w[h] * err * err;
double v = y[h] - ymean; double v = y[h] - yMean;
sstot += w[h] * w[h] * v * v; sumSquaredTotal += w[h] * w[h] * v * v;
} }
double det = sstot > 0.000001 ? 1.0 - (sserr / sstot) : 1.0; result.confidence = sumSquaredTotal > 0.000001 ?
1.0 - (sumSquaredError / sumSquaredTotal) :
result.confidence = det; 1.0;
return result; return result;
} }
......
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'lsq_solver.dart'; import 'lsq_solver.dart';
...@@ -10,62 +14,62 @@ class GestureVelocity { ...@@ -10,62 +14,62 @@ class GestureVelocity {
final double y; final double y;
} }
class Estimator { class _Estimator {
int degree; int degree;
double time; double time;
List<double> xcoeff; List<double> xCoefficients;
List<double> ycoeff; List<double> yCoefficients;
double confidence; double confidence;
String toString() { String toString() {
String result = "Estimator(degree: " + degree.toString(); String result = "Estimator(degree: " + degree.toString();
result += ", time: " + time.toString(); result += ", time: " + time.toString();
result += ", confidence: " + confidence.toString(); result += ", confidence: " + confidence.toString();
result += ", xcoeff: " + (new Vector.fromValues(xcoeff)).toString(); result += ", xCoefficients: " + xCoefficients.toString();
result += ", ycoeff: " + (new Vector.fromValues(ycoeff)).toString(); result += ", yCoefficients: " + yCoefficients.toString();
return result; return result;
} }
} }
abstract class VelocityTrackerStrategy { abstract class _VelocityTrackerStrategy {
void addMovement(double timeStamp, double x, double y); void addMovement(double timeStamp, double x, double y);
bool getEstimator(Estimator estimator); bool getEstimator(_Estimator estimator);
void clear(); void clear();
} }
enum Weighting { enum _Weighting {
WEIGHTING_NONE, weightingNone,
WEIGHTING_DELTA, weightingDelta,
WEIGHTING_CENTRAL, weightingCentral,
WEIGHTING_RECENT weightingRecent
} }
class Movement { class _Movement {
double event_time = 0.0; double eventTime = 0.0;
ui.Point position = ui.Point.origin; ui.Point position = ui.Point.origin;
} }
class LeastSquaresVelocityTrackerStrategy extends VelocityTrackerStrategy { class _LeastSquaresVelocityTrackerStrategy extends _VelocityTrackerStrategy {
static const int kHistorySize = 20; static const int kHistorySize = 20;
static const int kHorizonMS = 100; static const int kHorizonMilliseconds = 100;
LeastSquaresVelocityTrackerStrategy(this.degree, this.weighting) _LeastSquaresVelocityTrackerStrategy(this.degree, this.weighting)
: _index = 0, _movements = new List<Movement>(kHistorySize); : _index = 0, _movements = new List<_Movement>(kHistorySize);
final int degree; final int degree;
final Weighting weighting; final _Weighting weighting;
final List<Movement> _movements; final List<_Movement> _movements;
int _index; int _index;
void addMovement(double timeStamp, double x, double y) { void addMovement(double timeStamp, double x, double y) {
if (++_index == kHistorySize) if (++_index == kHistorySize)
_index = 0; _index = 0;
Movement movement = _getMovement(_index); _Movement movement = _getMovement(_index);
movement.event_time = timeStamp; movement.eventTime = timeStamp;
movement.position = new ui.Point(x, y); movement.position = new ui.Point(x, y);
} }
bool getEstimator(Estimator estimator) { bool getEstimator(_Estimator estimator) {
// Iterate over movement samples in reverse time order and collect samples. // Iterate over movement samples in reverse time order and collect samples.
List<double> x = new List<double>(); List<double> x = new List<double>();
List<double> y = new List<double>(); List<double> y = new List<double>();
...@@ -73,12 +77,12 @@ class LeastSquaresVelocityTrackerStrategy extends VelocityTrackerStrategy { ...@@ -73,12 +77,12 @@ class LeastSquaresVelocityTrackerStrategy extends VelocityTrackerStrategy {
List<double> time = new List<double>(); List<double> time = new List<double>();
int m = 0; int m = 0;
int index = _index; int index = _index;
Movement newest_movement = _getMovement(index); _Movement newestMovement = _getMovement(index);
do { do {
Movement movement = _getMovement(index); _Movement movement = _getMovement(index);
double age = newest_movement.event_time - movement.event_time; double age = newestMovement.eventTime - movement.eventTime;
if (age > kHorizonMS) if (age > kHorizonMilliseconds)
break; break;
ui.Point position = movement.position; ui.Point position = movement.position;
...@@ -104,9 +108,9 @@ class LeastSquaresVelocityTrackerStrategy extends VelocityTrackerStrategy { ...@@ -104,9 +108,9 @@ class LeastSquaresVelocityTrackerStrategy extends VelocityTrackerStrategy {
LeastSquaresSolver ySolver = new LeastSquaresSolver(time, y, w); LeastSquaresSolver ySolver = new LeastSquaresSolver(time, y, w);
PolynomialFit yFit = ySolver.solve(n); PolynomialFit yFit = ySolver.solve(n);
if (yFit != null) { if (yFit != null) {
estimator.xcoeff = xFit.coefficients; estimator.xCoefficients = xFit.coefficients;
estimator.ycoeff = yFit.coefficients; estimator.yCoefficients = yFit.coefficients;
estimator.time = newest_movement.event_time; estimator.time = newestMovement.eventTime;
estimator.degree = n; estimator.degree = n;
estimator.confidence = xFit.confidence * yFit.confidence; estimator.confidence = xFit.confidence * yFit.confidence;
return true; return true;
...@@ -116,9 +120,9 @@ class LeastSquaresVelocityTrackerStrategy extends VelocityTrackerStrategy { ...@@ -116,9 +120,9 @@ class LeastSquaresVelocityTrackerStrategy extends VelocityTrackerStrategy {
// No velocity data available for this pointer, but we do have its current // No velocity data available for this pointer, but we do have its current
// position. // position.
estimator.xcoeff = [ x[0] ]; estimator.xCoefficients = [ x[0] ];
estimator.ycoeff = [ y[0] ]; estimator.yCoefficients = [ y[0] ];
estimator.time = newest_movement.event_time; estimator.time = newestMovement.eventTime;
estimator.degree = 0; estimator.degree = 0;
estimator.confidence = 1.0; estimator.confidence = 1.0;
return true; return true;
...@@ -130,7 +134,7 @@ class LeastSquaresVelocityTrackerStrategy extends VelocityTrackerStrategy { ...@@ -130,7 +134,7 @@ class LeastSquaresVelocityTrackerStrategy extends VelocityTrackerStrategy {
double _chooseWeight(int index) { double _chooseWeight(int index) {
switch (weighting) { switch (weighting) {
case Weighting.WEIGHTING_DELTA: case _Weighting.weightingDelta:
// Weight points based on how much time elapsed between them and the next // Weight points based on how much time elapsed between them and the next
// point so that points that "cover" a shorter time span are weighed less. // point so that points that "cover" a shorter time span are weighed less.
// delta 0ms: 0.5 // delta 0ms: 0.5
...@@ -138,61 +142,61 @@ class LeastSquaresVelocityTrackerStrategy extends VelocityTrackerStrategy { ...@@ -138,61 +142,61 @@ class LeastSquaresVelocityTrackerStrategy extends VelocityTrackerStrategy {
if (index == _index) { if (index == _index) {
return 1.0; return 1.0;
} }
int next_index = (index + 1) % kHistorySize; int nextIndex = (index + 1) % kHistorySize;
double delta_millis = _movements[next_index].event_time - double deltaMilliseconds = _movements[nextIndex].eventTime -
_movements[index].event_time; _movements[index].eventTime;
if (delta_millis < 0) if (deltaMilliseconds < 0)
return 0.5; return 0.5;
if (delta_millis < 10) if (deltaMilliseconds < 10)
return 0.5 + delta_millis * 0.05; return 0.5 + deltaMilliseconds * 0.05;
return 1.0; return 1.0;
case Weighting.WEIGHTING_CENTRAL: case _Weighting.weightingCentral:
// Weight points based on their age, weighing very recent and very old // Weight points based on their age, weighing very recent and very old
// points less. // points less.
// age 0ms: 0.5 // age 0ms: 0.5
// age 10ms: 1.0 // age 10ms: 1.0
// age 50ms: 1.0 // age 50ms: 1.0
// age 60ms: 0.5 // age 60ms: 0.5
double age_millis = _movements[_index].event_time - double ageMilliseconds = _movements[_index].eventTime -
_movements[index].event_time; _movements[index].eventTime;
if (age_millis < 0) if (ageMilliseconds < 0)
return 0.5; return 0.5;
if (age_millis < 10) if (ageMilliseconds < 10)
return 0.5 + age_millis * 0.05; return 0.5 + ageMilliseconds * 0.05;
if (age_millis < 50) if (ageMilliseconds < 50)
return 1.0; return 1.0;
if (age_millis < 60) if (ageMilliseconds < 60)
return 0.5 + (60 - age_millis) * 0.05; return 0.5 + (60 - ageMilliseconds) * 0.05;
return 0.5; return 0.5;
case Weighting.WEIGHTING_RECENT: case _Weighting.weightingRecent:
// Weight points based on their age, weighing older points less. // Weight points based on their age, weighing older points less.
// age 0ms: 1.0 // age 0ms: 1.0
// age 50ms: 1.0 // age 50ms: 1.0
// age 100ms: 0.5 // age 100ms: 0.5
double age_millis = _movements[_index].event_time - double ageMilliseconds = _movements[_index].eventTime -
_movements[index].event_time; _movements[index].eventTime;
if (age_millis < 50) { if (ageMilliseconds < 50) {
return 1.0; return 1.0;
} }
if (age_millis < 100) { if (ageMilliseconds < 100) {
return 0.5 + (100 - age_millis) * 0.01; return 0.5 + (100 - ageMilliseconds) * 0.01;
} }
return 0.5; return 0.5;
case Weighting.WEIGHTING_NONE: case _Weighting.weightingNone:
default: default:
return 1.0; return 1.0;
} }
} }
Movement _getMovement(int i) { _Movement _getMovement(int i) {
Movement result = _movements[i]; _Movement result = _movements[i];
if (result == null) { if (result == null) {
result = new Movement(); result = new _Movement();
_movements[i] = result; _movements[i] = result;
} }
return result; return result;
...@@ -206,7 +210,7 @@ class VelocityTracker { ...@@ -206,7 +210,7 @@ class VelocityTracker {
VelocityTracker() : _lastTimeStamp = 0.0, _strategy = _createStrategy(); VelocityTracker() : _lastTimeStamp = 0.0, _strategy = _createStrategy();
double _lastTimeStamp; double _lastTimeStamp;
VelocityTrackerStrategy _strategy; _VelocityTrackerStrategy _strategy;
void addPosition(double timeStamp, double x, double y) { void addPosition(double timeStamp, double x, double y) {
if ((timeStamp - _lastTimeStamp) >= kAssumePointerMoveStoppedTimeMs) if ((timeStamp - _lastTimeStamp) >= kAssumePointerMoveStoppedTimeMs)
...@@ -216,19 +220,19 @@ class VelocityTracker { ...@@ -216,19 +220,19 @@ class VelocityTracker {
} }
GestureVelocity getVelocity() { GestureVelocity getVelocity() {
Estimator estimator = new Estimator(); _Estimator estimator = new _Estimator();
if (_strategy.getEstimator(estimator) && estimator.degree >= 1) { if (_strategy.getEstimator(estimator) && estimator.degree >= 1) {
// convert from pixels/ms to pixels/s // convert from pixels/ms to pixels/s
return new GestureVelocity( return new GestureVelocity(
isValid: true, isValid: true,
x: estimator.xcoeff[1]*1000, x: estimator.xCoefficients[1] * 1000,
y: estimator.ycoeff[1]*1000 y: estimator.yCoefficients[1] * 1000
); );
} }
return new GestureVelocity(isValid: false, x: 0.0, y: 0.0); return new GestureVelocity(isValid: false, x: 0.0, y: 0.0);
} }
static VelocityTrackerStrategy _createStrategy() { static _VelocityTrackerStrategy _createStrategy() {
return new LeastSquaresVelocityTrackerStrategy(2, Weighting.WEIGHTING_NONE); return new _LeastSquaresVelocityTrackerStrategy(2, _Weighting.weightingNone);
} }
} }
...@@ -13,8 +13,14 @@ enum EventRecorderMode { ...@@ -13,8 +13,14 @@ enum EventRecorderMode {
record record
} }
typedef void EventsReady(List<PointerInputEvent> events); typedef void EventsReadyCallback(List<PointerInputEvent> events);
/// EventRecorder is a utility widget that allows input events occurring
/// on the child to be recorded. The widget is initially in the "stop" state
/// by default. When in the "record" state, all pointer input events
/// occurring on the child are recorded into a buffer. When the "stop" state
/// is entered again, the onEventsReady callback is invoked with a list of
/// the recorded events.
class EventRecorder extends StatefulComponent { class EventRecorder extends StatefulComponent {
EventRecorder({ EventRecorder({
Key key, Key key,
...@@ -25,32 +31,29 @@ class EventRecorder extends StatefulComponent { ...@@ -25,32 +31,29 @@ class EventRecorder extends StatefulComponent {
final Widget child; final Widget child;
final EventRecorderMode mode; final EventRecorderMode mode;
final EventsReady onEventsReady; final EventsReadyCallback onEventsReady;
_EventRecorderState createState() => new _EventRecorderState(); _EventRecorderState createState() => new _EventRecorderState();
} }
class _EventRecorderState extends State<EventRecorder> { class _EventRecorderState extends State<EventRecorder> {
EventRecorderMode _mode;
List<PointerInputEvent> _events = new List<PointerInputEvent>(); List<PointerInputEvent> _events = new List<PointerInputEvent>();
void initState() { void initState() {
super.initState(); super.initState();
_mode = config.mode;
} }
void didUpdateConfig(EventRecorder oldConfig) { void didUpdateConfig(EventRecorder oldConfig) {
if (_mode == EventRecorderMode.record && if (oldConfig.mode == EventRecorderMode.record &&
config.mode == EventRecorderMode.stop) { config.mode == EventRecorderMode.stop) {
config.onEventsReady(_events); config.onEventsReady(_events);
_events.clear(); _events.clear();
} }
_mode = config.mode;
} }
void _recordEvent(PointerInputEvent event) { void _recordEvent(PointerInputEvent event) {
if (_mode == EventRecorderMode.record) { if (config.mode == EventRecorderMode.record) {
_events.add(event); _events.add(event);
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment