summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSiarhei Vishniakou <svv@google.com>2022-09-23 00:17:03 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2022-09-23 00:17:03 +0000
commit674b1dc79112e353f26267a3a7122eac3c165858 (patch)
tree3af5ed9cb9d4667f9e379c277a2ac9755efedc2c
parent6fed5772040b036885957e755fc619afd6ff8ac4 (diff)
parent9b40752880ad12a6afd219e0c0f97d9933f1a174 (diff)
downloadlibpalmrejection-674b1dc79112e353f26267a3a7122eac3c165858.tar.gz
Time-based resampling am: 9b40752880
Original change: https://googleplex-android-review.googlesource.com/c/platform/external/libpalmrejection/+/19472519 Change-Id: Ibf84af80fce42453f15c07cc506f06cd34ba4a30 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r--Android.bp1
-rw-r--r--chrome_to_android_compatibility.cc15
-rw-r--r--chrome_to_android_compatibility.h8
-rw-r--r--chrome_to_android_compatibility_test_support.cc6
-rw-r--r--chrome_to_android_compatibility_test_support.h19
-rw-r--r--copy.bara.sky2
-rw-r--r--ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.cc133
-rw-r--r--ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h2
-rw-r--r--ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.cc17
-rw-r--r--ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.h4
-rw-r--r--ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_unittest.cc76
-rw-r--r--ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.cc103
-rw-r--r--ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.h33
-rw-r--r--ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util_unittest.cc73
14 files changed, 337 insertions, 155 deletions
diff --git a/Android.bp b/Android.bp
index 7211fd9..d0ab4dd 100644
--- a/Android.bp
+++ b/Android.bp
@@ -18,6 +18,7 @@ cc_library_static {
local_include_dirs: ["."],
export_include_dirs: ["."],
srcs: [
+ "chrome_to_android_compatibility.cc",
"ui/events/ozone/features.cc",
"ui/events/ozone/evdev/touch_evdev_types.cc",
"ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.cc",
diff --git a/chrome_to_android_compatibility.cc b/chrome_to_android_compatibility.cc
new file mode 100644
index 0000000..18b5a80
--- /dev/null
+++ b/chrome_to_android_compatibility.cc
@@ -0,0 +1,15 @@
+// Copyright 2022 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 "chrome_to_android_compatibility.h"
+
+// Android's external/libchrome directory is out of date.
+// Add missing templates here as a temporary solution
+namespace base {
+
+bool operator==(const TimeTicks& t1, const TimeTicks& t2) {
+ return t1.since_origin() == t2.since_origin();
+}
+
+} // namespace base
diff --git a/chrome_to_android_compatibility.h b/chrome_to_android_compatibility.h
index f51ea36..91c442a 100644
--- a/chrome_to_android_compatibility.h
+++ b/chrome_to_android_compatibility.h
@@ -10,6 +10,14 @@
// Add missing templates here as a temporary solution
namespace base {
+/**
+ * Workaround for the error in unit tests: ISO C++20 considers use of overloaded
+ * operator '==' (with operand types 'const base::TimeTicks'
+ * and 'const base::TimeTicks') to be ambiguous despite there being a unique
+ * best viable function [-Werror,-Wambiguous-reversed-operator]
+ */
+bool operator==(const TimeTicks& t1, const TimeTicks& t2);
+
namespace time_internal {
// clang-format off
diff --git a/chrome_to_android_compatibility_test_support.cc b/chrome_to_android_compatibility_test_support.cc
index 5aa94b9..d1fe1e5 100644
--- a/chrome_to_android_compatibility_test_support.cc
+++ b/chrome_to_android_compatibility_test_support.cc
@@ -6,12 +6,6 @@
#include "base/time/time.h"
-namespace base {
-bool operator==(const TimeTicks& t1, const TimeTicks& t2) {
- return t1.since_origin() == t2.since_origin();
-}
-} // namespace base
-
namespace gfx {
// clang-format off
diff --git a/chrome_to_android_compatibility_test_support.h b/chrome_to_android_compatibility_test_support.h
deleted file mode 100644
index c2e4c90..0000000
--- a/chrome_to_android_compatibility_test_support.h
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2022 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.
-
-#pragma once
-
-#include "base/time/time.h"
-
-namespace base {
-
-/**
- * Workaround for the error in unit tests: ISO C++20 considers use of overloaded
- * operator '==' (with operand types 'const base::TimeTicks'
- * and 'const base::TimeTicks') to be ambiguous despite there being a unique
- * best viable function [-Werror,-Wambiguous-reversed-operator]
- */
-bool operator==(const TimeTicks& t1, const TimeTicks& t2);
-
-} // namespace base
diff --git a/copy.bara.sky b/copy.bara.sky
index c270d38..cb88b0c 100644
--- a/copy.bara.sky
+++ b/copy.bara.sky
@@ -90,9 +90,9 @@ core.workflow(
"OWNERS",
"PREUPLOAD.cfg",
"TEST_MAPPING",
+ "chrome_to_android_compatibility.cc",
"chrome_to_android_compatibility.h",
"chrome_to_android_compatibility_test_support.cc",
- "chrome_to_android_compatibility_test_support.h",
"copy.bara.sky",
],
),
diff --git a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.cc b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.cc
index 0b301e4..9a8e385 100644
--- a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.cc
+++ b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.cc
@@ -37,20 +37,49 @@ float EuclideanDistance(const gfx::PointF& a, const gfx::PointF& b) {
bool IsEarlyStageSample(
const PalmFilterStroke& stroke,
const NeuralStylusPalmDetectionFilterModelConfig& config) {
- return config.early_stage_sample_counts.find(stroke.samples_seen()) !=
- config.early_stage_sample_counts.end();
+ if (!config.resample_period) {
+ return config.early_stage_sample_counts.find(stroke.samples_seen()) !=
+ config.early_stage_sample_counts.end();
+ }
+ // Duration is not well-defined for sample_count <= 1, so we handle
+ // it separately.
+ if (stroke.samples().empty()) {
+ return false;
+ }
+ if (stroke.samples().size() == 1) {
+ return config.early_stage_sample_counts.find(1) !=
+ config.early_stage_sample_counts.end();
+ }
+ for (const uint32_t sample_count : config.early_stage_sample_counts) {
+ const base::TimeDelta duration = config.GetEquivalentDuration(sample_count);
+ // Previous sample must not have passed the 'duration' threshold, but the
+ // current sample must pass the threshold
+ if (stroke.LastSampleCrossed(duration)) {
+ return true;
+ }
+ }
+ return false;
}
bool HasDecidedStroke(
const PalmFilterStroke& stroke,
const NeuralStylusPalmDetectionFilterModelConfig& config) {
- return stroke.samples_seen() >= config.max_sample_count;
+ if (!config.resample_period) {
+ return stroke.samples_seen() >= config.max_sample_count;
+ }
+ const base::TimeDelta max_duration =
+ config.GetEquivalentDuration(config.max_sample_count);
+ return stroke.Duration() >= max_duration;
}
bool IsVeryShortStroke(
const PalmFilterStroke& stroke,
const NeuralStylusPalmDetectionFilterModelConfig& config) {
- return stroke.samples_seen() < config.min_sample_count;
+ if (!config.resample_period) {
+ return stroke.samples_seen() < config.min_sample_count;
+ }
+ return stroke.Duration() <
+ config.GetEquivalentDuration(config.min_sample_count);
}
/**
@@ -58,9 +87,15 @@ bool IsVeryShortStroke(
* being evaluated. The parameter 'neighbor_min_sample_count' might be different
* from the config, depending on the specific usage in the caller.
*/
-bool HasInsufficientDataAsNeighbor(const PalmFilterStroke& neighbor_stroke,
- size_t neighbor_min_sample_count) {
- return neighbor_stroke.samples().size() < neighbor_min_sample_count;
+bool HasInsufficientDataAsNeighbor(
+ const PalmFilterStroke& neighbor_stroke,
+ size_t neighbor_min_sample_count,
+ const NeuralStylusPalmDetectionFilterModelConfig& config) {
+ if (!config.resample_period) {
+ return neighbor_stroke.samples().size() < neighbor_min_sample_count;
+ }
+ return neighbor_stroke.Duration() <
+ config.GetEquivalentDuration(neighbor_min_sample_count);
}
} // namespace
@@ -105,7 +140,8 @@ void NeuralStylusPalmDetectionFilter::FindBiggestNeighborsWithin(
if (neighbor.tracking_id() == stroke.tracking_id()) {
continue;
}
- if (HasInsufficientDataAsNeighbor(neighbor, neighbor_min_sample_count)) {
+ if (HasInsufficientDataAsNeighbor(neighbor, neighbor_min_sample_count,
+ model_->config())) {
continue;
}
float distance =
@@ -145,7 +181,8 @@ void NeuralStylusPalmDetectionFilter::FindNearestNeighborsWithin(
if (neighbor.tracking_id() == stroke.tracking_id()) {
continue;
}
- if (HasInsufficientDataAsNeighbor(neighbor, neighbor_min_sample_count)) {
+ if (HasInsufficientDataAsNeighbor(neighbor, neighbor_min_sample_count,
+ model_->config())) {
continue;
}
float distance =
@@ -300,15 +337,29 @@ bool NeuralStylusPalmDetectionFilter::ShouldDecideStroke(
const PalmFilterStroke& stroke) const {
const NeuralStylusPalmDetectionFilterModelConfig& config = model_->config();
// Inference only executed once per stroke
- return stroke.samples_seen() == config.max_sample_count;
+ if (!config.resample_period) {
+ return stroke.samples_seen() == config.max_sample_count;
+ }
+ return stroke.LastSampleCrossed(
+ config.GetEquivalentDuration(config.max_sample_count));
}
bool NeuralStylusPalmDetectionFilter::IsHeuristicPalmStroke(
const PalmFilterStroke& stroke) const {
const auto& config = model_->config();
- if (stroke.samples().size() >= config.max_sample_count) {
- LOG(DFATAL) << "Should not call this method on long strokes.";
- return false;
+ if (config.resample_period) {
+ if (stroke.Duration() >
+ config.GetEquivalentDuration(config.max_sample_count)) {
+ LOG(DFATAL)
+ << "Should not call this method on long strokes. Got duration = "
+ << stroke.Duration();
+ return false;
+ }
+ } else {
+ if (stroke.samples().size() >= config.max_sample_count) {
+ LOG(DFATAL) << "Should not call this method on long strokes.";
+ return false;
+ }
}
if (config.heuristic_palm_touch_limit > 0.0) {
@@ -401,6 +452,9 @@ std::vector<float> NeuralStylusPalmDetectionFilter::ExtractFeatures(
void NeuralStylusPalmDetectionFilter::AppendFeatures(
const PalmFilterStroke& stroke,
std::vector<float>* features) const {
+ if (model_->config().resample_period) {
+ return AppendResampledFeatures(stroke, features);
+ }
const int size = stroke.samples().size();
for (int i = 0; i < size; ++i) {
const PalmFilterSample& sample = stroke.samples()[i];
@@ -435,6 +489,59 @@ void NeuralStylusPalmDetectionFilter::AppendFeatures(
features->push_back(samples_seen - model_->config().max_sample_count);
}
}
+
+/**
+ * The flow here is similar to 'AppendFeatures' above, but we rely on the
+ * timing of the samples rather than on the explicit number / position of
+ * samples.
+ */
+void NeuralStylusPalmDetectionFilter::AppendResampledFeatures(
+ const PalmFilterStroke& stroke,
+ std::vector<float>* features) const {
+ size_t sample_count = 0;
+ const base::TimeTicks& first_time = stroke.samples()[0].time;
+ const base::TimeDelta& resample_period = *model_->config().resample_period;
+ const base::TimeDelta max_duration =
+ model_->config().GetEquivalentDuration(model_->config().max_sample_count);
+ for (auto time = first_time; (time - first_time) <= max_duration &&
+ time <= stroke.samples().back().time;
+ time += resample_period) {
+ sample_count++;
+ const PalmFilterSample& sample = stroke.GetSampleAt(time);
+ features->push_back(sample.major_radius);
+ features->push_back(sample.minor_radius <= 0.0 ? sample.major_radius
+ : sample.minor_radius);
+ float distance = 0;
+ if (time != first_time) {
+ distance = EuclideanDistance(
+ stroke.GetSampleAt(time - resample_period).point, sample.point);
+ }
+ features->push_back(distance);
+ features->push_back(sample.edge);
+ features->push_back(1.0); // existence.
+ }
+ const int padding = model_->config().max_sample_count - sample_count;
+ DCHECK_GE(padding, 0);
+
+ for (int i = 0; i < padding * kFeaturesPerSample; ++i) {
+ features->push_back(0.0);
+ }
+ // "fill proportion."
+ features->push_back(static_cast<float>(sample_count) /
+ model_->config().max_sample_count);
+ features->push_back(EuclideanDistance(stroke.samples().front().point,
+ stroke.samples().back().point));
+
+ // Start sequence number. 0 is min.
+ uint32_t samples_seen =
+ (stroke.Duration() / (*model_->config().resample_period)) + 1;
+ if (samples_seen < model_->config().max_sample_count) {
+ features->push_back(0);
+ } else {
+ features->push_back(samples_seen - model_->config().max_sample_count);
+ }
+}
+
void NeuralStylusPalmDetectionFilter::AppendFeaturesAsNeighbor(
const PalmFilterStroke& stroke,
float distance,
diff --git a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h
index bab332e..c27e9e9 100644
--- a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h
+++ b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h
@@ -109,6 +109,8 @@ class COMPONENT_EXPORT(EVDEV) NeuralStylusPalmDetectionFilter
std::vector<float> ExtractFeatures(int tracking_id) const;
void AppendFeatures(const PalmFilterStroke& stroke,
std::vector<float>* features) const;
+ void AppendResampledFeatures(const PalmFilterStroke& stroke,
+ std::vector<float>* features) const;
void AppendFeaturesAsNeighbor(const PalmFilterStroke& stroke,
float distance,
std::vector<float>* features) const;
diff --git a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.cc b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.cc
index da596b8..51aa4df 100644
--- a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.cc
+++ b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.cc
@@ -4,6 +4,8 @@
#include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.h"
+#include "base/logging.h"
+
namespace ui {
NeuralStylusPalmDetectionFilterModelConfig::
@@ -15,4 +17,19 @@ NeuralStylusPalmDetectionFilterModelConfig::
NeuralStylusPalmDetectionFilterModelConfig::
~NeuralStylusPalmDetectionFilterModelConfig() = default;
+
+base::TimeDelta
+NeuralStylusPalmDetectionFilterModelConfig::GetEquivalentDuration(
+ uint32_t sample_count) const {
+ if (!resample_period) {
+ LOG(DFATAL) << __func__
+ << " should only be called if resampling is enabled";
+ return base::TimeDelta::FromMicroseconds(0);
+ }
+ if (sample_count <= 1) {
+ return base::TimeDelta::FromMicroseconds(0);
+ }
+ return (sample_count - 1) * (*resample_period);
+}
+
} // namespace ui \ No newline at end of file
diff --git a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.h b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.h
index dab5455..bcc21b2 100644
--- a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.h
+++ b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.h
@@ -42,6 +42,10 @@ struct COMPONENT_EXPORT(EVDEV) NeuralStylusPalmDetectionFilterModelConfig {
// Maximum sample count.
uint32_t max_sample_count = 0;
+ // Convert the provided 'sample_count' to an equivalent time duration.
+ // Should only be called when resampling is enabled.
+ base::TimeDelta GetEquivalentDuration(uint32_t sample_count) const;
+
// Minimum count of samples for a stroke to be considered as a neighbor.
uint32_t neighbor_min_sample_count = 0;
diff --git a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_unittest.cc b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_unittest.cc
index ce250f7..13288f0 100644
--- a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_unittest.cc
+++ b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_unittest.cc
@@ -14,7 +14,7 @@
#if !defined(__ANDROID__) && !defined(__ANDROID_HOST__)
#include "ui/events/ozone/evdev/event_device_test_util.h"
#else
-#include "chrome_to_android_compatibility_test_support.h"
+#include "chrome_to_android_compatibility.h"
#endif
#include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.h"
#include "ui/events/ozone/evdev/touch_filter/palm_detection_filter.h"
@@ -29,7 +29,8 @@ class MockNeuralModel : public NeuralStylusPalmDetectionFilterModel {
const NeuralStylusPalmDetectionFilterModelConfig&());
};
-class NeuralStylusPalmDetectionFilterTest : public testing::Test {
+class NeuralStylusPalmDetectionFilterTest
+ : public testing::TestWithParam<float> {
public:
NeuralStylusPalmDetectionFilterTest() = default;
@@ -48,6 +49,11 @@ class NeuralStylusPalmDetectionFilterTest : public testing::Test {
model_config_.heuristic_palm_touch_limit = 40;
model_config_.heuristic_palm_area_limit = 1000;
model_config_.max_dead_neighbor_time = base::Milliseconds(100);
+ const float resample_period = GetParam();
+ if (resample_period != 0.0) {
+ model_config_.resample_period = base::Milliseconds(resample_period);
+ sample_period_ = *model_config_.resample_period;
+ }
EXPECT_CALL(*model_, config())
.Times(testing::AnyNumber())
.WillRepeatedly(testing::ReturnRef(model_config_));
@@ -87,13 +93,19 @@ class NeuralStylusPalmDetectionFilterTest : public testing::Test {
MockNeuralModel* model_;
NeuralStylusPalmDetectionFilterModelConfig model_config_;
std::unique_ptr<PalmDetectionFilter> palm_detection_filter_;
+ base::TimeDelta sample_period_ = base::Milliseconds(8);
};
-class NeuralStylusPalmDetectionFilterDeathTest
- : public NeuralStylusPalmDetectionFilterTest {};
+INSTANTIATE_TEST_SUITE_P(ParametricFilterTest,
+ NeuralStylusPalmDetectionFilterTest,
+ ::testing::Values(0, 8.0),
+ [](const auto& paramInfo) {
+ return paramInfo.param != 0.0 ? "ResamplingEnabled"
+ : "ResamplingDisabled";
+ });
#if !defined(__ANDROID__) && !defined(__ANDROID_HOST__)
-TEST_F(NeuralStylusPalmDetectionFilterTest, EventDeviceSimpleTest) {
+TEST_P(NeuralStylusPalmDetectionFilterTest, EventDeviceSimpleTest) {
EventDeviceInfo devinfo;
std::vector<std::pair<DeviceCapabilities, bool>> devices = {
{kNocturneTouchScreen, true},
@@ -123,24 +135,26 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, EventDeviceSimpleTest) {
}
}
-TEST_F(NeuralStylusPalmDetectionFilterDeathTest, EventDeviceConstructionDeath) {
+TEST(NeuralStylusPalmDetectionFilterDeathTest, EventDeviceConstructionDeath) {
EventDeviceInfo bad_devinfo;
EXPECT_TRUE(CapabilitiesToDeviceInfo(kNocturneStylus, &bad_devinfo));
+ std::unique_ptr<NeuralStylusPalmDetectionFilterModel> model_(
+ new testing::StrictMock<MockNeuralModel>);
+ std::unique_ptr<SharedPalmDetectionFilterState> shared_palm_state =
+ std::make_unique<SharedPalmDetectionFilterState>();
EXPECT_DCHECK_DEATH({
- NeuralStylusPalmDetectionFilter f(
- bad_devinfo,
- std::unique_ptr<NeuralStylusPalmDetectionFilterModel>(model_),
- shared_palm_state.get());
+ NeuralStylusPalmDetectionFilter f(bad_devinfo, std::move(model_),
+ shared_palm_state.get());
});
}
#endif
-TEST_F(NeuralStylusPalmDetectionFilterTest, NameTest) {
+TEST_P(NeuralStylusPalmDetectionFilterTest, NameTest) {
EXPECT_EQ("NeuralStylusPalmDetectionFilter",
palm_detection_filter_->FilterNameForTesting());
}
-TEST_F(NeuralStylusPalmDetectionFilterTest, ShortTouchPalmAreaTest) {
+TEST_P(NeuralStylusPalmDetectionFilterTest, ShortTouchPalmAreaTest) {
std::bitset<kNumTouchEvdevSlots> actual_held, actual_cancelled,
expected_cancelled;
touch_[0].touching = true;
@@ -167,7 +181,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, ShortTouchPalmAreaTest) {
EXPECT_EQ(expected_cancelled, actual_cancelled);
}
-TEST_F(NeuralStylusPalmDetectionFilterTest, ShortTouchPalmSizeTest) {
+TEST_P(NeuralStylusPalmDetectionFilterTest, ShortTouchPalmSizeTest) {
std::bitset<kNumTouchEvdevSlots> actual_held, actual_cancelled;
touch_[0].touching = true;
touch_[0].tracking_id = 600;
@@ -185,7 +199,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, ShortTouchPalmSizeTest) {
touch_[0].was_touching = true;
touch_[0].touching = false;
touch_[0].tracking_id = -1;
- touch_time += base::Milliseconds(8.0f);
+ touch_time += sample_period_;
palm_detection_filter_->Filter(touch_, touch_time, &actual_held,
&actual_cancelled);
EXPECT_TRUE(actual_held.none());
@@ -203,7 +217,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, ShortTouchPalmSizeTest) {
touch_[0].was_touching = true;
touch_[0].touching = false;
touch_[0].tracking_id = -1;
- touch_time += base::Milliseconds(8.0f);
+ touch_time += sample_period_;
palm_detection_filter_->Filter(touch_, touch_time, &actual_held,
&actual_cancelled);
EXPECT_TRUE(actual_held.none());
@@ -212,7 +226,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, ShortTouchPalmSizeTest) {
EXPECT_TRUE(actual_cancelled.none());
}
-TEST_F(NeuralStylusPalmDetectionFilterTest, CallFilterTest) {
+TEST_P(NeuralStylusPalmDetectionFilterTest, CallFilterTest) {
// Set up 3 touches as touching:
// Touch 0 starts off and is sent twice
// Touch 1 and 2 are then added on: 2 is far away, 1 is nearby. We expect a
@@ -259,7 +273,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, CallFilterTest) {
touch_[2].tracking_id = 502;
touch_[2].slot = 2;
- touch_time += base::Milliseconds(8.0f);
+ touch_time += sample_period_;
palm_detection_filter_->Filter(touch_, touch_time, &actual_held,
&actual_cancelled);
EXPECT_TRUE(actual_held.none());
@@ -284,7 +298,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, CallFilterTest) {
Inference(testing::Pointwise(testing::FloatEq(), features)))
.Times(1)
.WillOnce(testing::Return(0.5));
- touch_time += base::Milliseconds(8.0f);
+ touch_time += sample_period_;
palm_detection_filter_->Filter(touch_, touch_time, &actual_held,
&actual_cancelled);
EXPECT_TRUE(actual_held.none());
@@ -306,7 +320,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, CallFilterTest) {
Inference(testing::Pointwise(testing::FloatEq(), features)))
.Times(1)
.WillOnce(testing::Return(0.5));
- touch_time += base::Milliseconds(8.0f);
+ touch_time += sample_period_;
palm_detection_filter_->Filter(touch_, touch_time, &actual_held,
&actual_cancelled);
EXPECT_TRUE(actual_held.none());
@@ -314,7 +328,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, CallFilterTest) {
EXPECT_EQ(actual_cancelled, expected_cancelled);
}
-TEST_F(NeuralStylusPalmDetectionFilterTest, CallFilterTestWithAdaptiveHold) {
+TEST_P(NeuralStylusPalmDetectionFilterTest, CallFilterTestWithAdaptiveHold) {
std::bitset<kNumTouchEvdevSlots> actual_held, actual_cancelled;
std::bitset<kNumTouchEvdevSlots> expected_held, expected_cancelled;
@@ -369,7 +383,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, CallFilterTestWithAdaptiveHold) {
Inference(testing::Pointwise(testing::FloatEq(), features)))
.Times(1)
.WillOnce(testing::Return(0.5));
- touch_time += base::Milliseconds(8.0f);
+ touch_time += sample_period_;
palm_detection_filter_->Filter(touch_, touch_time, &actual_held,
&actual_cancelled);
// Slot 0 is held.
@@ -425,7 +439,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, CallFilterTestWithAdaptiveHold) {
.Times(1)
.WillOnce(testing::Return(0.5));
- touch_time += base::Milliseconds(8.0f);
+ touch_time += sample_period_;
palm_detection_filter_->Filter(touch_, touch_time, &actual_held,
&actual_cancelled);
@@ -464,7 +478,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, CallFilterTestWithAdaptiveHold) {
Inference(testing::Pointwise(testing::FloatEq(), features)))
.Times(1)
.WillOnce(testing::Return(0.5));
- touch_time += base::Milliseconds(8.0f);
+ touch_time += sample_period_;
palm_detection_filter_->Filter(touch_, touch_time, &actual_held,
&actual_cancelled);
@@ -483,7 +497,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, CallFilterTestWithAdaptiveHold) {
EXPECT_EQ(actual_cancelled, expected_cancelled);
}
-TEST_F(NeuralStylusPalmDetectionFilterTest, InferenceOnceNotPalm) {
+TEST_P(NeuralStylusPalmDetectionFilterTest, InferenceOnceNotPalm) {
std::bitset<kNumTouchEvdevSlots> actual_held, actual_cancelled;
base::TimeTicks touch_time =
base::TimeTicks::UnixEpoch() + base::Milliseconds(10.0);
@@ -501,7 +515,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, InferenceOnceNotPalm) {
if (i != 0) {
touch_[0].was_touching = true;
}
- touch_time += base::Milliseconds(8.0f);
+ touch_time += sample_period_;
palm_detection_filter_->Filter(touch_, touch_time, &actual_held,
&actual_cancelled);
ASSERT_TRUE(actual_held.none()) << " Failed at " << i;
@@ -509,7 +523,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, InferenceOnceNotPalm) {
}
}
-TEST_F(NeuralStylusPalmDetectionFilterTest, InferenceOncePalm) {
+TEST_P(NeuralStylusPalmDetectionFilterTest, InferenceOncePalm) {
std::bitset<kNumTouchEvdevSlots> actual_held, actual_cancelled;
std::bitset<kNumTouchEvdevSlots> expected_cancelled;
base::TimeTicks touch_time =
@@ -533,7 +547,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, InferenceOncePalm) {
if (i != 0) {
touch_[0].was_touching = true;
}
- touch_time += base::Milliseconds(8.0f);
+ touch_time += sample_period_;
palm_detection_filter_->Filter(touch_, touch_time, &actual_held,
&actual_cancelled);
ASSERT_EQ(original_finger_time,
@@ -553,7 +567,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, InferenceOncePalm) {
}
}
-TEST_F(NeuralStylusPalmDetectionFilterTest, DelayShortFingerTouch) {
+TEST_P(NeuralStylusPalmDetectionFilterTest, DelayShortFingerTouch) {
std::bitset<kNumTouchEvdevSlots> actual_held, actual_cancelled;
std::bitset<kNumTouchEvdevSlots> expected_held, expected_cancelled;
model_config_.heuristic_delay_start_if_palm = true;
@@ -573,7 +587,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, DelayShortFingerTouch) {
EXPECT_EQ(expected_cancelled, actual_cancelled);
}
-TEST_F(NeuralStylusPalmDetectionFilterTest, DelayShortPalmTouch) {
+TEST_P(NeuralStylusPalmDetectionFilterTest, DelayShortPalmTouch) {
std::bitset<kNumTouchEvdevSlots> actual_held, actual_cancelled;
std::bitset<kNumTouchEvdevSlots> expected_held, expected_cancelled;
model_config_.heuristic_delay_start_if_palm = true;
@@ -596,7 +610,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, DelayShortPalmTouch) {
// Delay continues even afterwards, until inference time: then it's off.
for (uint32_t i = 1; i < model_config_.max_sample_count - 1; ++i) {
touch_[0].was_touching = true;
- touch_time += base::Milliseconds(10.0);
+ touch_time += sample_period_;
touch_[0].major = 15;
touch_[0].minor = 15;
touch_[0].x += 1;
@@ -610,7 +624,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, DelayShortPalmTouch) {
EXPECT_CALL(*model_, Inference(testing::_))
.Times(1)
.WillOnce(testing::Return(-0.1));
- touch_time = base::TimeTicks::UnixEpoch() + base::Milliseconds(10.0);
+ touch_time += sample_period_;
palm_detection_filter_->Filter(touch_, touch_time, &actual_held,
&actual_cancelled);
expected_held.set(0, false);
diff --git a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.cc b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.cc
index d1813ef..7c581dd 100644
--- a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.cc
+++ b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.cc
@@ -4,6 +4,7 @@
#include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.h"
+#include <base/logging.h>
#include <algorithm>
namespace ui {
@@ -80,7 +81,7 @@ const static auto kPreferInitialEventDelay =
* not interpolated, the values are taken from the 'after' sample unless the
* requested time is very close to the 'before' sample.
*/
-PalmFilterSample getSampleAtTime(base::TimeTicks time,
+PalmFilterSample GetSampleAtTime(base::TimeTicks time,
const PalmFilterSample& before,
const PalmFilterSample& after) {
// Use the newest sample as the base, except when the requested time is very
@@ -153,16 +154,30 @@ PalmFilterStroke::~PalmFilterStroke() {}
void PalmFilterStroke::ProcessSample(const PalmFilterSample& sample) {
DCHECK_EQ(tracking_id_, sample.tracking_id);
- if (resample_period_.has_value()) {
- Resample(sample);
- return;
+ if (samples_seen_ == 0) {
+ first_sample_time_ = sample.time;
}
AddSample(sample);
- while (samples_.size() > max_sample_count_) {
- AddToUnscaledCentroid(-samples_.front().point.OffsetFromOrigin());
- samples_.pop_front();
+ if (resample_period_.has_value()) {
+ // Prune based on time
+ const base::TimeDelta max_duration =
+ (*resample_period_) * (max_sample_count_ - 1);
+ while (samples_.size() > 2 &&
+ samples_.back().time - samples_[1].time >= max_duration) {
+ // We can only discard the sample if after it's discarded, we still cover
+ // the entire range. If we don't, we need to keep this sample for
+ // calculating resampled values.
+ AddToUnscaledCentroid(-samples_.front().point.OffsetFromOrigin());
+ samples_.pop_front();
+ }
+ } else {
+ // Prune based on number of samples
+ while (samples_.size() > max_sample_count_) {
+ AddToUnscaledCentroid(-samples_.front().point.OffsetFromOrigin());
+ samples_.pop_front();
+ }
}
}
@@ -172,36 +187,6 @@ void PalmFilterStroke::AddSample(const PalmFilterSample& sample) {
samples_seen_++;
}
-/**
- * When resampling is enabled, we don't store all samples. Only the resampled
- * values are stored into samples_. In addition, the last real event is stored
- * into last_sample_, which is used to calculate the resampled values.
- */
-void PalmFilterStroke::Resample(const PalmFilterSample& sample) {
- if (samples_seen_ == 0) {
- AddSample(sample);
- last_sample_ = sample;
- return;
- }
-
- // We already have a valid last sample here.
- DCHECK_LE(last_sample_.time, sample.time);
- // Generate resampled values
- base::TimeTicks next_sample_time = samples_.back().time + *resample_period_;
- while (next_sample_time <= sample.time) {
- AddSample(getSampleAtTime(next_sample_time, last_sample_, sample));
- next_sample_time = samples_.back().time + (*resample_period_);
- }
- last_sample_ = sample;
-
- // Prune the resampled collection
- while ((samples_.back().time - samples_.front().time) >=
- (*resample_period_) * max_sample_count_) {
- AddToUnscaledCentroid(-samples_.front().point.OffsetFromOrigin());
- samples_.pop_front();
- }
-}
-
void PalmFilterStroke::AddToUnscaledCentroid(const gfx::Vector2dF point) {
const gfx::Vector2dF corrected_point = point - unscaled_centroid_sum_error_;
const gfx::PointF new_unscaled_centroid =
@@ -226,6 +211,47 @@ int PalmFilterStroke::tracking_id() const {
return tracking_id_;
}
+base::TimeDelta PalmFilterStroke::Duration() const {
+ if (samples_.empty()) {
+ LOG(DFATAL) << "No samples available";
+ return base::Milliseconds(0);
+ }
+ return samples_.back().time - first_sample_time_;
+}
+
+base::TimeDelta PalmFilterStroke::PreviousDuration() const {
+ if (samples_.size() <= 1) {
+ LOG(DFATAL) << "Not enough samples";
+ return base::Milliseconds(0);
+ }
+ const PalmFilterSample& secondToLastSample = samples_.rbegin()[1];
+ return secondToLastSample.time - first_sample_time_;
+}
+
+bool PalmFilterStroke::LastSampleCrossed(base::TimeDelta duration) const {
+ if (samples_.size() <= 1) {
+ // If there's only 1 sample, stroke just started and Duration() is zero.
+ return false;
+ }
+ return PreviousDuration() < duration && duration <= Duration();
+}
+
+PalmFilterSample PalmFilterStroke::GetSampleAt(base::TimeTicks time) const {
+ size_t i = 0;
+ for (; i < samples_.size() && samples_[i].time < time; ++i) {
+ }
+
+ if (i < samples_.size() && !samples_.empty() && samples_[i].time == time) {
+ return samples_[i];
+ }
+ if (i == 0 || i == samples_.size()) {
+ LOG(DFATAL) << "Invalid index: " << i
+ << ", can't interpolate for time: " << time;
+ return {};
+ }
+ return GetSampleAtTime(time, samples_[i - 1], samples_[i]);
+}
+
uint64_t PalmFilterStroke::samples_seen() const {
return samples_seen_;
}
@@ -312,11 +338,10 @@ std::ostream& operator<<(std::ostream& out, const PalmFilterStroke& stroke) {
out << " max_sample_count_ = " << stroke.max_sample_count_ << "\n";
if (stroke.resample_period_) {
out << " resample_period_ = " << *(stroke.resample_period_) << "\n";
- out << " last_sample_ = " << stroke.last_sample_ << "\n";
} else {
out << " resample_period_ = <not set>\n";
- out << " last_sample_ = <not valid b/c resampling is off>\n";
}
+ out << " first_sample_time_ = " << stroke.first_sample_time_ << "\n";
out << " unscaled_centroid_ = " << stroke.unscaled_centroid_ << "\n";
out << " unscaled_centroid_sum_error_ = "
<< stroke.unscaled_centroid_sum_error_ << "\n";
diff --git a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.h b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.h
index 47f0c33..d50a32d 100644
--- a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.h
+++ b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.h
@@ -12,7 +12,7 @@
#if defined(__ANDROID__) || defined(__ANDROID_HOST__)
#undef LOG_INFO
#undef LOG_WARNING
-#include <chrome_to_android_compatibility_test_support.h>
+#include <chrome_to_android_compatibility.h>
#endif
#include "base/time/time.h"
#if !defined(__ANDROID__) && !defined(__ANDROID_HOST__)
@@ -85,6 +85,25 @@ class COMPONENT_EXPORT(EVDEV) PalmFilterStroke {
float BiggestSize() const;
// If no elements in stroke, returns 0.0;
float MaxMajorRadius() const;
+ /**
+ * Return the time duration of this stroke.
+ */
+ base::TimeDelta Duration() const;
+ /**
+ * Provide a (potentially resampled) sample at the requested time.
+ * Only interpolation is allowed.
+ * The requested time must be within the window at which the gesture occurred.
+ */
+ PalmFilterSample GetSampleAt(base::TimeTicks time) const;
+
+ /**
+ * Return true if the provided duration is between the duration of the
+ * previous sample and the current sample. In other words, if the addition of
+ * the last sample caused the total stroke duration to exceed the provided
+ * duration. Return false otherwise.
+ */
+ bool LastSampleCrossed(base::TimeDelta duration) const;
+
const std::deque<PalmFilterSample>& samples() const;
uint64_t samples_seen() const;
int tracking_id() const;
@@ -92,10 +111,8 @@ class COMPONENT_EXPORT(EVDEV) PalmFilterStroke {
private:
void AddToUnscaledCentroid(const gfx::Vector2dF point);
void AddSample(const PalmFilterSample& sample);
- /**
- * Process the sample. Potentially store the resampled sample into samples_.
- */
- void Resample(const PalmFilterSample& sample);
+
+ base::TimeDelta PreviousDuration() const;
std::deque<PalmFilterSample> samples_;
const int tracking_id_;
@@ -108,13 +125,9 @@ class COMPONENT_EXPORT(EVDEV) PalmFilterStroke {
* number of times 'AddSample' has been called.
*/
uint64_t samples_seen_ = 0;
- /**
- * The last sample seen by the model. Used when resampling is enabled in order
- * to compute the resampled value.
- */
- PalmFilterSample last_sample_;
const uint64_t max_sample_count_;
+ base::TimeTicks first_sample_time_;
const base::Optional<base::TimeDelta> resample_period_;
gfx::PointF unscaled_centroid_ = gfx::PointF(0., 0.);
diff --git a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util_unittest.cc b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util_unittest.cc
index ac3c841..e7019b0 100644
--- a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util_unittest.cc
+++ b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util_unittest.cc
@@ -13,7 +13,7 @@
#include "testing/gtest/include/gtest/gtest.h"
#if defined(__ANDROID__) || defined(__ANDROID_HOST__)
#include <linux/input-event-codes.h>
-#include "chrome_to_android_compatibility_test_support.h"
+#include "chrome_to_android_compatibility.h"
#else
#include "ui/events/ozone/evdev/event_device_test_util.h"
#endif
@@ -393,23 +393,24 @@ TEST(PalmFilterStrokeTest, NumberOfResampledValues) {
ASSERT_THAT(stroke.samples(), ElementsAre(SampleTime(down_time)));
ASSERT_EQ(1u, stroke.samples_seen());
- // Add second sample at time = T + 2ms. It's not yet time for the new frame,
- // so no new sample should be generated.
+ // Add second sample at time = T + 4ms. All samples are stored, even if it's
+ // before the next resample time.
base::TimeTicks time = down_time + base::Milliseconds(4);
sample = CreatePalmFilterSample(touch_, time, model_config_, device_info);
stroke.ProcessSample(sample);
- ASSERT_THAT(stroke.samples(), ElementsAre(SampleTime(down_time)));
- ASSERT_EQ(1u, stroke.samples_seen());
+ ASSERT_THAT(stroke.samples(),
+ ElementsAre(SampleTime(down_time), SampleTime(time)));
+ ASSERT_EQ(2u, stroke.samples_seen());
- // Add third sample at time = T + 10ms. An event at time = T + 8ms should be
- // generated.
+ // Add third sample at time = T + 10ms.
time = down_time + base::Milliseconds(10);
sample = CreatePalmFilterSample(touch_, time, model_config_, device_info);
stroke.ProcessSample(sample);
ASSERT_THAT(stroke.samples(),
ElementsAre(SampleTime(down_time),
- SampleTime(down_time + base::Milliseconds(8))));
- ASSERT_EQ(2u, stroke.samples_seen());
+ SampleTime(down_time + base::Milliseconds(4)),
+ SampleTime(down_time + base::Milliseconds(10))));
+ ASSERT_EQ(3u, stroke.samples_seen());
}
TEST(PalmFilterStrokeTest, ResamplingTest) {
@@ -446,7 +447,7 @@ TEST(PalmFilterStrokeTest, ResamplingTest) {
CreatePalmFilterSample(touch_, time, model_config_, device_info);
stroke.ProcessSample(sample2);
// The samples should remain unchanged
- ASSERT_THAT(stroke.samples(), ElementsAre(sample1));
+ ASSERT_THAT(stroke.samples(), ElementsAre(sample1, sample2));
// Add third sample at time = T + 12ms. A resampled event at time = T + 8ms
// should be generated.
@@ -458,16 +459,16 @@ TEST(PalmFilterStrokeTest, ResamplingTest) {
PalmFilterSample sample3 =
CreatePalmFilterSample(touch_, time, model_config_, device_info);
stroke.ProcessSample(sample3);
- ASSERT_THAT(
- stroke.samples(),
- ElementsAre(sample1, SampleTime(down_time + base::Milliseconds(8))));
-
- EXPECT_EQ(150, stroke.samples().back().point.x());
- EXPECT_EQ(22, stroke.samples().back().point.y());
- EXPECT_EQ(14, stroke.samples().back().major_radius);
- EXPECT_EQ(13, stroke.samples().back().minor_radius);
+ ASSERT_THAT(stroke.samples(), ElementsAre(sample1, sample2, sample3));
}
+/**
+ * There should always be at least (max_sample_count - 1) * resample_period
+ * worth of samples. However, that's not sufficient. In the cases where the gap
+ * between samples is very large (larger than the sample horizon), there needs
+ * to be another sample in order to calculate resampled values throughout the
+ * entire range.
+ */
TEST(PalmFilterStrokeTest, MultipleResampledValues) {
NeuralStylusPalmDetectionFilterModelConfig model_config_;
model_config_.max_sample_count = 3;
@@ -491,8 +492,7 @@ TEST(PalmFilterStrokeTest, MultipleResampledValues) {
// First sample should go in as is
ASSERT_THAT(stroke.samples(), ElementsAre(sample1));
- // Add second sample at time = T + 20ms. Two resampled values should be
- // generated: 1) at time = T+8ms 2) at time = T+16ms
+ // Add second sample at time = T + 20ms.
base::TimeTicks time = down_time + base::Milliseconds(20);
touch_.x = 20;
touch_.y = 30;
@@ -501,22 +501,23 @@ TEST(PalmFilterStrokeTest, MultipleResampledValues) {
PalmFilterSample sample2 =
CreatePalmFilterSample(touch_, time, model_config_, device_info);
stroke.ProcessSample(sample2);
- ASSERT_THAT(stroke.samples(),
- ElementsAre(SampleTime(down_time),
- SampleTime(down_time + base::Milliseconds(8)),
- SampleTime(down_time + base::Milliseconds(16))));
-
- // First sample : time = T + 8ms
- EXPECT_EQ(8, stroke.samples()[1].point.x());
- EXPECT_EQ(18, stroke.samples()[1].point.y());
- EXPECT_EQ(220, stroke.samples()[1].major_radius);
- EXPECT_EQ(120, stroke.samples()[1].minor_radius);
-
- // Second sample : time = T + 16ms
- EXPECT_EQ(16, stroke.samples().back().point.x());
- EXPECT_EQ(26, stroke.samples().back().point.y());
- EXPECT_EQ(220, stroke.samples().back().major_radius);
- EXPECT_EQ(120, stroke.samples().back().minor_radius);
+
+ ASSERT_THAT(stroke.samples(), ElementsAre(sample1, sample2));
+
+ // Verify resampled sample : time = T + 8ms
+ PalmFilterSample resampled_sample =
+ stroke.GetSampleAt(down_time + base::Milliseconds(8));
+ EXPECT_EQ(8, resampled_sample.point.x());
+ EXPECT_EQ(18, resampled_sample.point.y());
+ EXPECT_EQ(220, resampled_sample.major_radius);
+ EXPECT_EQ(120, resampled_sample.minor_radius);
+
+ // Verify resampled sample : time = T + 16ms
+ resampled_sample = stroke.GetSampleAt(down_time + base::Milliseconds(16));
+ EXPECT_EQ(16, resampled_sample.point.x());
+ EXPECT_EQ(26, resampled_sample.point.y());
+ EXPECT_EQ(220, resampled_sample.major_radius);
+ EXPECT_EQ(120, resampled_sample.minor_radius);
}
} // namespace ui