diff options
author | Stanislaw Halik <sthalik@misaki.pl> | 2015-10-30 07:37:41 +0100 |
---|---|---|
committer | Stanislaw Halik <sthalik@misaki.pl> | 2015-10-30 08:39:32 +0100 |
commit | aa066bdd4622d4f6824fee864f6be6806813f04d (patch) | |
tree | 3df328b8b364cba2373a85827191b259bd78d546 /filter-ewma2 | |
parent | d6a54431d178632a2bf466c9904f74abd143afe6 (diff) |
move to subdirectory-based build system
Closes #224
Diffstat (limited to 'filter-ewma2')
-rw-r--r-- | filter-ewma2/CMakeLists.txt | 1 | ||||
-rw-r--r-- | filter-ewma2/ftnoir_ewma_filtercontrols.ui | 372 | ||||
-rw-r--r-- | filter-ewma2/ftnoir_filter_ewma2.cpp | 81 | ||||
-rw-r--r-- | filter-ewma2/ftnoir_filter_ewma2.h | 66 | ||||
-rw-r--r-- | filter-ewma2/ftnoir_filter_ewma2_dialog.cpp | 43 |
5 files changed, 563 insertions, 0 deletions
diff --git a/filter-ewma2/CMakeLists.txt b/filter-ewma2/CMakeLists.txt new file mode 100644 index 00000000..af786487 --- /dev/null +++ b/filter-ewma2/CMakeLists.txt @@ -0,0 +1 @@ +opentrack_boilerplate(opentrack-filter-ewma) diff --git a/filter-ewma2/ftnoir_ewma_filtercontrols.ui b/filter-ewma2/ftnoir_ewma_filtercontrols.ui new file mode 100644 index 00000000..9387f0d5 --- /dev/null +++ b/filter-ewma2/ftnoir_ewma_filtercontrols.ui @@ -0,0 +1,372 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>UICFilterControls</class> + <widget class="QWidget" name="UICFilterControls"> + <property name="windowModality"> + <enum>Qt::NonModal</enum> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>448</width> + <height>380</height> + </rect> + </property> + <property name="windowTitle"> + <string>EWMA2 filter settings</string> + </property> + <property name="windowIcon"> + <iconset resource="ewma-filter.qrc"> + <normaloff>:/images/filter-16.png</normaloff>:/images/filter-16.png</iconset> + </property> + <property name="layoutDirection"> + <enum>Qt::LeftToRight</enum> + </property> + <property name="autoFillBackground"> + <bool>false</bool> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QFrame" name="frame"> + <layout class="QGridLayout" name="gridLayout_2"> + <property name="topMargin"> + <number>2</number> + </property> + <property name="bottomMargin"> + <number>2</number> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="lblInvert1_6"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + <property name="text"> + <string>Min</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QSlider" name="minSmooth"> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>100</number> + </property> + <property name="pageStep"> + <number>5</number> + </property> + <property name="value"> + <number>2</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="tickPosition"> + <enum>QSlider::NoTicks</enum> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QSpinBox" name="spinMinSmooth"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="styleSheet"> + <string notr="true">background:none;</string> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + <property name="buttonSymbols"> + <enum>QAbstractSpinBox::PlusMinus</enum> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>120</number> + </property> + <property name="singleStep"> + <number>5</number> + </property> + <property name="value"> + <number>2</number> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="lblInvert1_7"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + <property name="text"> + <string>Max</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QSlider" name="maxSmooth"> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>100</number> + </property> + <property name="pageStep"> + <number>5</number> + </property> + <property name="value"> + <number>10</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="tickPosition"> + <enum>QSlider::NoTicks</enum> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QSpinBox" name="spinMaxSmooth"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="styleSheet"> + <string notr="true">background:none;</string> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + <property name="buttonSymbols"> + <enum>QAbstractSpinBox::PlusMinus</enum> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>120</number> + </property> + <property name="singleStep"> + <number>5</number> + </property> + <property name="value"> + <number>10</number> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="lblInvert1_8"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + <property name="text"> + <string>Curve</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QSlider" name="powCurve"> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>100</number> + </property> + <property name="pageStep"> + <number>5</number> + </property> + <property name="value"> + <number>10</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="tickPosition"> + <enum>QSlider::NoTicks</enum> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QSpinBox" name="spinPowCurve"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="styleSheet"> + <string notr="true">background:none;</string> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + <property name="buttonSymbols"> + <enum>QAbstractSpinBox::PlusMinus</enum> + </property> + <property name="maximum"> + <number>100</number> + </property> + <property name="singleStep"> + <number>5</number> + </property> + <property name="value"> + <number>10</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QLabel" name="label_4"> + <property name="styleSheet"> + <string notr="true">background-color: rgb(214, 214, 214); +border-color: rgb(0, 0, 0);</string> + </property> + <property name="frameShape"> + <enum>QFrame::Box</enum> + </property> + <property name="text"> + <string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';">Min:</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';">Defines the way the filter responds to fast movements;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';">Higher value: slower response;</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2';"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';">Max:</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';">Defines the way the filter responds to slow movements;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';">Higher value: slower response;</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2';"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';">Pow:</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';">Defines the filters 'readiness' to respond to speed changes;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';">Higher value = </span><span style=" font-family:'MS Shell Dlg 2'; font-weight:600;">faster</span><span style=" font-family:'MS Shell Dlg 2';"> response;</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2';"><br /></p></body></html></string> + </property> + <property name="margin"> + <number>5</number> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources> + <include location="ewma-filter.qrc"/> + </resources> + <connections> + <connection> + <sender>minSmooth</sender> + <signal>valueChanged(int)</signal> + <receiver>spinMinSmooth</receiver> + <slot>setValue(int)</slot> + <hints> + <hint type="sourcelabel"> + <x>303</x> + <y>33</y> + </hint> + <hint type="destinationlabel"> + <x>391</x> + <y>36</y> + </hint> + </hints> + </connection> + <connection> + <sender>maxSmooth</sender> + <signal>valueChanged(int)</signal> + <receiver>spinMaxSmooth</receiver> + <slot>setValue(int)</slot> + <hints> + <hint type="sourcelabel"> + <x>281</x> + <y>61</y> + </hint> + <hint type="destinationlabel"> + <x>390</x> + <y>74</y> + </hint> + </hints> + </connection> + <connection> + <sender>powCurve</sender> + <signal>valueChanged(int)</signal> + <receiver>spinPowCurve</receiver> + <slot>setValue(int)</slot> + <hints> + <hint type="sourcelabel"> + <x>236</x> + <y>101</y> + </hint> + <hint type="destinationlabel"> + <x>391</x> + <y>98</y> + </hint> + </hints> + </connection> + </connections> + <slots> + <slot>startEngineClicked()</slot> + <slot>stopEngineClicked()</slot> + <slot>cameraSettingsClicked()</slot> + </slots> +</ui> diff --git a/filter-ewma2/ftnoir_filter_ewma2.cpp b/filter-ewma2/ftnoir_filter_ewma2.cpp new file mode 100644 index 00000000..c09fb912 --- /dev/null +++ b/filter-ewma2/ftnoir_filter_ewma2.cpp @@ -0,0 +1,81 @@ +/* Copyright (c) 2014 Donovan Baarda <abo@minkirri.apana.org.au> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + */ +#include "ftnoir_filter_ewma2.h" +#include <cmath> +#include <QDebug> +#include <QWidget> +#include "opentrack/plugin-api.hpp" +#include <algorithm> +#include <QMutexLocker> + +// Exponentially Weighted Moving Average (EWMA) Filter with dynamic smoothing. +// +// This filter tries to adjust the amount of filtering to minimize lag when +// moving, and minimize noise when still. It uses the delta filtered over the +// last 1/60sec (16ms) compared to the delta's average noise variance over +// the last 60sec to try and detect movement vs noise. As the delta increases +// from 0 to 3 stdevs of the noise, the filtering scales down from maxSmooth +// to minSmooth at a rate controlled by the powCurve setting. + + +FTNoIR_Filter::FTNoIR_Filter() +{ + reset(); +} + +void FTNoIR_Filter::receiveSettings() +{ + s.b->reload(); +} + +void FTNoIR_Filter::reset() +{ + timer.invalidate(); +} + +void FTNoIR_Filter::filter(const double *input, double *output) +{ + // Start the timer and initialise filter state if it's not running. + if (!timer.isValid()) { + timer.start(); + for (int i=0;i<6;i++) { + last_output[i] = input[i]; + last_delta[i] = 0.0; + last_noise[i] = 0.0; + } + } + // Get the time in seconds since last run and restart the timer. + auto dt = timer.restart() / 1000.0f; + // Calculate delta_alpha and noise_alpha from dt. + double delta_alpha = dt/(dt + delta_RC); + double noise_alpha = dt/(dt + noise_RC); + // Calculate the new camera position. + for (int i=0;i<6;i++) { + // Calculate the current and smoothed delta. + double delta = input[i] - last_output[i]; + last_delta[i] = delta_alpha*delta + (1.0-delta_alpha)*last_delta[i]; + // Calculate the current and smoothed noise variance. + double noise = last_delta[i]*last_delta[i]; + last_noise[i] = noise_alpha*noise + (1.0-noise_alpha)*last_noise[i]; + // Normalise the noise between 0->1 for 0->9 variances (0->3 stddevs). + double norm_noise = std::min<double>(noise/(9.0*last_noise[i]), 1.0); + if (std::isnan(norm_noise)) + norm_noise = 0; + // Calculate the smoothing 0.0->1.0 from the normalized noise. + // TODO(abo): change kSmoothingScaleCurve to a float where 1.0 is sqrt(norm_noise). + double smoothing = 1.0 - pow(norm_noise, s.kSmoothingScaleCurve/20.0); + // Currently min/max smoothing are ints 0->100. We want 0.0->3.0 seconds. + // TODO(abo): Change kMinSmoothing, kMaxSmoothing to floats 0.0->3.0 seconds RC. + double RC = 3.0*(s.kMinSmoothing + smoothing*(s.kMaxSmoothing - s.kMinSmoothing))/100.0; + // Calculate the dynamic alpha. + double alpha = dt/(dt + RC); + // Calculate the new output position. + output[i] = last_output[i] = alpha*input[i] + (1.0-alpha)*last_output[i]; + } +} + +OPENTRACK_DECLARE_FILTER(FTNoIR_Filter, FilterControls, FTNoIR_FilterDll) diff --git a/filter-ewma2/ftnoir_filter_ewma2.h b/filter-ewma2/ftnoir_filter_ewma2.h new file mode 100644 index 00000000..bf4e83ad --- /dev/null +++ b/filter-ewma2/ftnoir_filter_ewma2.h @@ -0,0 +1,66 @@ +#pragma once + +#include "opentrack/plugin-api.hpp" +#include "ui_ftnoir_ewma_filtercontrols.h" +#include <QElapsedTimer> +#include <QWidget> +#include <QMutex> +#include "opentrack/options.hpp" +using namespace options; + +struct settings : opts { + // these are sadly sliders for now due to int/double mismatch -sh + value<int> kMinSmoothing, kMaxSmoothing, kSmoothingScaleCurve; + settings() : + opts("ewma-filter"), + kMinSmoothing(b, "min-smoothing", 15), + kMaxSmoothing(b, "max-smoothing", 50), + kSmoothingScaleCurve(b, "smoothing-scale-curve", 10) + {} +}; + + +class FTNoIR_Filter : public IFilter +{ +public: + FTNoIR_Filter(); + void reset(); + void filter(const double *input, double *output); + void receiveSettings(); +private: + // Deltas are smoothed over the last 1/60sec (16ms). + const double delta_RC = 0.016; + // Noise is smoothed over the last 60sec. + const double noise_RC = 60.0; + double last_delta[6]; + double last_noise[6]; + double last_output[6]; + QElapsedTimer timer; + settings s; +}; + +class FilterControls: public IFilterDialog +{ + Q_OBJECT +public: + FilterControls(); + void register_filter(IFilter* flt); + void unregister_filter(); + +private: + Ui::UICFilterControls ui; + void save(); + settings s; + FTNoIR_Filter* pFilter; + +private slots: + void doOK(); + void doCancel(); +}; + +class FTNoIR_FilterDll : public Metadata +{ +public: + QString name() { return QString("EWMA"); } + QIcon icon() { return QIcon(":/images/filter-16.png"); } +}; diff --git a/filter-ewma2/ftnoir_filter_ewma2_dialog.cpp b/filter-ewma2/ftnoir_filter_ewma2_dialog.cpp new file mode 100644 index 00000000..30fb6003 --- /dev/null +++ b/filter-ewma2/ftnoir_filter_ewma2_dialog.cpp @@ -0,0 +1,43 @@ +#include "ftnoir_filter_ewma2.h" +#include <cmath> +#include <QDebug> +#include "opentrack/plugin-api.hpp" +#include "ui_ftnoir_ewma_filtercontrols.h" + +FilterControls::FilterControls() : pFilter(NULL) +{ + ui.setupUi( this ); + + connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(doOK())); + connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(doCancel())); + + tie_setting(s.kMaxSmoothing, ui.maxSmooth); + tie_setting(s.kMinSmoothing, ui.minSmooth); + tie_setting(s.kSmoothingScaleCurve, ui.powCurve); +} + +void FilterControls::register_filter(IFilter* flt) +{ + pFilter = (FTNoIR_Filter*) flt; +} + +void FilterControls::unregister_filter() +{ + pFilter = NULL; +} + +void FilterControls::doOK() { + save(); + this->close(); +} + +void FilterControls::doCancel() { + s.b->reload(); + this->close(); +} + +void FilterControls::save() { + s.b->save(); + if (pFilter) + pFilter->receiveSettings(); +} |