Commit 5dff68a5 authored by Rob Schonberger's avatar Rob Schonberger Committed by Commit Bot

Add 3 Utility classes for Neural Palm rejection, and their unittests.

3 Utility classes being added:

DistilledDevInfo: a distilled smaller version of EventDeviceInfo with
the relevant fields for Neural Palm Rejection.

Sample: a single Sample based on an InProgressTouch

Stroke: A group of samples in multi-touch.

We also add thorough unit tests for all 3 classes.

Bug: 1009290
Change-Id: Idd7e3f9f6a09dda2bd0d619c7b576fc3d86461be
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1832951
Commit-Queue: Rob Schonberger <robsc@chromium.org>
Reviewed-by: default avatarMichael Spang <spang@chromium.org>
Cr-Commit-Position: refs/heads/master@{#702618}
parent af1a7d31
...@@ -627,6 +627,7 @@ if (!is_ios) { ...@@ -627,6 +627,7 @@ if (!is_ios) {
"ozone/evdev/touch_event_converter_evdev_unittest.cc", "ozone/evdev/touch_event_converter_evdev_unittest.cc",
"ozone/evdev/touch_filter/false_touch_finder_unittest.cc", "ozone/evdev/touch_filter/false_touch_finder_unittest.cc",
"ozone/evdev/touch_filter/heuristic_stylus_palm_detection_filter_unittest.cc", "ozone/evdev/touch_filter/heuristic_stylus_palm_detection_filter_unittest.cc",
"ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util_unittest.cc",
"ozone/evdev/touch_filter/open_palm_detection_filter_unittest.cc", "ozone/evdev/touch_filter/open_palm_detection_filter_unittest.cc",
"ozone/evdev/touch_filter/palm_detection_filter_factory_unittest.cc", "ozone/evdev/touch_filter/palm_detection_filter_factory_unittest.cc",
] ]
......
...@@ -122,6 +122,8 @@ if (use_ozone) { ...@@ -122,6 +122,8 @@ if (use_ozone) {
"evdev/touch_filter/horizontally_aligned_touch_noise_filter.h", "evdev/touch_filter/horizontally_aligned_touch_noise_filter.h",
"evdev/touch_filter/low_pressure_filter.cc", "evdev/touch_filter/low_pressure_filter.cc",
"evdev/touch_filter/low_pressure_filter.h", "evdev/touch_filter/low_pressure_filter.h",
"evdev/touch_filter/neural_stylus_palm_detection_filter_util.cc",
"evdev/touch_filter/neural_stylus_palm_detection_filter_util.h",
"evdev/touch_filter/open_palm_detection_filter.cc", "evdev/touch_filter/open_palm_detection_filter.cc",
"evdev/touch_filter/open_palm_detection_filter.h", "evdev/touch_filter/open_palm_detection_filter.h",
"evdev/touch_filter/palm_detection_filter.cc", "evdev/touch_filter/palm_detection_filter.cc",
......
// Copyright 2019 The Chromium 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 "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.h"
namespace ui {
DistilledDevInfo::DistilledDevInfo(const EventDeviceInfo& devinfo) {
max_x = devinfo.GetAbsMaximum(ABS_MT_POSITION_X);
x_res = devinfo.GetAbsResolution(ABS_MT_POSITION_X);
max_y = devinfo.GetAbsMaximum(ABS_MT_POSITION_Y);
y_res = devinfo.GetAbsResolution(ABS_MT_POSITION_Y);
major_radius_res = devinfo.GetAbsResolution(ABS_MT_TOUCH_MAJOR);
if (major_radius_res == 0) {
// Device does not report major res: set to 1.
major_radius_res = 1;
}
if (devinfo.HasAbsEvent(ABS_MT_TOUCH_MINOR)) {
minor_radius_supported = true;
minor_radius_res = devinfo.GetAbsResolution(ABS_MT_TOUCH_MINOR);
} else {
minor_radius_supported = false;
minor_radius_res = major_radius_res;
}
if (minor_radius_res == 0) {
// Device does not report minor res: set to 1.
minor_radius_res = 1;
}
}
const DistilledDevInfo DistilledDevInfo::Create(
const EventDeviceInfo& devinfo) {
return DistilledDevInfo(devinfo);
}
Sample::Sample(const InProgressTouchEvdev& touch,
const base::TimeTicks& time,
const DistilledDevInfo& dev_info)
: time(time) { // radius_x and radius_y have been
// scaled by resolution.already.
// Original model here is not normalized appropriately, so we divide by 40.
major_radius = std::max(touch.major, touch.minor) * dev_info.x_res / 40.0 *
dev_info.major_radius_res;
if (dev_info.minor_radius_supported) {
minor_radius = std::min(touch.major, touch.minor) * dev_info.x_res / 40.0 *
dev_info.minor_radius_res;
} else {
minor_radius = major_radius;
}
// Nearest edge distance, in cm.
float nearest_x_edge = std::min(touch.x, dev_info.max_x - touch.x);
float nearest_y_edge = std::min(touch.y, dev_info.max_y - touch.y);
float normalized_x_edge = nearest_x_edge / dev_info.x_res;
float normalized_y_edge = nearest_y_edge / dev_info.y_res;
edge = std::min(normalized_x_edge, normalized_y_edge);
point = gfx::PointF(touch.x / dev_info.x_res, touch.y / dev_info.y_res);
tracking_id = touch.tracking_id;
pressure = touch.pressure;
}
Sample::Sample(const Sample& other) = default;
Sample& Sample::operator=(const Sample& other) = default;
Stroke::Stroke(const Stroke& other) = default;
Stroke::Stroke(Stroke&& other) = default;
Stroke::Stroke(int max_length) : max_length_(max_length) {}
Stroke::~Stroke() {}
void Stroke::AddSample(const Sample& samp) {
if (samples_.empty()) {
tracking_id_ = samp.tracking_id;
}
DCHECK_EQ(tracking_id_, samp.tracking_id);
samples_.push_back(samp);
while (samples_.size() > max_length_) {
samples_.pop_front();
}
}
gfx::PointF Stroke::GetCentroid() const {
// TODO(robsc): Implement a Kahan sum to accurately track running sum instead
// of brute force.
if (samples_.size() == 0) {
return gfx::PointF(0., 0.);
}
gfx::PointF unscaled_centroid;
for (const auto& sample : samples_) {
unscaled_centroid += sample.point.OffsetFromOrigin();
}
return gfx::ScalePoint(unscaled_centroid, 1.f / samples_.size());
}
int Stroke::tracking_id() const {
return tracking_id_;
}
float Stroke::BiggestSize() const {
float biggest = 0;
for (const auto& sample : samples_) {
float size;
if (sample.minor_radius <= 0) {
size = sample.major_radius * sample.major_radius;
} else {
size = sample.major_radius * sample.minor_radius;
}
biggest = std::max(biggest, size);
}
return biggest;
}
} // namespace ui
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UI_EVENTS_OZONE_EVDEV_TOUCH_FILTER_NEURAL_STYLUS_PALM_DETECTION_FILTER_UTIL_H_
#define UI_EVENTS_OZONE_EVDEV_TOUCH_FILTER_NEURAL_STYLUS_PALM_DETECTION_FILTER_UTIL_H_
#include <deque>
#include <vector>
#include "base/time/time.h"
#include "ui/events/ozone/evdev/event_device_info.h"
#include "ui/events/ozone/evdev/touch_evdev_types.h"
#include "ui/gfx/geometry/point_f.h"
namespace ui {
struct EVENTS_OZONE_EVDEV_EXPORT DistilledDevInfo {
private:
explicit DistilledDevInfo(const EventDeviceInfo& devinfo);
public:
static const DistilledDevInfo Create(const EventDeviceInfo& devinfo);
DistilledDevInfo() = delete;
float max_x = 0.f;
float max_y = 0.f;
float x_res = 1.f;
float y_res = 1.f;
float major_radius_res = 1.f;
float minor_radius_res = 1.f;
bool minor_radius_supported = false;
};
// Data for a single touch event.
class EVENTS_OZONE_EVDEV_EXPORT Sample {
public:
Sample(const InProgressTouchEvdev& touch,
const base::TimeTicks& time,
const DistilledDevInfo& dev_info);
Sample(const Sample& other);
Sample() = delete;
Sample& operator=(const Sample& other);
float major_radius = 0;
float minor_radius = 0;
float pressure = 0;
float edge = 0;
int tracking_id = 0;
gfx::PointF point;
base::TimeTicks time = base::TimeTicks::UnixEpoch();
};
class EVENTS_OZONE_EVDEV_EXPORT Stroke {
public:
explicit Stroke(int max_length);
Stroke(const Stroke& other);
Stroke(Stroke&& other);
virtual ~Stroke();
void AddSample(const Sample& sample);
gfx::PointF GetCentroid() const;
float BiggestSize() const;
const std::deque<Sample>& samples() const;
int tracking_id() const;
private:
std::deque<Sample> samples_;
int tracking_id_ = 0;
unsigned long max_length_;
};
} // namespace ui
#endif // UI_EVENTS_OZONE_EVDEV_TOUCH_FILTER_NEURAL_STYLUS_PALM_DETECTION_FILTER_UTIL_H_
// Copyright 2019 The Chromium 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 "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.h"
#include <utility>
#include <vector>
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/events/ozone/evdev/event_device_test_util.h"
#include "ui/events/ozone/evdev/touch_evdev_types.h"
#include "ui/events/ozone/evdev/touch_filter/palm_detection_filter.h"
#include "ui/events/ozone/evdev/touch_filter/shared_palm_detection_filter_state.h"
namespace ui {
class NeuralStylusPalmDetectionFilterUtilTest : public testing::Test {
public:
NeuralStylusPalmDetectionFilterUtilTest() = default;
void SetUp() override {
EXPECT_TRUE(
CapabilitiesToDeviceInfo(kNocturneTouchScreen, &nocturne_touchscreen_));
touch_.major = 25;
touch_.minor = 24;
touch_.pressure = 23;
touch_.tracking_id = 22;
touch_.x = 21;
touch_.y = 20;
}
protected:
InProgressTouchEvdev touch_;
EventDeviceInfo nocturne_touchscreen_;
DISALLOW_COPY_AND_ASSIGN(NeuralStylusPalmDetectionFilterUtilTest);
};
TEST_F(NeuralStylusPalmDetectionFilterUtilTest, DistilledNocturneTest) {
const DistilledDevInfo nocturne_distilled =
DistilledDevInfo::Create(nocturne_touchscreen_);
EXPECT_FLOAT_EQ(nocturne_distilled.max_x,
nocturne_touchscreen_.GetAbsMaximum(ABS_MT_POSITION_X));
EXPECT_FLOAT_EQ(nocturne_distilled.max_y,
nocturne_touchscreen_.GetAbsMaximum(ABS_MT_POSITION_Y));
EXPECT_FLOAT_EQ(nocturne_distilled.x_res,
nocturne_touchscreen_.GetAbsResolution(ABS_MT_POSITION_X));
EXPECT_FLOAT_EQ(nocturne_distilled.y_res,
nocturne_touchscreen_.GetAbsResolution(ABS_MT_POSITION_Y));
EXPECT_FLOAT_EQ(nocturne_distilled.major_radius_res,
nocturne_touchscreen_.GetAbsResolution(ABS_MT_TOUCH_MAJOR));
EXPECT_TRUE(nocturne_distilled.minor_radius_supported);
EXPECT_FLOAT_EQ(nocturne_distilled.minor_radius_res,
nocturne_touchscreen_.GetAbsResolution(ABS_MT_TOUCH_MINOR));
}
TEST_F(NeuralStylusPalmDetectionFilterUtilTest, DistilledLinkTest) {
EventDeviceInfo link_touchscreen;
ASSERT_TRUE(CapabilitiesToDeviceInfo(kLinkTouchscreen, &link_touchscreen));
const DistilledDevInfo link_distilled =
DistilledDevInfo::Create(link_touchscreen);
EXPECT_FALSE(link_distilled.minor_radius_supported);
EXPECT_FLOAT_EQ(1.f, link_distilled.major_radius_res);
EXPECT_FLOAT_EQ(link_distilled.major_radius_res,
link_distilled.minor_radius_res);
}
TEST_F(NeuralStylusPalmDetectionFilterUtilTest, SampleTest) {
base::TimeTicks t =
base::TimeTicks::UnixEpoch() + base::TimeDelta::FromSeconds(30);
const DistilledDevInfo nocturne_distilled =
DistilledDevInfo::Create(nocturne_touchscreen_);
const Sample s(touch_, t, nocturne_distilled);
EXPECT_EQ(t, s.time);
EXPECT_EQ(25, s.major_radius);
EXPECT_EQ(24, s.minor_radius);
EXPECT_EQ(23, s.pressure);
EXPECT_EQ(22, s.tracking_id);
EXPECT_EQ(gfx::PointF(21 / 40.f, 20 / 40.f), s.point);
EXPECT_EQ(0.5, s.edge);
}
TEST_F(NeuralStylusPalmDetectionFilterUtilTest, LinkTouchscreenSampleTest) {
EventDeviceInfo link_touchscreen;
base::TimeTicks t =
base::TimeTicks::UnixEpoch() + base::TimeDelta::FromSeconds(30);
ASSERT_TRUE(CapabilitiesToDeviceInfo(kLinkTouchscreen, &link_touchscreen));
const DistilledDevInfo link_distilled =
DistilledDevInfo::Create(link_touchscreen);
touch_.minor = 0; // no minor from link.
const Sample s(touch_, t, link_distilled);
EXPECT_FLOAT_EQ(12.5, s.major_radius);
EXPECT_FLOAT_EQ(12.5, s.minor_radius);
}
TEST_F(NeuralStylusPalmDetectionFilterUtilTest, StrokeTest) {
Stroke stroke(3); // maxsize: 3.
EXPECT_EQ(0, stroke.tracking_id());
// With no points, center is 0.
EXPECT_EQ(gfx::PointF(0., 0.), stroke.GetCentroid());
base::TimeTicks t =
base::TimeTicks::UnixEpoch() + base::TimeDelta::FromSeconds(30);
const DistilledDevInfo nocturne_distilled =
DistilledDevInfo::Create(nocturne_touchscreen_);
// Deliberately long test to ensure floating point continued accuracy.
for (int i = 0; i < 500000; ++i) {
touch_.x = 15 + i;
Sample s(touch_, t, nocturne_distilled);
stroke.AddSample(std::move(s));
EXPECT_EQ(touch_.tracking_id, stroke.tracking_id());
if (i < 3) {
if (i == 0) {
EXPECT_FLOAT_EQ(gfx::PointF(15 / 40.f, 0.5).x(),
stroke.GetCentroid().x());
} else if (i == 1) {
EXPECT_FLOAT_EQ(gfx::PointF((30 + 1) / (2 * 40.f), 0.5).x(),
stroke.GetCentroid().x());
} else if (i == 2) {
EXPECT_FLOAT_EQ(gfx::PointF((45 + 1 + 2) / (3 * 40.f), 0.5).x(),
stroke.GetCentroid().x());
}
continue;
}
float expected_x = (45 + 3 * i - 3) / (3 * 40.f);
gfx::PointF expected_centroid = gfx::PointF(expected_x, 0.5);
ASSERT_FLOAT_EQ(expected_centroid.x(), stroke.GetCentroid().x())
<< "failed at i " << i;
}
}
TEST_F(NeuralStylusPalmDetectionFilterUtilTest, StrokeBiggestSizeTest) {
Stroke stroke(3), no_minor_stroke(3); // maxsize: 3.
EXPECT_EQ(0, stroke.BiggestSize());
base::TimeTicks t =
base::TimeTicks::UnixEpoch() + base::TimeDelta::FromSeconds(30);
const DistilledDevInfo nocturne_distilled =
DistilledDevInfo::Create(nocturne_touchscreen_);
for (int i = 0; i < 500; ++i) {
touch_.major = 2 + i;
touch_.minor = 1 + i;
Sample s(touch_, t, nocturne_distilled);
stroke.AddSample(std::move(s));
EXPECT_FLOAT_EQ((1 + i) * (2 + i), stroke.BiggestSize());
Sample second_s(touch_, t, nocturne_distilled);
second_s.minor_radius = 0;
no_minor_stroke.AddSample(std::move(second_s));
EXPECT_FLOAT_EQ((2 + i) * (2 + i), no_minor_stroke.BiggestSize());
}
}
} // namespace ui
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment