blob: f9358529509ccfab7505d5988924f8cb890f8eb2 [file] [log] [blame]
// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "gestures/include/palm_classifying_filter_interpreter.h"
#include <base/memory/scoped_ptr.h>
#include "gestures/include/gestures.h"
#include "gestures/include/interpreter.h"
#include "gestures/include/tracer.h"
#include "gestures/include/util.h"
namespace gestures {
PalmClassifyingFilterInterpreter::PalmClassifyingFilterInterpreter(
PropRegistry* prop_reg, Interpreter* next, FingerMetrics* finger_metrics,
Tracer* tracer)
: FilterInterpreter(next, tracer),
finger_metrics_(finger_metrics),
palm_pressure_(prop_reg, "Palm Pressure", 200.0),
palm_width_(prop_reg, "Palm Width", 21.2),
palm_edge_min_width_(prop_reg, "Tap Exclusion Border Width", 8.0),
palm_edge_width_(prop_reg, "Palm Edge Zone Width", 14.0),
palm_edge_point_speed_(prop_reg, "Palm Edge Zone Min Point Speed", 100.0),
palm_eval_timeout_(prop_reg, "Palm Eval Timeout", 0.1),
palm_stationary_time_(prop_reg, "Palm Stationary Time", 2.0),
palm_stationary_distance_(prop_reg, "Palm Stationary Distance", 4.0),
palm_pointing_min_dist_(prop_reg,
"Palm Pointing Min Move Distance",
8.0),
palm_pointing_max_reverse_dist_(prop_reg,
"Palm Pointing Max Reverse Move Distance",
0.3)
{
InitName();
if (!finger_metrics_) {
test_finger_metrics_.reset(new FingerMetrics(prop_reg));
finger_metrics_ = test_finger_metrics_.get();
}
}
Gesture* PalmClassifyingFilterInterpreter::SyncInterpretImpl(
HardwareState* hwstate,
stime_t* timeout) {
FillOriginInfo(*hwstate);
UpdateDistanceInfo(*hwstate);
UpdatePalmState(*hwstate);
UpdatePalmFlags(hwstate);
FillPrevInfo(*hwstate);
if (next_.get())
return next_->SyncInterpret(hwstate, timeout);
return NULL;
}
void PalmClassifyingFilterInterpreter::SetHardwarePropertiesImpl(
const HardwareProperties& hwprops) {
hw_props_ = hwprops;
if (next_.get())
next_->SetHardwareProperties(hwprops);
}
void PalmClassifyingFilterInterpreter::FillOriginInfo(
const HardwareState& hwstate) {
RemoveMissingIdsFromMap(&origin_timestamps_, hwstate);
RemoveMissingIdsFromMap(&origin_fingerstates_, hwstate);
for (size_t i = 0; i < hwstate.finger_cnt; i++) {
const FingerState& fs = hwstate.fingers[i];
if (MapContainsKey(origin_timestamps_, fs.tracking_id))
continue;
origin_timestamps_[fs.tracking_id] = hwstate.timestamp;
origin_fingerstates_[fs.tracking_id] = fs;
}
}
void PalmClassifyingFilterInterpreter::FillPrevInfo(
const HardwareState& hwstate) {
RemoveMissingIdsFromMap(&prev_fingerstates_, hwstate);
prev_time_ = hwstate.timestamp;
for (size_t i = 0; i < hwstate.finger_cnt; i++) {
const FingerState& fs = hwstate.fingers[i];
prev_fingerstates_[fs.tracking_id] = fs;
}
}
void PalmClassifyingFilterInterpreter::UpdateDistanceInfo(
const HardwareState& hwstate) {
RemoveMissingIdsFromMap(&distance_positive_[0], hwstate);
RemoveMissingIdsFromMap(&distance_positive_[1], hwstate);
RemoveMissingIdsFromMap(&distance_negative_[0], hwstate);
RemoveMissingIdsFromMap(&distance_negative_[1], hwstate);
for (size_t i = 0; i < hwstate.finger_cnt; i++) {
const FingerState& fs = hwstate.fingers[i];
int id = fs.tracking_id;
if (MapContainsKey(prev_fingerstates_, id)) {
float delta[2];
delta[0] = fs.position_x - prev_fingerstates_[id].position_x;
delta[1] = fs.position_y - prev_fingerstates_[id].position_y;
for (int i = 0; i < 2; i++) {
if (delta[i] > 0)
distance_positive_[i][id] += delta[i];
else
distance_negative_[i][id] -= delta[i];
}
} else {
distance_positive_[0][id] = 0;
distance_positive_[1][id] = 0;
distance_negative_[0][id] = 0;
distance_negative_[1][id] = 0;
}
}
}
bool PalmClassifyingFilterInterpreter::FingerNearOtherFinger(
const HardwareState& hwstate,
size_t finger_idx) {
const FingerState& fs = hwstate.fingers[finger_idx];
for (int i = 0; i < hwstate.finger_cnt; ++i) {
const FingerState& other_fs = hwstate.fingers[i];
if (other_fs.tracking_id == fs.tracking_id)
continue;
bool too_close_to_other_finger =
finger_metrics_->FingersCloseEnoughToGesture(fs, other_fs) &&
!SetContainsValue(palm_, other_fs.tracking_id);
if (too_close_to_other_finger) {
was_near_other_fingers_.insert(fs.tracking_id);
return true;
}
}
return false;
}
bool PalmClassifyingFilterInterpreter::FingerInPalmEnvelope(
const FingerState& fs) {
float limit = palm_edge_min_width_.val_ +
(fs.pressure / palm_pressure_.val_) *
(palm_edge_width_.val_ - palm_edge_min_width_.val_);
return fs.position_x < limit ||
fs.position_x > (hw_props_.right - limit);
}
bool PalmClassifyingFilterInterpreter::FingerInBottomArea(
const FingerState& fs) {
return fs.position_y > (hw_props_.bottom - palm_edge_min_width_.val_);
}
void PalmClassifyingFilterInterpreter::UpdatePalmState(
const HardwareState& hwstate) {
RemoveMissingIdsFromSet(&palm_, hwstate);
RemoveMissingIdsFromSet(&pointing_, hwstate);
RemoveMissingIdsFromSet(&non_stationary_palm_, hwstate);
RemoveMissingIdsFromSet(&fingers_not_in_edge_, hwstate);
RemoveMissingIdsFromSet(&was_near_other_fingers_, hwstate);
for (short i = 0; i < hwstate.finger_cnt; i++) {
const FingerState& fs = hwstate.fingers[i];
if (!(FingerInPalmEnvelope(fs) || FingerInBottomArea(fs)))
fingers_not_in_edge_.insert(fs.tracking_id);
// Mark anything over the palm thresh as a palm
if (fs.pressure >= palm_pressure_.val_ ||
fs.touch_major >= palm_width_.val_) {
palm_.insert(fs.tracking_id);
pointing_.erase(fs.tracking_id);
continue;
}
}
const float kPalmStationaryDistSq =
palm_stationary_distance_.val_ * palm_stationary_distance_.val_;
for (short i = 0; i < hwstate.finger_cnt; i++) {
const FingerState& fs = hwstate.fingers[i];
bool prev_palm = SetContainsValue(palm_, fs.tracking_id);
bool prev_pointing = SetContainsValue(pointing_, fs.tracking_id);
// Lock onto palm
if (prev_palm)
continue;
// If the finger is recently placed, remove it from pointing/fingers.
// If it's still looking like pointing, it'll get readded.
if (FingerAge(fs.tracking_id, hwstate.timestamp) <
palm_eval_timeout_.val_) {
pointing_.erase(fs.tracking_id);
prev_pointing = false;
}
// If another finger is close by, let this be pointing
if (!prev_pointing && (FingerNearOtherFinger(hwstate, i) ||
!(FingerInPalmEnvelope(fs) ||
FingerInBottomArea(fs)))) {
pointing_.insert(fs.tracking_id);
}
// Check if fingers that only move within palm envelope are pointing.
if(!prev_pointing &&
!SetContainsValue(fingers_not_in_edge_, fs.tracking_id)) {
int id = fs.tracking_id;
float min_dist = palm_pointing_min_dist_.val_;
float max_reverse_dist = palm_pointing_max_reverse_dist_.val_;
// Ideally, we want to say that a finger is pointing if it moves only in
// one direction significantly without zig-zag. But due to touch sensor's
// inaccuratcy, we make the rule to be that a finger has to move in one
// direction significantly with little move in the opposite direction.
for (size_t i = 0; i < arraysize(distance_positive_); i++)
if ((distance_positive_[i][id] >= min_dist &&
distance_negative_[i][id] <= max_reverse_dist) ||
(distance_positive_[i][id] <= max_reverse_dist &&
distance_negative_[i][id] >= min_dist))
pointing_.insert(id);
}
// However, if the contact has been stationary for a while since it
// touched down, it is a palm. We track a potential palm closely for the
// first amount of time to see if it fits this pattern.
if (FingerAge(fs.tracking_id, prev_time_) >
palm_stationary_time_.val_ ||
SetContainsValue(non_stationary_palm_, fs.tracking_id)) {
// Finger is too old to reconsider or is moving a lot
continue;
}
if (DistSq(origin_fingerstates_[fs.tracking_id], fs) >
kPalmStationaryDistSq || !(FingerInPalmEnvelope(fs) ||
FingerInBottomArea(fs))) {
// Finger moving a lot or not in palm envelope; not a stationary palm.
non_stationary_palm_.insert(fs.tracking_id);
continue;
}
if (FingerAge(fs.tracking_id, prev_time_) <=
palm_stationary_time_.val_ &&
FingerAge(fs.tracking_id, hwstate.timestamp) >
palm_stationary_time_.val_ &&
!SetContainsValue(non_stationary_palm_, fs.tracking_id)) {
// Enough time has passed. Make this stationary contact a palm.
palm_.insert(fs.tracking_id);
pointing_.erase(fs.tracking_id);
}
}
}
void PalmClassifyingFilterInterpreter::UpdatePalmFlags(HardwareState* hwstate) {
for (short i = 0; i < hwstate->finger_cnt; i++) {
FingerState* fs = &hwstate->fingers[i];
if (SetContainsValue(palm_, fs->tracking_id)) {
fs->flags |= GESTURES_FINGER_PALM;
} else if (!SetContainsValue(pointing_, fs->tracking_id) &&
!SetContainsValue(was_near_other_fingers_, fs->tracking_id)) {
if (FingerInPalmEnvelope(*fs)) {
fs->flags |= GESTURES_FINGER_PALM;
} else if (FingerInBottomArea(*fs)) {
fs->flags |= (GESTURES_FINGER_WARP_X | GESTURES_FINGER_WARP_Y);
}
} else if (SetContainsValue(pointing_, fs->tracking_id) &&
FingerInPalmEnvelope(*fs)) {
fs->flags |= GESTURES_FINGER_POSSIBLE_PALM;
}
}
}
stime_t PalmClassifyingFilterInterpreter::FingerAge(short finger_id,
stime_t now) const {
if (!MapContainsKey(origin_timestamps_, finger_id)) {
Err("Don't have record of finger age for finger %d", finger_id);
return -1;
}
return now - origin_timestamps_[finger_id];
}
} // namespace gestures