blob: fc5d367e43a930cf55eb07810432ac000ea07976 [file] [log] [blame]
// 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 <algorithm>
#include <cstdint>
#include <utility>
#include <vector>
#include "testing/gtest/include/gtest/gtest.h"
#if defined(__ANDROID__)
#include <linux/input-event-codes.h>
#include "chrome_to_android_compatibility_test_support.h"
#else
#include "ui/events/ozone/evdev/event_device_test_util.h"
#endif
#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 {
#if defined(__ANDROID__)
/**
* The tests that require an actual device (something that responds to ioctls)
* have been removed. The rest of the tests were simplified by modifying the
* 'CreatePalmFilterDeviceInfo' method.
*/
enum DeviceType {
kNocturneTouchScreen,
kLinkTouchscreen,
kKohakuTouchscreen,
};
static PalmFilterDeviceInfo CreatePalmFilterDeviceInfo(DeviceType deviceType) {
// Default value for resolution is 1 if it's not specified (or set to 0). See
// https://source.chromium.org/chromium/chromium/src/+/main:ui/events/ozone/evdev/touch_filter/
// neural_stylus_palm_detection_filter_util.cc;l=11;drc=7f74e1d22e2d0b91856ea6b3619098cd05ef6158
switch (deviceType) {
case kNocturneTouchScreen:
return PalmFilterDeviceInfo{
.max_x = 10404,
.max_y = 6936,
.x_res = 40,
.y_res = 40,
.major_radius_res = 1,
.minor_radius_res = 1,
.minor_radius_supported = true,
};
case kLinkTouchscreen:
return PalmFilterDeviceInfo{
.max_x = 2559,
.max_y = 1699,
.x_res = 20,
.y_res = 20,
.major_radius_res = 1,
.minor_radius_res = 1,
.minor_radius_supported = false,
};
case kKohakuTouchscreen:
return PalmFilterDeviceInfo{
.max_x = 2559,
.max_y = 1699,
.x_res = 1,
.y_res = 1,
.major_radius_res = 1,
.minor_radius_res = 1,
.minor_radius_supported = false,
};
}
}
class EventDeviceInfo {
public:
float GetAbsResolution(int /*axis*/) { return 20; }
};
bool CapabilitiesToDeviceInfo(DeviceType, EventDeviceInfo*) {
return true;
}
#endif
class NeuralStylusPalmDetectionFilterUtilTest : public testing::Test {
public:
NeuralStylusPalmDetectionFilterUtilTest() = default;
NeuralStylusPalmDetectionFilterUtilTest(
const NeuralStylusPalmDetectionFilterUtilTest&) = delete;
NeuralStylusPalmDetectionFilterUtilTest& operator=(
const NeuralStylusPalmDetectionFilterUtilTest&) = delete;
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_;
NeuralStylusPalmDetectionFilterModelConfig model_config_;
};
#if !defined(__ANDROID__)
TEST_F(NeuralStylusPalmDetectionFilterUtilTest, DistilledNocturneTest) {
const PalmFilterDeviceInfo nocturne_distilled =
CreatePalmFilterDeviceInfo(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, NoMinorResTest) {
// Nocturne has minor resolution: but lets pretend it didnt. we should recover
// "1" as the resolution.
auto abs_info = nocturne_touchscreen_.GetAbsInfoByCode(ABS_MT_TOUCH_MINOR);
abs_info.resolution = 0;
nocturne_touchscreen_.SetAbsInfo(ABS_MT_TOUCH_MINOR, abs_info);
const PalmFilterDeviceInfo nocturne_distilled =
CreatePalmFilterDeviceInfo(nocturne_touchscreen_);
EXPECT_EQ(1, nocturne_distilled.minor_radius_res);
EXPECT_EQ(1, nocturne_distilled.major_radius_res);
}
#endif
TEST_F(NeuralStylusPalmDetectionFilterUtilTest, DistillerKohakuTest) {
EventDeviceInfo kohaku_touchscreen;
ASSERT_TRUE(
CapabilitiesToDeviceInfo(kKohakuTouchscreen, &kohaku_touchscreen));
const PalmFilterDeviceInfo kohaku_distilled =
#if !defined(__ANDROID__)
CreatePalmFilterDeviceInfo(kohaku_touchscreen);
#else
CreatePalmFilterDeviceInfo(kKohakuTouchscreen);
#endif
EXPECT_FALSE(kohaku_distilled.minor_radius_supported);
EXPECT_EQ(1, kohaku_distilled.x_res);
EXPECT_EQ(1, kohaku_distilled.y_res);
}
TEST_F(NeuralStylusPalmDetectionFilterUtilTest, DistilledLinkTest) {
EventDeviceInfo link_touchscreen;
ASSERT_TRUE(CapabilitiesToDeviceInfo(kLinkTouchscreen, &link_touchscreen));
const PalmFilterDeviceInfo link_distilled =
#if !defined(__ANDROID__)
CreatePalmFilterDeviceInfo(link_touchscreen);
#else
CreatePalmFilterDeviceInfo(kLinkTouchscreen);
#endif
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, PalmFilterSampleTest) {
base::TimeTicks time = base::TimeTicks() + base::Seconds(30);
const PalmFilterDeviceInfo nocturne_distilled =
#if !defined(__ANDROID__)
CreatePalmFilterDeviceInfo(nocturne_touchscreen_);
#else
CreatePalmFilterDeviceInfo(kNocturneTouchScreen);
#endif
const PalmFilterSample sample =
CreatePalmFilterSample(touch_, time, model_config_, nocturne_distilled);
EXPECT_EQ(time, sample.time);
EXPECT_EQ(25, sample.major_radius);
EXPECT_EQ(24, sample.minor_radius);
EXPECT_EQ(23, sample.pressure);
EXPECT_EQ(22, sample.tracking_id);
EXPECT_EQ(gfx::PointF(21 / 40.f, 20 / 40.f), sample.point);
EXPECT_EQ(0.5, sample.edge);
}
TEST_F(NeuralStylusPalmDetectionFilterUtilTest, LinkTouchscreenSampleTest) {
EventDeviceInfo link_touchscreen;
base::TimeTicks time = base::TimeTicks() + base::Seconds(30);
ASSERT_TRUE(CapabilitiesToDeviceInfo(kLinkTouchscreen, &link_touchscreen));
const PalmFilterDeviceInfo link_distilled =
#if !defined(__ANDROID__)
CreatePalmFilterDeviceInfo(link_touchscreen);
#else
CreatePalmFilterDeviceInfo(kLinkTouchscreen);
#endif
touch_.minor = 0; // no minor from link.
// use 40 as a base since model is trained on that kind of device.
model_config_.radius_polynomial_resize = {
link_touchscreen.GetAbsResolution(ABS_MT_POSITION_X) / 40.0f, 0.0};
const PalmFilterSample sample =
CreatePalmFilterSample(touch_, time, model_config_, link_distilled);
EXPECT_FLOAT_EQ(12.5, sample.major_radius);
EXPECT_FLOAT_EQ(12.5, sample.minor_radius);
}
TEST_F(NeuralStylusPalmDetectionFilterUtilTest, PalmFilterStrokeTest) {
PalmFilterStroke 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 time = base::TimeTicks() + base::Seconds(30);
const PalmFilterDeviceInfo nocturne_distilled =
#if !defined(__ANDROID__)
CreatePalmFilterDeviceInfo(nocturne_touchscreen_);
#else
CreatePalmFilterDeviceInfo(kNocturneTouchScreen);
#endif
// Deliberately long test to ensure floating point continued accuracy.
for (int i = 0; i < 500000; ++i) {
touch_.x = 15 + i;
PalmFilterSample sample =
CreatePalmFilterSample(touch_, time, model_config_, nocturne_distilled);
stroke.AddSample(std::move(sample));
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;
}
stroke.SetTrackingId(55);
EXPECT_EQ(55, stroke.tracking_id());
}
TEST_F(NeuralStylusPalmDetectionFilterUtilTest,
PalmFilterStrokeBiggestSizeTest) {
PalmFilterStroke stroke(3);
PalmFilterStroke no_minor_stroke(3); // maxsize: 3.
EXPECT_EQ(0, stroke.BiggestSize());
base::TimeTicks time = base::TimeTicks() + base::Seconds(30);
const PalmFilterDeviceInfo nocturne_distilled =
#if !defined(__ANDROID__)
CreatePalmFilterDeviceInfo(nocturne_touchscreen_);
#else
CreatePalmFilterDeviceInfo(kNocturneTouchScreen);
#endif
for (int i = 0; i < 500; ++i) {
touch_.major = 2 + i;
touch_.minor = 1 + i;
PalmFilterSample sample =
CreatePalmFilterSample(touch_, time, model_config_, nocturne_distilled);
EXPECT_EQ(static_cast<uint64_t>(i), stroke.samples_seen());
stroke.AddSample(sample);
EXPECT_FLOAT_EQ((1 + i) * (2 + i), stroke.BiggestSize());
PalmFilterSample second_sample =
CreatePalmFilterSample(touch_, time, model_config_, nocturne_distilled);
second_sample.minor_radius = 0;
no_minor_stroke.AddSample(std::move(second_sample));
EXPECT_FLOAT_EQ((2 + i) * (2 + i), no_minor_stroke.BiggestSize());
EXPECT_EQ(std::min(3ul, 1ul + i), stroke.samples().size());
}
}
TEST_F(NeuralStylusPalmDetectionFilterUtilTest, StrokeGetMaxMajorTest) {
PalmFilterStroke stroke(3);
EXPECT_FLOAT_EQ(0, stroke.MaxMajorRadius());
base::TimeTicks time = base::TimeTicks::UnixEpoch() + base::Seconds(30);
const PalmFilterDeviceInfo nocturne_distilled =
#if !defined(__ANDROID__)
CreatePalmFilterDeviceInfo(nocturne_touchscreen_);
#else
CreatePalmFilterDeviceInfo(kNocturneTouchScreen);
#endif
for (int i = 1; i < 50; ++i) {
touch_.major = i;
touch_.minor = i - 1;
PalmFilterSample sample =
CreatePalmFilterSample(touch_, time, model_config_, nocturne_distilled);
time += base::Milliseconds(8);
EXPECT_EQ(static_cast<uint64_t>(i - 1), stroke.samples_seen());
stroke.AddSample(sample);
EXPECT_FLOAT_EQ(i, stroke.MaxMajorRadius());
}
}
TEST_F(NeuralStylusPalmDetectionFilterUtilTest, SampleRadiusConversion) {
// A single number: a _constant_.
model_config_.radius_polynomial_resize = {71.3};
base::TimeTicks time = base::TimeTicks::UnixEpoch() + base::Seconds(30);
const PalmFilterDeviceInfo nocturne_distilled =
#if !defined(__ANDROID__)
CreatePalmFilterDeviceInfo(nocturne_touchscreen_);
#else
CreatePalmFilterDeviceInfo(kNocturneTouchScreen);
#endif
PalmFilterSample sample =
CreatePalmFilterSample(touch_, time, model_config_, nocturne_distilled);
EXPECT_FLOAT_EQ(71.3, sample.major_radius);
EXPECT_FLOAT_EQ(71.3, sample.minor_radius);
// 0.1*r^2 + 0.4*r - 5.0
model_config_.radius_polynomial_resize = {0.1, 0.4, -5.0};
sample =
CreatePalmFilterSample(touch_, time, model_config_, nocturne_distilled);
EXPECT_FLOAT_EQ(0.1 * 25 * 25 + 0.4 * 25 - 5.0, sample.major_radius);
EXPECT_FLOAT_EQ(0.1 * 24 * 24 + 0.4 * 24 - 5.0, sample.minor_radius);
}
} // namespace ui