Commit 97e77417 authored by Axel Antoine's avatar Axel Antoine Committed by Commit Bot

Add 1euro filter to third_party

1€ Filter is a simple speed-based low-pass filter for noisy input in
interactive systems.
See the public page: http://cristal.univ-lille.fr/~casiez/1euro/

It is added to filter the prediction in blink.

Bug: 981888
Change-Id: I46687dfd98ef6341edfefaf4782fa537106a283b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1691088
Commit-Queue: Axel Antoine <axantoine@google.com>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Reviewed-by: default avatarElla Ge <eirage@chromium.org>
Cr-Commit-Position: refs/heads/master@{#680625}
parent 6b04d4fd
# 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.
import("//testing/test.gni")
config("one_euro_filter_config") {
include_dirs = [ "src" ]
}
static_library("one_euro_filter") {
sources = [
"src/low_pass_filter.cc",
"src/low_pass_filter.h",
"src/one_euro_filter.cc",
"src/one_euro_filter.h",
]
configs -= [ "//build/config/compiler:chromium_code" ]
configs += [ "//build/config/compiler:no_chromium_code" ]
public_configs = [ ":one_euro_filter_config" ]
}
test("one_euro_filter_unittests") {
include_dirs = [ "tests" ]
sources = [
"tests/gtest_main.cc",
"tests/one_euro_filter_unittest.cc",
]
deps = [
":one_euro_filter",
"//testing/gtest",
]
public_configs = [ ":one_euro_filter_config" ]
}
Copyright 2019 Inria
Author: Nicolas Roussel (nicolas.roussel@inria.fr)
BSD License https://opensource.org/licenses/BSD-3-Clause
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holders, nor those of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
axantoine@google.com
eirage@chromium.org
nzolghadr@chromium.org
\ No newline at end of file
Name: One Euro Filter
Short Name: 1€ filter
URL: http://cristal.univ-lille.fr/~casiez/1euro/
Version: 0
License: BSD
License File: LICENSE
Security Critical: no
Description:
1€ Filter is a simple speed-based low-pass filter for noisy input in
interactive systems.
Local Modifications:
1. Separate source code from headers
2. Add namespaces
3. Add tests
4. Added Clone and Reset functions
#include "low_pass_filter.h"
namespace one_euro_filter {
void LowPassFilter::SetAlpha(double alpha) {
if (alpha <= 0.0 || alpha > 1.0)
a_ = 0.5;
else
a_ = alpha;
}
LowPassFilter::LowPassFilter() {}
LowPassFilter::LowPassFilter(double alpha, double initval) {
init_alpha_ = alpha;
initval_ = initval;
y_ = s_ = initval;
SetAlpha(alpha);
initialized_ = false;
}
double LowPassFilter::Filter(double value) {
double result;
if (initialized_)
result = a_ * value + (1.0 - a_) * s_;
else {
result = value;
initialized_ = true;
}
y_ = value;
s_ = result;
return result;
}
double LowPassFilter::FilterWithAlpha(double value, double alpha) {
SetAlpha(alpha);
return Filter(value);
}
bool LowPassFilter::HasLastRawValue(void) {
return initialized_;
}
double LowPassFilter::LastRawValue(void) {
return y_;
}
void LowPassFilter::Reset() {
y_ = s_ = initval_;
SetAlpha(init_alpha_);
initialized_ = false;
}
LowPassFilter* LowPassFilter::Clone(void) {
LowPassFilter* new_filter = new LowPassFilter();
new_filter->y_ = y_;
new_filter->a_ = a_;
new_filter->s_ = s_;
new_filter->initialized_ = initialized_;
return new_filter;
}
} // namespace one_euro_filter
\ No newline at end of file
#ifndef ONE_EURO_LOW_PASS_FILTER_H_
#define ONE_EURO_LOW_PASS_FILTER_H_
#include <iostream>
namespace one_euro_filter {
class LowPassFilter {
public:
LowPassFilter(double alpha, double initval = 0.0);
double Filter(double value);
double FilterWithAlpha(double value, double alpha);
bool HasLastRawValue(void);
double LastRawValue(void);
LowPassFilter* Clone(void);
// Reset the filter to its initial values
void Reset(void);
private:
// save init values
double init_alpha_, initval_;
double y_, a_, s_;
bool initialized_;
LowPassFilter();
void SetAlpha(double alpha);
};
} // namespace one_euro_filter
#endif // ONE_EURO_LOW_PASS_FILTER_H_
#include "one_euro_filter.h"
#include <cmath>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
namespace one_euro_filter {
OneEuroFilter::OneEuroFilter(double freq,
double mincutoff,
double beta,
double dcutoff) {
init_freq_ = freq;
init_mincutoff_ = mincutoff;
init_beta_ = beta;
init_dcutoff_ = dcutoff;
SetFrequency(freq);
SetMinCutoff(mincutoff);
SetBeta(beta);
SetDerivateCutoff(dcutoff);
x_ = std::make_unique<LowPassFilter>(Alpha(mincutoff_));
dx_ = std::make_unique<LowPassFilter>(Alpha(dcutoff_));
lasttime_ = UndefinedTime;
}
OneEuroFilter::OneEuroFilter() {}
OneEuroFilter::~OneEuroFilter() {}
double OneEuroFilter::Filter(double value, TimeStamp timestamp) {
// update the sampling frequency based on timestamps
if (lasttime_ != UndefinedTime && timestamp != UndefinedTime &&
timestamp - lasttime_ > 0)
freq_ = 1.0 / (timestamp - lasttime_);
lasttime_ = timestamp;
// estimate the current variation per second
double dvalue = x_->HasLastRawValue() ? (value - x_->LastRawValue()) * freq_
: 0.0; // FIXME: 0.0 or value?
double edvalue = dx_->FilterWithAlpha(dvalue, Alpha(dcutoff_));
// use it to update the cutoff frequency
double cutoff = mincutoff_ + beta_ * std::fabs(edvalue);
// filter the given value
return x_->FilterWithAlpha(value, Alpha(cutoff));
}
void OneEuroFilter::Reset() {
SetFrequency(init_freq_);
SetMinCutoff(init_mincutoff_);
SetBeta(init_beta_);
SetDerivateCutoff(init_dcutoff_);
x_->Reset();
dx_->Reset();
lasttime_ = UndefinedTime;
}
OneEuroFilter* OneEuroFilter::Clone() {
OneEuroFilter* new_filter = new OneEuroFilter();
new_filter->freq_ = freq_;
new_filter->beta_ = beta_;
new_filter->dcutoff_ = dcutoff_;
new_filter->mincutoff_ = mincutoff_;
new_filter->lasttime_ = lasttime_;
new_filter->x_.reset(x_->Clone());
new_filter->dx_.reset(dx_->Clone());
return new_filter;
}
double OneEuroFilter::Alpha(double cutoff) {
double te = 1.0 / freq_;
double tau = 1.0 / (2 * M_PI * cutoff);
return 1.0 / (1.0 + tau / te);
}
void OneEuroFilter::SetFrequency(double f) {
freq_ = f > 0.0 ? f : 120; // 120Hz default
}
void OneEuroFilter::SetMinCutoff(double mc) {
mincutoff_ = mc > 0.0 ? mc : 1.0;
}
void OneEuroFilter::SetBeta(double b) {
beta_ = b;
}
void OneEuroFilter::SetDerivateCutoff(double dc) {
dcutoff_ = dc > 0.0 ? dc : 1.0;
}
} // namespace one_euro_filter
\ No newline at end of file
#ifndef ONE_EURO_ONE_EURO_FILTER_H_
#define ONE_EURO_ONE_EURO_FILTER_H_
#include "low_pass_filter.h"
namespace one_euro_filter {
namespace test {
class OneEuroFilterTest;
}
namespace {
typedef double TimeStamp; // in seconds
static const TimeStamp UndefinedTime = -1.0;
} // namespace
class OneEuroFilter {
public:
// Creates a 1euro filter
OneEuroFilter(double freq,
double mincutoff = 1.0,
double beta = 0.0,
double dcutoff = 1.0);
~OneEuroFilter();
// Filter a value
// param value: value to be filtered
// returns: the filtered value;
double Filter(double value, TimeStamp timestamp = UndefinedTime);
OneEuroFilter* Clone();
// Reset the filter to its initial values
void Reset();
private:
friend class test::OneEuroFilterTest;
// Save initial values
double init_freq_;
double init_mincutoff_;
double init_beta_;
double init_dcutoff_;
double freq_;
double mincutoff_;
double beta_;
double dcutoff_;
std::unique_ptr<LowPassFilter> x_;
std::unique_ptr<LowPassFilter> dx_;
TimeStamp lasttime_;
OneEuroFilter();
double Alpha(double cutoff);
void SetFrequency(double f);
void SetMinCutoff(double mc);
void SetBeta(double b);
void SetDerivateCutoff(double dc);
};
} // namespace one_euro_filter
#endif // ONE_EURO_ONE_EURO_FILTER_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 "third_party/googletest/src/googletest/include/gtest/gtest.h"
int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
\ No newline at end of file
// 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 THIRD_PARTY_1EURO_ONE_EURO_FILTER_UNITTEST_H_
#define THIRD_PARTY_1EURO_ONE_EURO_FILTER_UNITTEST_H_
#include "third_party/one_euro_filter/src/one_euro_filter.h"
#include <stdlib.h>
#include <time.h>
#include "third_party/googletest/src/googletest/include/gtest/gtest.h"
namespace one_euro_filter {
namespace test {
constexpr double kEpsilon = 0.0001;
class OneEuroFilterTest : public testing::Test {
public:
OneEuroFilterTest() {}
void SetUp() { filter_ = std::make_unique<OneEuroFilter>(120); }
void SetUpFilter(double freq,
double mincutoff = 1.0,
double beta = 0.0,
double dcutoff = 1.0) {
filter_ = std::make_unique<OneEuroFilter>(freq, mincutoff, beta, dcutoff);
}
bool CheckBeta(double beta) { return filter_->beta_ == beta; }
bool CheckFrequency(double frequency) { return filter_->freq_ == frequency; }
bool CheckMinCutoff(double mincutoff) {
return filter_->mincutoff_ == mincutoff;
}
bool CheckDerivateCutoff(double dcutoff) {
return filter_->dcutoff_ == dcutoff;
}
protected:
std::unique_ptr<OneEuroFilter> filter_;
};
// Check default values when wrong parameters are sent to the filter
TEST_F(OneEuroFilterTest, ParametersTest) {
SetUpFilter(100, 100, 100, 100);
EXPECT_TRUE(CheckBeta(100));
EXPECT_TRUE(CheckFrequency(100));
EXPECT_TRUE(CheckMinCutoff(100));
EXPECT_TRUE(CheckDerivateCutoff(100));
SetUpFilter(-100, -100, -100, -100);
EXPECT_TRUE(CheckBeta(-100));
EXPECT_TRUE(CheckFrequency(120));
EXPECT_TRUE(CheckMinCutoff(1.0));
EXPECT_TRUE(CheckDerivateCutoff(1.0));
}
// Check the filter is working when sending random numbers inside the
// interval [0,1] at random interval. Each filtered number should be
// inside this interval
TEST_F(OneEuroFilterTest, RandomValuesRandomTimestampTest) {
srand(time(0));
double x, xf;
double sum_x = 0, sum_xf = 0;
TimeStamp ts = 1;
double delta = 0.008; // Every 8 ms
for (int i = 0; i < 100; i++) {
x = ((double)rand()) / RAND_MAX;
xf = filter_->Filter(x, ts);
ts += delta * x; // Randon delta time between events
EXPECT_GT(x, 0);
EXPECT_LT(x, 1);
EXPECT_GT(xf, 0);
EXPECT_LT(xf, 1);
sum_x += x;
sum_xf += xf;
}
EXPECT_NE(sum_x, sum_xf);
}
// Check the filter is working when sending random numbers inside the
// interval [0,1] at constant interval. Each filtered number should be
// inside this interval
TEST_F(OneEuroFilterTest, RandomValuesConstantTimestampTest) {
srand(time(0));
double x, xf;
double sum_x = 0, sum_xf = 0;
TimeStamp ts = 1;
double delta = 0.008; // Every 8 ms
for (int i = 0; i < 100; i++) {
x = ((double)rand()) / RAND_MAX;
xf = filter_->Filter(x, ts);
ts += delta;
EXPECT_GT(x, 0);
EXPECT_LT(x, 1);
EXPECT_GT(xf, 0);
EXPECT_LT(xf, 1);
sum_x += x;
sum_xf += xf;
}
EXPECT_NE(sum_x, sum_xf);
}
// Check the filter is working when sending the same number at random interval.
// Each filtered value should be the same
TEST_F(OneEuroFilterTest, SameValuesRandomTimestampTest) {
srand(time(0));
double x = 0.5, xf;
TimeStamp ts = 1;
double delta = 0.008; // Every 8 ms
for (int i = 0; i < 100; i++) {
xf = filter_->Filter(x, ts);
ts += delta * ((double)rand()) /
RAND_MAX; // Randon delta time between events
EXPECT_EQ(x, xf);
}
}
// Check the filter is working when sending the same number at constant
// interval. Each filtered value should be the same
TEST_F(OneEuroFilterTest, SameValuesConstantTimestampTest) {
double x = 0.5, xf;
TimeStamp ts = 0;
double delta = 0.008; // Every 8 ms
for (int i = 0; i < 100; i++) {
xf = filter_->Filter(x, ts);
ts += delta;
EXPECT_EQ(x, xf);
}
}
// Check if the filter is well cloned. We create a first filter, send random
// values, and then we clone it. If we send the same new random values to both
// filters, we should have the same filtered results
TEST_F(OneEuroFilterTest, CloneTest) {
srand(time(0));
double x;
TimeStamp ts = 1;
double delta = 0.008; // Every 8 ms
for (int i = 0; i < 100; i++) {
x = rand();
filter_->Filter(x, ts);
ts += delta;
}
std::unique_ptr<OneEuroFilter> fork_filter;
fork_filter.reset(filter_->Clone());
double xf1, xf2;
for (int i = 0; i < 100; i++) {
x = rand();
xf1 = filter_->Filter(x, ts);
xf2 = fork_filter->Filter(x, ts);
EXPECT_NEAR(xf1, xf2, kEpsilon);
ts += delta;
}
}
// Check if the filter is well reset. We send random values, save the values and
// results, then we reset the filter. We send again the same values and see if
// we have the same results, which would be statistically impossible with 100
// random wihtout a proper resetting.
TEST_F(OneEuroFilterTest, TestResetting) {
std::vector<double> random_values;
std::vector<double> timestamps;
std::vector<double> results;
srand(time(0));
double x, r;
TimeStamp ts = 1;
double delta = 0.008; // Every 8 ms
for (int i = 0; i < 100; i++) {
x = ((double)rand()) / RAND_MAX; // betwwen 0 and 1
random_values.push_back(x);
timestamps.push_back(ts);
r = filter_->Filter(x, ts);
results.push_back(r);
ts += delta * x; // Randon delta time between events
}
filter_->Reset();
EXPECT_EQ((int)random_values.size(), 100);
EXPECT_EQ((int)timestamps.size(), 100);
EXPECT_EQ((int)results.size(), 100);
for (int i = 0; i < 100; i++) {
EXPECT_EQ(results[i], filter_->Filter(random_values[i], timestamps[i]));
}
}
} // namespace test
} // namespace one_euro_filter
#endif // THIRD_PARTY_1EURO_ONE_EURO_FILTER_UNITTEST_H_
\ No newline at end of file
...@@ -66,6 +66,7 @@ jumbo_source_set("blink") { ...@@ -66,6 +66,7 @@ jumbo_source_set("blink") {
deps = [ deps = [
"//cc:cc", "//cc:cc",
"//third_party/blink/public:blink_headers", "//third_party/blink/public:blink_headers",
"//third_party/one_euro_filter",
"//ui/events", "//ui/events",
"//ui/events:dom_keycode_converter", "//ui/events:dom_keycode_converter",
"//ui/events:events_base", "//ui/events:events_base",
......
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