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