1 // Copyright 2019 The Chromium Authors. All rights reserved.
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 "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.h"
6
7 #include <algorithm>
8 #include <cstdint>
9 #include <utility>
10 #include <vector>
11
12 #include "testing/gmock/include/gmock/gmock.h"
13 #include "testing/gtest/include/gtest/gtest.h"
14 #if defined(__ANDROID__) || defined(__ANDROID_HOST__)
15 #include <linux/input-event-codes.h>
16 #include "chrome_to_android_compatibility.h"
17 #else
18 #include "ui/events/ozone/evdev/event_device_test_util.h"
19 #endif
20 #include "ui/events/ozone/evdev/touch_evdev_types.h"
21 #include "ui/events/ozone/evdev/touch_filter/palm_detection_filter.h"
22 #include "ui/events/ozone/evdev/touch_filter/shared_palm_detection_filter_state.h"
23
24 using ::testing::ElementsAre;
25 using ::testing::IsEmpty;
26
27 namespace ui {
28
29 #if defined(__ANDROID__) || defined(__ANDROID_HOST__)
30 /**
31 * The tests that require an actual device (something that responds to ioctls)
32 * have been removed. The rest of the tests were simplified by modifying the
33 * 'CreatePalmFilterDeviceInfo' method.
34 */
35 enum DeviceType {
36 kNocturneTouchScreen,
37 kLinkTouchscreen,
38 kKohakuTouchscreen,
39 };
40
CreatePalmFilterDeviceInfo(DeviceType deviceType)41 static PalmFilterDeviceInfo CreatePalmFilterDeviceInfo(DeviceType deviceType) {
42 // Default value for resolution is 1 if it's not specified (or set to 0). See
43 // https://source.chromium.org/chromium/chromium/src/+/main:ui/events/ozone/evdev/touch_filter/
44 // neural_stylus_palm_detection_filter_util.cc;l=11;drc=7f74e1d22e2d0b91856ea6b3619098cd05ef6158
45
46 switch (deviceType) {
47 case kNocturneTouchScreen:
48 return PalmFilterDeviceInfo{
49 .max_x = 10404,
50 .max_y = 6936,
51 .x_res = 40,
52 .y_res = 40,
53 .major_radius_res = 1,
54 .minor_radius_res = 1,
55 .minor_radius_supported = true,
56 };
57 case kLinkTouchscreen:
58 return PalmFilterDeviceInfo{
59 .max_x = 2559,
60 .max_y = 1699,
61 .x_res = 20,
62 .y_res = 20,
63 .major_radius_res = 1,
64 .minor_radius_res = 1,
65 .minor_radius_supported = false,
66 };
67 case kKohakuTouchscreen:
68 return PalmFilterDeviceInfo{
69 .max_x = 2559,
70 .max_y = 1699,
71 .x_res = 1,
72 .y_res = 1,
73 .major_radius_res = 1,
74 .minor_radius_res = 1,
75 .minor_radius_supported = false,
76 };
77 }
78 }
79
80 class EventDeviceInfo {
81 public:
GetAbsResolution(int)82 float GetAbsResolution(int /*axis*/) { return 20; }
83 };
84
CapabilitiesToDeviceInfo(DeviceType,EventDeviceInfo *)85 bool CapabilitiesToDeviceInfo(DeviceType, EventDeviceInfo*) {
86 return true;
87 }
88 #endif
89
90 MATCHER_P(SampleTime, time, "Does the sample have given time.") {
91 *result_listener << "Sample time" << arg.time << " is not " << time;
92 return time == arg.time;
93 }
94
95 class NeuralStylusPalmDetectionFilterUtilTest
96 : public testing::TestWithParam<bool> {
97 public:
98 NeuralStylusPalmDetectionFilterUtilTest() = default;
99
100 NeuralStylusPalmDetectionFilterUtilTest(
101 const NeuralStylusPalmDetectionFilterUtilTest&) = delete;
102 NeuralStylusPalmDetectionFilterUtilTest& operator=(
103 const NeuralStylusPalmDetectionFilterUtilTest&) = delete;
104
SetUp()105 void SetUp() override {
106 EXPECT_TRUE(
107 CapabilitiesToDeviceInfo(kNocturneTouchScreen, &nocturne_touchscreen_));
108 touch_.major = 25;
109 touch_.minor = 24;
110 touch_.pressure = 23;
111 touch_.tracking_id = 22;
112 touch_.x = 21;
113 touch_.y = 20;
114 model_config_.max_sample_count = 3;
115 const bool resample_touch = GetParam();
116 if (resample_touch) {
117 model_config_.resample_period = base::Milliseconds(8);
118 }
119 }
120
121 protected:
122 InProgressTouchEvdev touch_;
123 EventDeviceInfo nocturne_touchscreen_;
124 NeuralStylusPalmDetectionFilterModelConfig model_config_;
125 };
126
127 INSTANTIATE_TEST_SUITE_P(ParametricUtilTest,
128 NeuralStylusPalmDetectionFilterUtilTest,
129 ::testing::Bool(),
__anon8fbd2f5b0102(const auto& paramInfo) 130 [](const auto& paramInfo) {
131 return paramInfo.param ? "ResamplingEnabled"
132 : "ResamplingDisabled";
133 });
134
135 #if !defined(__ANDROID__) && !defined(__ANDROID_HOST__)
TEST_P(NeuralStylusPalmDetectionFilterUtilTest,DistilledNocturneTest)136 TEST_P(NeuralStylusPalmDetectionFilterUtilTest, DistilledNocturneTest) {
137 const PalmFilterDeviceInfo nocturne_distilled =
138 CreatePalmFilterDeviceInfo(nocturne_touchscreen_);
139 EXPECT_FLOAT_EQ(nocturne_distilled.max_x,
140 nocturne_touchscreen_.GetAbsMaximum(ABS_MT_POSITION_X));
141 EXPECT_FLOAT_EQ(nocturne_distilled.max_y,
142 nocturne_touchscreen_.GetAbsMaximum(ABS_MT_POSITION_Y));
143 EXPECT_FLOAT_EQ(nocturne_distilled.x_res,
144 nocturne_touchscreen_.GetAbsResolution(ABS_MT_POSITION_X));
145 EXPECT_FLOAT_EQ(nocturne_distilled.y_res,
146 nocturne_touchscreen_.GetAbsResolution(ABS_MT_POSITION_Y));
147 EXPECT_FLOAT_EQ(nocturne_distilled.major_radius_res,
148 nocturne_touchscreen_.GetAbsResolution(ABS_MT_TOUCH_MAJOR));
149 EXPECT_TRUE(nocturne_distilled.minor_radius_supported);
150 EXPECT_FLOAT_EQ(nocturne_distilled.minor_radius_res,
151 nocturne_touchscreen_.GetAbsResolution(ABS_MT_TOUCH_MINOR));
152 }
153
TEST_P(NeuralStylusPalmDetectionFilterUtilTest,NoMinorResTest)154 TEST_P(NeuralStylusPalmDetectionFilterUtilTest, NoMinorResTest) {
155 // Nocturne has minor resolution, but let's pretend it doesn't. we should
156 // recover "1" as the resolution.
157 auto abs_info = nocturne_touchscreen_.GetAbsInfoByCode(ABS_MT_TOUCH_MINOR);
158 abs_info.resolution = 0;
159 nocturne_touchscreen_.SetAbsInfo(ABS_MT_TOUCH_MINOR, abs_info);
160 const PalmFilterDeviceInfo nocturne_distilled =
161 CreatePalmFilterDeviceInfo(nocturne_touchscreen_);
162 EXPECT_EQ(1, nocturne_distilled.minor_radius_res);
163 EXPECT_EQ(1, nocturne_distilled.major_radius_res);
164 }
165 #endif
166
TEST_P(NeuralStylusPalmDetectionFilterUtilTest,DistillerKohakuTest)167 TEST_P(NeuralStylusPalmDetectionFilterUtilTest, DistillerKohakuTest) {
168 EventDeviceInfo kohaku_touchscreen;
169 ASSERT_TRUE(
170 CapabilitiesToDeviceInfo(kKohakuTouchscreen, &kohaku_touchscreen));
171 const PalmFilterDeviceInfo kohaku_distilled =
172 #if !defined(__ANDROID__) && !defined(__ANDROID_HOST__)
173 CreatePalmFilterDeviceInfo(kohaku_touchscreen);
174 #else
175 CreatePalmFilterDeviceInfo(kKohakuTouchscreen);
176 #endif
177 EXPECT_FALSE(kohaku_distilled.minor_radius_supported);
178 EXPECT_EQ(1, kohaku_distilled.x_res);
179 EXPECT_EQ(1, kohaku_distilled.y_res);
180 }
181
TEST_P(NeuralStylusPalmDetectionFilterUtilTest,DistilledLinkTest)182 TEST_P(NeuralStylusPalmDetectionFilterUtilTest, DistilledLinkTest) {
183 EventDeviceInfo link_touchscreen;
184 ASSERT_TRUE(CapabilitiesToDeviceInfo(kLinkTouchscreen, &link_touchscreen));
185 const PalmFilterDeviceInfo link_distilled =
186 #if !defined(__ANDROID__) && !defined(__ANDROID_HOST__)
187 CreatePalmFilterDeviceInfo(link_touchscreen);
188 #else
189 CreatePalmFilterDeviceInfo(kLinkTouchscreen);
190 #endif
191 EXPECT_FALSE(link_distilled.minor_radius_supported);
192 EXPECT_FLOAT_EQ(1.f, link_distilled.major_radius_res);
193 EXPECT_FLOAT_EQ(link_distilled.major_radius_res,
194 link_distilled.minor_radius_res);
195 }
196
TEST_P(NeuralStylusPalmDetectionFilterUtilTest,PalmFilterSampleTest)197 TEST_P(NeuralStylusPalmDetectionFilterUtilTest, PalmFilterSampleTest) {
198 base::TimeTicks time = base::TimeTicks() + base::Seconds(30);
199 const PalmFilterDeviceInfo nocturne_distilled =
200 #if !defined(__ANDROID__) && !defined(__ANDROID_HOST__)
201 CreatePalmFilterDeviceInfo(nocturne_touchscreen_);
202 #else
203 CreatePalmFilterDeviceInfo(kNocturneTouchScreen);
204 #endif
205 const PalmFilterSample sample =
206 CreatePalmFilterSample(touch_, time, model_config_, nocturne_distilled);
207 EXPECT_EQ(time, sample.time);
208 EXPECT_EQ(25, sample.major_radius);
209 EXPECT_EQ(24, sample.minor_radius);
210 EXPECT_EQ(23, sample.pressure);
211 EXPECT_EQ(22, sample.tracking_id);
212 EXPECT_EQ(gfx::PointF(21 / 40.f, 20 / 40.f), sample.point);
213 EXPECT_EQ(0.5, sample.edge);
214 }
215
TEST_P(NeuralStylusPalmDetectionFilterUtilTest,LinkTouchscreenSampleTest)216 TEST_P(NeuralStylusPalmDetectionFilterUtilTest, LinkTouchscreenSampleTest) {
217 EventDeviceInfo link_touchscreen;
218 base::TimeTicks time = base::TimeTicks() + base::Seconds(30);
219 ASSERT_TRUE(CapabilitiesToDeviceInfo(kLinkTouchscreen, &link_touchscreen));
220 const PalmFilterDeviceInfo link_distilled =
221 #if !defined(__ANDROID__) && !defined(__ANDROID_HOST__)
222 CreatePalmFilterDeviceInfo(link_touchscreen);
223 #else
224 CreatePalmFilterDeviceInfo(kLinkTouchscreen);
225 #endif
226 touch_.minor = 0; // no minor from link.
227 // use 40 as a base since model is trained on that kind of device.
228 model_config_.radius_polynomial_resize = {
229 link_touchscreen.GetAbsResolution(ABS_MT_POSITION_X) / 40.0f, 0.0};
230 const PalmFilterSample sample =
231 CreatePalmFilterSample(touch_, time, model_config_, link_distilled);
232 EXPECT_FLOAT_EQ(12.5, sample.major_radius);
233 EXPECT_FLOAT_EQ(12.5, sample.minor_radius);
234 }
235
TEST_P(NeuralStylusPalmDetectionFilterUtilTest,PalmFilterStrokeTest)236 TEST_P(NeuralStylusPalmDetectionFilterUtilTest, PalmFilterStrokeTest) {
237 PalmFilterStroke stroke(model_config_, /*tracking_id*/ 55);
238 touch_.tracking_id = 55;
239 // With no points, center is 0.
240 EXPECT_EQ(gfx::PointF(0., 0.), stroke.GetCentroid());
241
242 base::TimeTicks time = base::TimeTicks() + base::Seconds(30);
243 const PalmFilterDeviceInfo nocturne_distilled =
244 #if !defined(__ANDROID__) && !defined(__ANDROID_HOST__)
245 CreatePalmFilterDeviceInfo(nocturne_touchscreen_);
246 #else
247 CreatePalmFilterDeviceInfo(kNocturneTouchScreen);
248 #endif
249 // Deliberately long test to ensure floating point continued accuracy.
250 for (int i = 0; i < 500000; ++i) {
251 touch_.x = 15 + i;
252 PalmFilterSample sample =
253 CreatePalmFilterSample(touch_, time, model_config_, nocturne_distilled);
254 time += base::Milliseconds(8);
255 stroke.ProcessSample(std::move(sample));
256 EXPECT_EQ(touch_.tracking_id, stroke.tracking_id());
257 if (i < 3) {
258 if (i == 0) {
259 EXPECT_FLOAT_EQ(gfx::PointF(15 / 40.f, 0.5).x(),
260 stroke.GetCentroid().x());
261 } else if (i == 1) {
262 EXPECT_FLOAT_EQ(gfx::PointF((30 + 1) / (2 * 40.f), 0.5).x(),
263 stroke.GetCentroid().x());
264 } else if (i == 2) {
265 EXPECT_FLOAT_EQ(gfx::PointF((45 + 1 + 2) / (3 * 40.f), 0.5).x(),
266 stroke.GetCentroid().x());
267 }
268 continue;
269 }
270 float expected_x = (45 + 3 * i - 3) / (3 * 40.f);
271 gfx::PointF expected_centroid = gfx::PointF(expected_x, 0.5);
272 ASSERT_FLOAT_EQ(expected_centroid.x(), stroke.GetCentroid().x())
273 << "failed at i " << i;
274 }
275 }
276
TEST_P(NeuralStylusPalmDetectionFilterUtilTest,PalmFilterStrokeBiggestSizeTest)277 TEST_P(NeuralStylusPalmDetectionFilterUtilTest,
278 PalmFilterStrokeBiggestSizeTest) {
279 PalmFilterStroke stroke(model_config_, /*tracking_id*/ 0);
280 PalmFilterStroke no_minor_stroke(model_config_, /*tracking_id*/ 0);
281 touch_.tracking_id = stroke.tracking_id();
282 EXPECT_EQ(0, stroke.BiggestSize());
283
284 base::TimeTicks time = base::TimeTicks() + base::Seconds(30);
285 const PalmFilterDeviceInfo nocturne_distilled =
286 #if !defined(__ANDROID__) && !defined(__ANDROID_HOST__)
287 CreatePalmFilterDeviceInfo(nocturne_touchscreen_);
288 #else
289 CreatePalmFilterDeviceInfo(kNocturneTouchScreen);
290 #endif
291 for (int i = 0; i < 500; ++i) {
292 touch_.major = 2 + i;
293 touch_.minor = 1 + i;
294 PalmFilterSample sample =
295 CreatePalmFilterSample(touch_, time, model_config_, nocturne_distilled);
296 EXPECT_EQ(static_cast<uint64_t>(i), stroke.samples_seen());
297 stroke.ProcessSample(sample);
298 EXPECT_FLOAT_EQ((1 + i) * (2 + i), stroke.BiggestSize());
299
300 PalmFilterSample second_sample =
301 CreatePalmFilterSample(touch_, time, model_config_, nocturne_distilled);
302 second_sample.minor_radius = 0;
303 no_minor_stroke.ProcessSample(std::move(second_sample));
304 EXPECT_FLOAT_EQ((2 + i) * (2 + i), no_minor_stroke.BiggestSize());
305 ASSERT_EQ(std::min(3ul, 1ul + i), stroke.samples().size());
306 time += base::Milliseconds(8);
307 }
308 }
309
TEST_P(NeuralStylusPalmDetectionFilterUtilTest,UnscaledMajorMinorResolution)310 TEST_P(NeuralStylusPalmDetectionFilterUtilTest, UnscaledMajorMinorResolution) {
311 model_config_.radius_polynomial_resize = {};
312 PalmFilterDeviceInfo device_info;
313 device_info.x_res = 2;
314 device_info.y_res = 5;
315 device_info.major_radius_res = 2;
316 device_info.minor_radius_res = 5;
317 device_info.minor_radius_supported = true;
318 touch_.major = 20;
319 touch_.minor = 10;
320 touch_.orientation = 0;
321 base::TimeTicks time = base::TimeTicks::UnixEpoch() + base::Seconds(30);
322 PalmFilterSample sample =
323 CreatePalmFilterSample(touch_, time, model_config_, device_info);
324 EXPECT_EQ(20 / 2, sample.major_radius);
325 EXPECT_EQ(10 / 5, sample.minor_radius);
326 }
327
TEST_P(NeuralStylusPalmDetectionFilterUtilTest,StrokeGetMaxMajorTest)328 TEST_P(NeuralStylusPalmDetectionFilterUtilTest, StrokeGetMaxMajorTest) {
329 PalmFilterStroke stroke(model_config_, /*tracking_id*/ 0);
330 touch_.tracking_id = stroke.tracking_id();
331 EXPECT_FLOAT_EQ(0, stroke.MaxMajorRadius());
332 base::TimeTicks time = base::TimeTicks::UnixEpoch() + base::Seconds(30);
333 const PalmFilterDeviceInfo nocturne_distilled =
334 #if !defined(__ANDROID__) && !defined(__ANDROID_HOST__)
335 CreatePalmFilterDeviceInfo(nocturne_touchscreen_);
336 #else
337 CreatePalmFilterDeviceInfo(kNocturneTouchScreen);
338 #endif
339 for (int i = 1; i < 50; ++i) {
340 touch_.major = i;
341 touch_.minor = i - 1;
342 PalmFilterSample sample =
343 CreatePalmFilterSample(touch_, time, model_config_, nocturne_distilled);
344 time += base::Milliseconds(8);
345 EXPECT_EQ(static_cast<uint64_t>(i - 1), stroke.samples_seen());
346 stroke.ProcessSample(sample);
347 EXPECT_FLOAT_EQ(i, stroke.MaxMajorRadius());
348 }
349 }
350
TEST_P(NeuralStylusPalmDetectionFilterUtilTest,SampleRadiusConversion)351 TEST_P(NeuralStylusPalmDetectionFilterUtilTest, SampleRadiusConversion) {
352 // A single number: a _constant_.
353 model_config_.radius_polynomial_resize = {71.3};
354 base::TimeTicks time = base::TimeTicks::UnixEpoch() + base::Seconds(30);
355 const PalmFilterDeviceInfo nocturne_distilled =
356 #if !defined(__ANDROID__) && !defined(__ANDROID_HOST__)
357 CreatePalmFilterDeviceInfo(nocturne_touchscreen_);
358 #else
359 CreatePalmFilterDeviceInfo(kNocturneTouchScreen);
360 #endif
361 PalmFilterSample sample =
362 CreatePalmFilterSample(touch_, time, model_config_, nocturne_distilled);
363 EXPECT_FLOAT_EQ(71.3, sample.major_radius);
364 EXPECT_FLOAT_EQ(71.3, sample.minor_radius);
365
366 // 0.1*r^2 + 0.4*r - 5.0
367 model_config_.radius_polynomial_resize = {0.1, 0.4, -5.0};
368 sample =
369 CreatePalmFilterSample(touch_, time, model_config_, nocturne_distilled);
370 EXPECT_FLOAT_EQ(0.1 * 25 * 25 + 0.4 * 25 - 5.0, sample.major_radius);
371 EXPECT_FLOAT_EQ(0.1 * 24 * 24 + 0.4 * 24 - 5.0, sample.minor_radius);
372 }
373
TEST(PalmFilterStrokeTest,NumberOfResampledValues)374 TEST(PalmFilterStrokeTest, NumberOfResampledValues) {
375 NeuralStylusPalmDetectionFilterModelConfig model_config_;
376 model_config_.max_sample_count = 3;
377 model_config_.resample_period = base::Milliseconds(8);
378 base::TimeTicks down_time = base::TimeTicks::UnixEpoch() + base::Seconds(30);
379
380 PalmFilterStroke stroke(model_config_, /*tracking_id*/ 0);
381 const PalmFilterDeviceInfo device_info;
382
383 // Initially, no samples
384 ASSERT_THAT(stroke.samples(), IsEmpty());
385 ASSERT_EQ(0u, stroke.samples_seen());
386
387 // Add first sample at time = T
388 InProgressTouchEvdev touch_;
389 touch_.tracking_id = stroke.tracking_id();
390 PalmFilterSample sample =
391 CreatePalmFilterSample(touch_, down_time, model_config_, device_info);
392 stroke.ProcessSample(sample);
393 ASSERT_THAT(stroke.samples(), ElementsAre(SampleTime(down_time)));
394 ASSERT_EQ(1u, stroke.samples_seen());
395
396 // Add second sample at time = T + 4ms. All samples are stored, even if it's
397 // before the next resample time.
398 base::TimeTicks time = down_time + base::Milliseconds(4);
399 sample = CreatePalmFilterSample(touch_, time, model_config_, device_info);
400 stroke.ProcessSample(sample);
401 ASSERT_THAT(stroke.samples(),
402 ElementsAre(SampleTime(down_time), SampleTime(time)));
403 ASSERT_EQ(2u, stroke.samples_seen());
404
405 // Add third sample at time = T + 10ms.
406 time = down_time + base::Milliseconds(10);
407 sample = CreatePalmFilterSample(touch_, time, model_config_, device_info);
408 stroke.ProcessSample(sample);
409 ASSERT_THAT(stroke.samples(),
410 ElementsAre(SampleTime(down_time),
411 SampleTime(down_time + base::Milliseconds(4)),
412 SampleTime(down_time + base::Milliseconds(10))));
413 ASSERT_EQ(3u, stroke.samples_seen());
414 }
415
TEST(PalmFilterStrokeTest,ResamplingTest)416 TEST(PalmFilterStrokeTest, ResamplingTest) {
417 NeuralStylusPalmDetectionFilterModelConfig model_config_;
418 model_config_.max_sample_count = 3;
419 model_config_.resample_period = base::Milliseconds(8);
420
421 PalmFilterStroke stroke(model_config_, /*tracking_id*/ 0);
422 PalmFilterDeviceInfo device_info;
423 device_info.minor_radius_supported = true;
424
425 // Add first sample at time = T
426 InProgressTouchEvdev touch_;
427 touch_.tracking_id = stroke.tracking_id();
428 touch_.x = 1;
429 touch_.y = 2;
430 touch_.major = 4;
431 touch_.minor = 3;
432 base::TimeTicks down_time = base::TimeTicks::UnixEpoch() + base::Seconds(30);
433 PalmFilterSample sample1 =
434 CreatePalmFilterSample(touch_, down_time, model_config_, device_info);
435 stroke.ProcessSample(sample1);
436 // First sample should not be modified
437 ASSERT_THAT(stroke.samples(), ElementsAre(sample1));
438
439 // Add second sample at time = T + 2ms. It's not yet time for the new frame,
440 // so no new sample should be generated.
441 base::TimeTicks time = down_time + base::Milliseconds(4);
442 touch_.x = 100;
443 touch_.y = 20;
444 touch_.major = 12;
445 touch_.minor = 11;
446 PalmFilterSample sample2 =
447 CreatePalmFilterSample(touch_, time, model_config_, device_info);
448 stroke.ProcessSample(sample2);
449 // The samples should remain unchanged
450 ASSERT_THAT(stroke.samples(), ElementsAre(sample1, sample2));
451
452 // Add third sample at time = T + 12ms. A resampled event at time = T + 8ms
453 // should be generated.
454 time = down_time + base::Milliseconds(12);
455 touch_.x = 200;
456 touch_.y = 24;
457 touch_.major = 14;
458 touch_.minor = 13;
459 PalmFilterSample sample3 =
460 CreatePalmFilterSample(touch_, time, model_config_, device_info);
461 stroke.ProcessSample(sample3);
462 ASSERT_THAT(stroke.samples(), ElementsAre(sample1, sample2, sample3));
463 }
464
465 /**
466 * There should always be at least (max_sample_count - 1) * resample_period
467 * worth of samples. However, that's not sufficient. In the cases where the gap
468 * between samples is very large (larger than the sample horizon), there needs
469 * to be another sample in order to calculate resampled values throughout the
470 * entire range.
471 */
TEST(PalmFilterStrokeTest,MultipleResampledValues)472 TEST(PalmFilterStrokeTest, MultipleResampledValues) {
473 NeuralStylusPalmDetectionFilterModelConfig model_config_;
474 model_config_.max_sample_count = 3;
475 model_config_.resample_period = base::Milliseconds(8);
476
477 PalmFilterStroke stroke(model_config_, /*tracking_id*/ 0);
478 PalmFilterDeviceInfo device_info;
479 device_info.minor_radius_supported = true;
480
481 // Add first sample at time = T
482 InProgressTouchEvdev touch_;
483 touch_.tracking_id = stroke.tracking_id();
484 touch_.x = 0;
485 touch_.y = 10;
486 touch_.major = 200;
487 touch_.minor = 100;
488 base::TimeTicks down_time = base::TimeTicks::UnixEpoch() + base::Seconds(30);
489 PalmFilterSample sample1 =
490 CreatePalmFilterSample(touch_, down_time, model_config_, device_info);
491 stroke.ProcessSample(sample1);
492 // First sample should go in as is
493 ASSERT_THAT(stroke.samples(), ElementsAre(sample1));
494
495 // Add second sample at time = T + 20ms.
496 base::TimeTicks time = down_time + base::Milliseconds(20);
497 touch_.x = 20;
498 touch_.y = 30;
499 touch_.major = 220;
500 touch_.minor = 120;
501 PalmFilterSample sample2 =
502 CreatePalmFilterSample(touch_, time, model_config_, device_info);
503 stroke.ProcessSample(sample2);
504
505 ASSERT_THAT(stroke.samples(), ElementsAre(sample1, sample2));
506
507 // Verify resampled sample : time = T + 8ms
508 PalmFilterSample resampled_sample =
509 stroke.GetSampleAt(down_time + base::Milliseconds(8));
510 EXPECT_EQ(8, resampled_sample.point.x());
511 EXPECT_EQ(18, resampled_sample.point.y());
512 EXPECT_EQ(220, resampled_sample.major_radius);
513 EXPECT_EQ(120, resampled_sample.minor_radius);
514
515 // Verify resampled sample : time = T + 16ms
516 resampled_sample = stroke.GetSampleAt(down_time + base::Milliseconds(16));
517 EXPECT_EQ(16, resampled_sample.point.x());
518 EXPECT_EQ(26, resampled_sample.point.y());
519 EXPECT_EQ(220, resampled_sample.major_radius);
520 EXPECT_EQ(120, resampled_sample.minor_radius);
521 }
522
523 } // namespace ui
524