xref: /aosp_15_r20/external/libchrome-gestures/src/trend_classifying_filter_interpreter.cc (revision aed3e5085e770be5b69ce25295ecf6ddf906af95)
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], &current.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(&current.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