1 // Copyright 2013 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "include/trend_classifying_filter_interpreter.h"
6
7 #include <cmath>
8
9 #include "include/filter_interpreter.h"
10 #include "include/finger_metrics.h"
11 #include "include/gestures.h"
12 #include "include/logging.h"
13 #include "include/prop_registry.h"
14 #include "include/tracer.h"
15 #include "include/util.h"
16
17 namespace {
18
19 // Constants for multiplication. Used in
20 // TrendClassifyingFilterInterpreter::ComputeKTVariance (see the header file).
21 const double k1_18 = 1.0 / 18.0;
22 const double k2_3 = 2.0 / 3.0;
23
24 const int kNumOfSamples = 20;
25
26 } // namespace {}
27
28 namespace gestures {
29
TrendClassifyingFilterInterpreter(PropRegistry * prop_reg,Interpreter * next,Tracer * tracer)30 TrendClassifyingFilterInterpreter::TrendClassifyingFilterInterpreter(
31 PropRegistry* prop_reg, Interpreter* next, Tracer* tracer)
32 : FilterInterpreter(nullptr, next, tracer, false),
33 trend_classifying_filter_enable_(
34 prop_reg, "Trend Classifying Filter Enabled", true),
35 second_order_enable_(
36 prop_reg, "Trend Classifying 2nd-order Motion Enabled", false),
37 min_num_of_samples_(
38 prop_reg, "Trend Classifying Min Num of Samples", 6),
39 num_of_samples_(
40 prop_reg, "Trend Classifying Num of Samples", kNumOfSamples),
41 z_threshold_(
42 prop_reg, "Trend Classifying Z Threshold", 2.5758293035489004) {
43 InitName();
44 }
45
SyncInterpretImpl(HardwareState & hwstate,stime_t * timeout)46 void TrendClassifyingFilterInterpreter::SyncInterpretImpl(
47 HardwareState& hwstate, stime_t* timeout) {
48 const char name[] = "TrendClassifyingFilterInterpreter::SyncInterpretImpl";
49 LogHardwareStatePre(name, hwstate);
50
51 if (trend_classifying_filter_enable_.val_)
52 UpdateFingerState(hwstate);
53
54 LogHardwareStatePost(name, hwstate);
55 next_->SyncInterpret(hwstate, timeout);
56 }
57
ComputeKTVariance(const int tie_n2,const int tie_n3,const size_t n_samples)58 double TrendClassifyingFilterInterpreter::ComputeKTVariance(const int tie_n2,
59 const int tie_n3, const size_t n_samples) {
60 // Replace divisions with multiplications for better performance
61 double var_n = n_samples * (n_samples - 1) * (2 * n_samples + 5) * k1_18;
62 double var_t = k2_3 * tie_n3 + tie_n2;
63 return var_n - var_t;
64 }
65
InterpretTestResult(const TrendType trend_type,const unsigned flag_increasing,const unsigned flag_decreasing,unsigned * flags)66 void TrendClassifyingFilterInterpreter::InterpretTestResult(
67 const TrendType trend_type,
68 const unsigned flag_increasing,
69 const unsigned flag_decreasing,
70 unsigned* flags) {
71 if (trend_type == TREND_INCREASING)
72 *flags |= flag_increasing;
73 else if (trend_type == TREND_DECREASING)
74 *flags |= flag_decreasing;
75 }
76
AddNewStateToBuffer(FingerHistory & history,const FingerState & fs)77 void TrendClassifyingFilterInterpreter::AddNewStateToBuffer(
78 FingerHistory& history, const FingerState& fs) {
79 // The history buffer is already full, pop one
80 if (history.size() == static_cast<size_t>(num_of_samples_.val_))
81 history.pop_front();
82
83 // Push the new finger state to the back of buffer
84 auto& current = history.emplace_back(fs);
85 if (history.size() == 1)
86 return;
87 auto& previous_end = history.at(-2);
88
89 current.DxAxis()->val = current.XAxis()->val - previous_end.XAxis()->val;
90 current.DyAxis()->val = current.YAxis()->val - previous_end.YAxis()->val;
91 // Update the nodes already in the buffer and compute the Kendall score/
92 // variance along the way. Complexity is O(|buffer|) per finger.
93 int tie_n2[KState::n_axes_] = { 0, 0, 0, 0, 0, 0 };
94 int tie_n3[KState::n_axes_] = { 0, 0, 0, 0, 0, 0 };
95 for (auto it = history.begin(); it != history.end(); ++it)
96 for (size_t i = 0; i < KState::n_axes_; i++)
97 if (it != history.begin() || !KState::IsDelta(i)) {
98 UpdateKTValuePair(&it->axes_[i], ¤t.axes_[i],
99 &tie_n2[i], &tie_n3[i]);
100 }
101 size_t n_samples = history.size();
102 for (size_t i = 0; i < KState::n_axes_; i++) {
103 current.axes_[i].var = ComputeKTVariance(tie_n2[i], tie_n3[i],
104 KState::IsDelta(i) ? n_samples - 1 : n_samples);
105 }
106 }
107
108 TrendClassifyingFilterInterpreter::TrendType
RunKTTest(const KState::KAxis * current,const size_t n_samples)109 TrendClassifyingFilterInterpreter::RunKTTest(const KState::KAxis* current,
110 const size_t n_samples) {
111 // Sample size is too small for a meaningful result
112 if (n_samples < static_cast<size_t>(min_num_of_samples_.val_))
113 return TREND_NONE;
114
115 // A zero score implies purely random behavior. Need to special-case it
116 // because the test might be fooled with a zero variance (e.g. all
117 // observations are tied).
118 if (!current->score)
119 return TREND_NONE;
120
121 // The test conduct the hypothesis test based on the fact that S/sqrt(Var(S))
122 // approximately follows the normal distribution. To optimize for speed,
123 // we reformulate the expression to drop the sqrt and division operations.
124 if (current->score * current->score <
125 z_threshold_.val_ * z_threshold_.val_ * current->var) {
126 return TREND_NONE;
127 }
128 return (current->score > 0) ? TREND_INCREASING : TREND_DECREASING;
129 }
130
UpdateFingerState(const HardwareState & hwstate)131 void TrendClassifyingFilterInterpreter::UpdateFingerState(
132 const HardwareState& hwstate) {
133 RemoveMissingIdsFromMap(&histories_, hwstate);
134
135 FingerState* fs = hwstate.fingers;
136 for (short i = 0; i < hwstate.finger_cnt; i++) {
137 // Update the map if the contact is new
138 if (!MapContainsKey(histories_, fs[i].tracking_id)) {
139 histories_[fs[i].tracking_id] = FingerHistory{};
140 }
141 auto& history = histories_[fs[i].tracking_id];
142
143 // Check if the score demonstrates statistical significance
144 AddNewStateToBuffer(history, fs[i]);
145 const auto& current = history.back();
146 const size_t n_samples = history.size();
147 for (size_t idx = 0; idx < KState::n_axes_; idx++)
148 if (second_order_enable_.val_ || !KState::IsDelta(idx)) {
149 TrendType result = RunKTTest(¤t.axes_[idx],
150 KState::IsDelta(idx) ? n_samples - 1 : n_samples);
151 InterpretTestResult(result, KState::IncFlag(idx),
152 KState::DecFlag(idx), &(fs[i].flags));
153 }
154 }
155 }
156
Init()157 void TrendClassifyingFilterInterpreter::KState::Init() {
158 for (size_t i = 0; i < KState::n_axes_; i++)
159 axes_[i].Init();
160 }
161
Init(const FingerState & fs)162 void TrendClassifyingFilterInterpreter::KState::Init(const FingerState& fs) {
163 Init();
164 XAxis()->val = fs.position_x, YAxis()->val = fs.position_y;
165 PressureAxis()->val = fs.pressure;
166 TouchMajorAxis()->val = fs.touch_major;
167 }
168
169 }
170