1 // Copyright 2012 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/palm_classifying_filter_interpreter.h"
6
7 #include "include/gestures.h"
8 #include "include/interpreter.h"
9 #include "include/tracer.h"
10 #include "include/util.h"
11
12 namespace gestures {
13
PalmClassifyingFilterInterpreter(PropRegistry * prop_reg,Interpreter * next,Tracer * tracer)14 PalmClassifyingFilterInterpreter::PalmClassifyingFilterInterpreter(
15 PropRegistry* prop_reg, Interpreter* next,
16 Tracer* tracer)
17 : FilterInterpreter(nullptr, next, tracer, false),
18 palm_pressure_(prop_reg, "Palm Pressure", 200.0),
19 palm_width_(prop_reg, "Palm Width", 21.2),
20 multi_palm_width_(prop_reg, "Multiple Palm Width", 75.0),
21 fat_finger_pressure_ratio_(prop_reg, "Fat Finger Pressure Ratio", 1.4),
22 fat_finger_width_ratio_(prop_reg, "Fat Finger Width Ratio", 1.3),
23 fat_finger_min_dist_(prop_reg, "Fat Finger Min Move Distance", 15.0),
24 palm_edge_min_width_(prop_reg, "Tap Exclusion Border Width", 8.0),
25 palm_edge_width_(prop_reg, "Palm Edge Zone Width", 14.0),
26 palm_top_edge_min_width_(prop_reg, "Top Edge Tap Exclusion Border Width",
27 3.0),
28 palm_edge_point_speed_(prop_reg, "Palm Edge Zone Min Point Speed", 100.0),
29 palm_eval_timeout_(prop_reg, "Palm Eval Timeout", 0.1),
30 palm_stationary_time_(prop_reg, "Palm Stationary Time", 2.0),
31 palm_stationary_distance_(prop_reg, "Palm Stationary Distance", 4.0),
32 palm_pointing_min_dist_(prop_reg,
33 "Palm Pointing Min Move Distance",
34 8.0),
35 palm_pointing_max_reverse_dist_(prop_reg,
36 "Palm Pointing Max Reverse Move Distance",
37 0.3),
38 palm_split_max_distance_(prop_reg, "Palm Split Maximum Distance", 4.0),
39 filter_top_edge_(prop_reg, "Palm Filter Top Edge Enable", false)
40 {
41 InitName();
42 requires_metrics_ = true;
43 }
44
SyncInterpretImpl(HardwareState & hwstate,stime_t * timeout)45 void PalmClassifyingFilterInterpreter::SyncInterpretImpl(
46 HardwareState& hwstate,
47 stime_t* timeout) {
48 const char name[] = "PalmClassifyingFilterInterpreter::SyncInterpretImpl";
49 LogHardwareStatePre(name, hwstate);
50
51 FillOriginInfo(hwstate);
52 FillMaxPressureWidthInfo(hwstate);
53 UpdateDistanceInfo(hwstate);
54 UpdatePalmState(hwstate);
55 UpdatePalmFlags(hwstate);
56 FillPrevInfo(hwstate);
57
58 LogHardwareStatePost(name, hwstate);
59 if (next_.get())
60 next_->SyncInterpret(hwstate, timeout);
61 }
62
FillOriginInfo(const HardwareState & hwstate)63 void PalmClassifyingFilterInterpreter::FillOriginInfo(
64 const HardwareState& hwstate) {
65 RemoveMissingIdsFromMap(&origin_timestamps_, hwstate);
66 RemoveMissingIdsFromMap(&origin_fingerstates_, hwstate);
67 for (size_t i = 0; i < hwstate.finger_cnt; i++) {
68 const FingerState& fs = hwstate.fingers[i];
69 if (MapContainsKey(origin_timestamps_, fs.tracking_id))
70 continue;
71 origin_timestamps_[fs.tracking_id] = hwstate.timestamp;
72 origin_fingerstates_[fs.tracking_id] = fs;
73 }
74 }
75
FillPrevInfo(const HardwareState & hwstate)76 void PalmClassifyingFilterInterpreter::FillPrevInfo(
77 const HardwareState& hwstate) {
78 RemoveMissingIdsFromMap(&prev_fingerstates_, hwstate);
79 prev_time_ = hwstate.timestamp;
80 for (size_t i = 0; i < hwstate.finger_cnt; i++) {
81 const FingerState& fs = hwstate.fingers[i];
82 prev_fingerstates_[fs.tracking_id] = fs;
83 }
84 }
85
FillMaxPressureWidthInfo(const HardwareState & hwstate)86 void PalmClassifyingFilterInterpreter::FillMaxPressureWidthInfo(
87 const HardwareState& hwstate) {
88 RemoveMissingIdsFromMap(&max_pressure_, hwstate);
89 RemoveMissingIdsFromMap(&max_width_, hwstate);
90 for (size_t i = 0; i < hwstate.finger_cnt; i++) {
91 const FingerState& fs = hwstate.fingers[i];
92 int id = fs.tracking_id;
93 if (MapContainsKey(max_pressure_, id)) {
94 if (fs.pressure > max_pressure_[id])
95 max_pressure_[id] = fs.pressure;
96 if (fs.touch_major > max_width_[id])
97 max_width_[id] = fs.touch_major;
98 } else {
99 max_pressure_[id] = fs.pressure;
100 max_width_[id] = fs.touch_major;
101 }
102 }
103 }
104
UpdateDistanceInfo(const HardwareState & hwstate)105 void PalmClassifyingFilterInterpreter::UpdateDistanceInfo(
106 const HardwareState& hwstate) {
107 RemoveMissingIdsFromMap(&distance_positive_[0], hwstate);
108 RemoveMissingIdsFromMap(&distance_positive_[1], hwstate);
109 RemoveMissingIdsFromMap(&distance_negative_[0], hwstate);
110 RemoveMissingIdsFromMap(&distance_negative_[1], hwstate);
111 for (size_t i = 0; i < hwstate.finger_cnt; i++) {
112 const FingerState& fs = hwstate.fingers[i];
113 int id = fs.tracking_id;
114 if (MapContainsKey(prev_fingerstates_, id)) {
115 float delta[2];
116 delta[0] = fs.position_x - prev_fingerstates_[id].position_x;
117 delta[1] = fs.position_y - prev_fingerstates_[id].position_y;
118 for (int i = 0; i < 2; i++) {
119 if (delta[i] > 0)
120 distance_positive_[i][id] += delta[i];
121 else
122 distance_negative_[i][id] -= delta[i];
123 }
124 } else {
125 distance_positive_[0][id] = 0;
126 distance_positive_[1][id] = 0;
127 distance_negative_[0][id] = 0;
128 distance_negative_[1][id] = 0;
129 }
130 }
131 }
132
FingerNearOtherFinger(const HardwareState & hwstate,size_t finger_idx)133 bool PalmClassifyingFilterInterpreter::FingerNearOtherFinger(
134 const HardwareState& hwstate,
135 size_t finger_idx) {
136 const FingerState& fs = hwstate.fingers[finger_idx];
137 for (int i = 0; i < hwstate.finger_cnt; ++i) {
138 const FingerState& other_fs = hwstate.fingers[i];
139 if (other_fs.tracking_id == fs.tracking_id)
140 continue;
141 bool close_enough_together =
142 metrics_->CloseEnoughToGesture(Vector2(fs), Vector2(other_fs)) &&
143 !SetContainsValue(palm_, other_fs.tracking_id);
144 bool too_close_together = DistSq(fs, other_fs) <
145 palm_split_max_distance_.val_ * palm_split_max_distance_.val_;
146 if (close_enough_together && !too_close_together) {
147 was_near_other_fingers_.insert(fs.tracking_id);
148 return true;
149 }
150 }
151 return false;
152 }
153
FingerInPalmEnvelope(const FingerState & fs)154 bool PalmClassifyingFilterInterpreter::FingerInPalmEnvelope(
155 const FingerState& fs) {
156 float limit = palm_edge_min_width_.val_ +
157 (fs.pressure / palm_pressure_.val_) *
158 (palm_edge_width_.val_ - palm_edge_min_width_.val_);
159 return fs.position_x < limit ||
160 fs.position_x > (hwprops_->right - limit) ||
161 (filter_top_edge_.val_ && fs.position_y < palm_top_edge_min_width_.val_);
162 }
163
FingerInBottomArea(const FingerState & fs)164 bool PalmClassifyingFilterInterpreter::FingerInBottomArea(
165 const FingerState& fs) {
166 return fs.position_y > (hwprops_->bottom - palm_edge_min_width_.val_);
167 }
168
UpdatePalmState(const HardwareState & hwstate)169 void PalmClassifyingFilterInterpreter::UpdatePalmState(
170 const HardwareState& hwstate) {
171 RemoveMissingIdsFromSet(&palm_, hwstate);
172 RemoveMissingIdsFromSet(&large_palm_, hwstate);
173 RemoveMissingIdsFromMap(&pointing_, hwstate);
174 RemoveMissingIdsFromSet(&non_stationary_palm_, hwstate);
175 RemoveMissingIdsFromSet(&fingers_not_in_edge_, hwstate);
176 RemoveMissingIdsFromSet(&was_near_other_fingers_, hwstate);
177
178 // Some finger(s) just leaves, skip this update for stability
179 if (prev_fingerstates_.size() > hwstate.finger_cnt)
180 return;
181
182 for (short i = 0; i < hwstate.finger_cnt; i++) {
183 const FingerState& fs = hwstate.fingers[i];
184 if (!(FingerInPalmEnvelope(fs) || FingerInBottomArea(fs)))
185 fingers_not_in_edge_.insert(fs.tracking_id);
186 // Mark anything over the palm thresh as a palm
187 if (fs.pressure >= palm_pressure_.val_ ||
188 fs.touch_major >= multi_palm_width_.val_) {
189 large_palm_.insert(fs.tracking_id);
190 palm_.insert(fs.tracking_id);
191 pointing_.erase(fs.tracking_id);
192 continue;
193 }
194 // Mark externally reported palms
195 if(fs.tool_type == FingerState::ToolType::kPalm){
196 palm_.insert(fs.tracking_id);
197 pointing_.erase(fs.tracking_id);
198 }
199 }
200
201 if (hwstate.finger_cnt == 1 &&
202 hwstate.fingers[0].touch_major >= palm_width_.val_) {
203 large_palm_.insert(hwstate.fingers[0].tracking_id);
204 palm_.insert(hwstate.fingers[0].tracking_id);
205 pointing_.erase(hwstate.fingers[0].tracking_id);
206 }
207
208 const float kPalmStationaryDistSq =
209 palm_stationary_distance_.val_ * palm_stationary_distance_.val_;
210 const float kFatFingerMinDistSq =
211 fat_finger_min_dist_.val_ * fat_finger_min_dist_.val_;
212 const float kFatFingerMaxPressure =
213 palm_pressure_.val_ * fat_finger_pressure_ratio_.val_;
214 const float kFatFingerMaxWidth =
215 palm_width_.val_ * fat_finger_width_ratio_.val_;
216
217 for (short i = 0; i < hwstate.finger_cnt; i++) {
218 const FingerState& fs = hwstate.fingers[i];
219 bool prev_palm = SetContainsValue(palm_, fs.tracking_id);
220 bool prev_pointing = MapContainsKey(pointing_, fs.tracking_id);
221
222 if (prev_palm) {
223 // If the finger's pressure & width are more like a fat finger
224 // and it has moved a lot, it might be a fat finger and remove
225 // it from palm.
226 float dist_sq = DistSq(origin_fingerstates_[fs.tracking_id], fs);
227 if (max_pressure_[fs.tracking_id] <= kFatFingerMaxPressure &&
228 max_width_[fs.tracking_id] <= kFatFingerMaxWidth &&
229 dist_sq > kFatFingerMinDistSq) {
230 large_palm_.erase(fs.tracking_id);
231 palm_.erase(fs.tracking_id);
232 } else {
233 // Lock onto palm
234 continue;
235 }
236 }
237
238 // If the finger is recently placed, remove it from pointing/fingers.
239 // If it's still looking like pointing, it'll get readded.
240 if (FingerAge(fs.tracking_id, hwstate.timestamp) <
241 palm_eval_timeout_.val_) {
242 pointing_.erase(fs.tracking_id);
243
244 prev_pointing = false;
245 }
246 // If another finger is close by, let this be pointing
247 bool near_finger = FingerNearOtherFinger(hwstate, i);
248 bool on_edge = FingerInPalmEnvelope(fs) ||
249 FingerInBottomArea(fs);
250 if (!prev_pointing && (near_finger || !on_edge)) {
251 unsigned reason = (near_finger ? kPointCloseToFinger : 0) |
252 ((!on_edge) ? kPointNotInEdge : 0);
253 pointing_[fs.tracking_id] = reason;
254 }
255
256 // Check if fingers that only move within palm envelope are pointing.
257 int id = fs.tracking_id;
258 float min_dist = palm_pointing_min_dist_.val_;
259 float max_reverse_dist = palm_pointing_max_reverse_dist_.val_;
260
261 // Ideally, we want to say that a finger is pointing if it moves only in
262 // one direction significantly without zig-zag. But due to touch sensor's
263 // inaccuratcy, we make the rule to be that a finger has to move in one
264 // direction significantly with little move in the opposite direction.
265 for (size_t j = 0; j < arraysize(distance_positive_); j++)
266 if ((distance_positive_[j][id] >= min_dist &&
267 distance_negative_[j][id] <= max_reverse_dist) ||
268 (distance_positive_[j][id] <= max_reverse_dist &&
269 distance_negative_[j][id] >= min_dist)) {
270 pointing_[id] |= kPointMoving;
271 }
272
273 // However, if the contact has been stationary for a while since it
274 // touched down, it is a palm. We track a potential palm closely for the
275 // first amount of time to see if it fits this pattern.
276 if (FingerAge(fs.tracking_id, prev_time_) >
277 palm_stationary_time_.val_ ||
278 SetContainsValue(non_stationary_palm_, fs.tracking_id)) {
279 // Finger is too old to reconsider or is moving a lot
280 continue;
281 }
282 if (DistSq(origin_fingerstates_[fs.tracking_id], fs) >
283 kPalmStationaryDistSq || !(FingerInPalmEnvelope(fs) ||
284 FingerInBottomArea(fs))) {
285 // Finger moving a lot or not in palm envelope; not a stationary palm.
286 non_stationary_palm_.insert(fs.tracking_id);
287 continue;
288 }
289 if (FingerAge(fs.tracking_id, prev_time_) <=
290 palm_stationary_time_.val_ &&
291 FingerAge(fs.tracking_id, hwstate.timestamp) >
292 palm_stationary_time_.val_ &&
293 !SetContainsValue(non_stationary_palm_, fs.tracking_id) &&
294 !FingerNearOtherFinger(hwstate, i)) {
295 // Enough time has passed. Make this stationary contact a palm.
296 palm_.insert(fs.tracking_id);
297 pointing_.erase(fs.tracking_id);
298 }
299 }
300 }
301
UpdatePalmFlags(HardwareState & hwstate)302 void PalmClassifyingFilterInterpreter::UpdatePalmFlags(HardwareState& hwstate) {
303 for (short i = 0; i < hwstate.finger_cnt; i++) {
304 FingerState* fs = &hwstate.fingers[i];
305 if (SetContainsValue(large_palm_, fs->tracking_id)) {
306 fs->flags |= GESTURES_FINGER_LARGE_PALM;
307 }
308 if (SetContainsValue(palm_, fs->tracking_id)) {
309 fs->flags |= GESTURES_FINGER_PALM;
310 } else if (!MapContainsKey(pointing_, fs->tracking_id) &&
311 !SetContainsValue(was_near_other_fingers_, fs->tracking_id)) {
312 if (FingerInPalmEnvelope(*fs)) {
313 fs->flags |= GESTURES_FINGER_PALM;
314 } else if (FingerInBottomArea(*fs)) {
315 fs->flags |= (GESTURES_FINGER_WARP_X | GESTURES_FINGER_WARP_Y);
316 }
317 } else if (MapContainsKey(pointing_, fs->tracking_id) &&
318 FingerInPalmEnvelope(*fs)) {
319 fs->flags |= GESTURES_FINGER_POSSIBLE_PALM;
320 if (pointing_[fs->tracking_id] == kPointCloseToFinger &&
321 !FingerNearOtherFinger(hwstate, i)) {
322 // Finger was near another finger, but it's not anymore, and it was
323 // only this other finger that caused it to point. Mark it w/ warp
324 // until it moves sufficiently to have another reason to be
325 // pointing.
326 fs->flags |= (GESTURES_FINGER_WARP_X | GESTURES_FINGER_WARP_Y);
327 }
328 }
329 }
330 }
331
FingerAge(short finger_id,stime_t now) const332 stime_t PalmClassifyingFilterInterpreter::FingerAge(short finger_id,
333 stime_t now) const {
334 if (!MapContainsKey(origin_timestamps_, finger_id)) {
335 Err("Don't have record of finger age for finger %d", finger_id);
336 return -1;
337 }
338 return now - origin_timestamps_.at(finger_id);
339 }
340
341 } // namespace gestures
342