Add Lagrange, and various prep changes

This commit is contained in:
X9VoiD 2020-12-28 14:42:08 +08:00
parent c1820d15f2
commit 250c0ff6d3
10 changed files with 186 additions and 100 deletions

View file

@ -0,0 +1,40 @@
using OpenTabletDriver.Plugin.Attributes;
using OpenTabletDriver.Plugin.Tablet.Interpolator;
using OpenTabletDriver.Plugin.Timers;
namespace VoiDPlugins.Filter
{
[PluginName("Lagrange")]
public class Lagrange : Interpolator
{
private readonly LagrangeCore Core = new();
private SyntheticTabletReport Report;
public Lagrange(ITimer timer) : base(timer)
{
}
public override SyntheticTabletReport Interpolate()
{
if (Core.IsFilled)
Report.Position = Core.Predict();
return Report;
}
public override void UpdateState(SyntheticTabletReport report)
{
Report = report;
Core.Add(report.Position);
}
[Property("Samples")]
public int Samples { set => Core.Samples = value; }
[Property("Offset"), Unit("ms")]
public float Offset { get; set; }
[BooleanProperty("Mode 2", "Enable if tablet report is jittery. (most of 266+hz tablets)")]
public bool Jitter { set => Core.Jitter = value; }
}
}

View file

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\..\.modules\OpenTabletDriver\OpenTabletDriver.Plugin\OpenTabletDriver.Plugin.csproj" />
<ProjectReference Include="..\..\VoiDPlugins.Library\VoiD\VoiD.csproj" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>net5</TargetFramework>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,70 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Numerics;
using VoiDPlugins.Library;
namespace VoiDPlugins.Filter
{
public class LagrangeCore
{
private RingBuffer<TimeSeriesPoint> Points;
private readonly Stopwatch Watch = new();
private float? prevElapsed;
public int Samples { set => Points = new(value); }
public bool Jitter;
public bool IsFilled { get => Points.IsFilled; }
public float TimeNow { get => (float)Watch.Elapsed.TotalMilliseconds; }
public float ReportInterval = 4;
public LagrangeCore()
{
Watch.Start();
}
~LagrangeCore()
{
Watch.Stop();
}
public void Add(Vector2 point)
{
var now = TimeNow;
Points.Insert(new TimeSeriesPoint(point, now));
if (prevElapsed.HasValue)
{
var interval = now - prevElapsed;
if (interval < ReportInterval * 1.5)
ReportInterval += (float)((interval - ReportInterval) * 0.1);
}
prevElapsed = now;
}
public Vector2 Predict(float offset = 0)
{
Vector2 lagrange = new(0, 0);
var i = 0;
foreach (var z in Points)
{
lagrange += z.Point * Decompose(TimeNow + offset, (float)z.Elapsed, i);
i++;
}
return lagrange;
}
private float Decompose(float interpTime, float time, int decompIndex)
{
float decomposed = 1;
int i = 0;
foreach (var point in Points)
{
if (i != decompIndex)
{
decomposed *= (float)((interpTime - point.Elapsed) / (time - point.Elapsed));
}
i++;
}
return decomposed;
}
}
}

View file

@ -0,0 +1,16 @@
using System.Numerics;
namespace VoiDPlugins.Filter
{
public class TimeSeriesPoint
{
public TimeSeriesPoint(Vector2 point, double elapsed)
{
Point = point;
Elapsed = elapsed;
}
public readonly Vector2 Point;
public double Elapsed;
}
}

View file

@ -49,7 +49,7 @@ namespace VoiDPlugins.Filter.MeL.Core
{
var predicted = new Vector2();
double predictAhead;
predictAhead = TimeNow - this.timeSeriesPoints.PeekFirst().Elapsed + offset;
predictAhead = TimeNow - this.timeSeriesPoints[0].Elapsed + offset;
predicted.X = (float)this.xCoeff.Evaluate(predictAhead);
predicted.Y = (float)this.yCoeff.Evaluate(predictAhead);

View file

@ -20,7 +20,7 @@ namespace VoiDPlugins.Filter.MeL.Core
private double[] ConstructTimeDesignMatrix()
{
var baseTime = this.timeSeriesPoints.PeekFirst().Elapsed;
var baseTime = this.timeSeriesPoints[0].Elapsed;
var data = new double[Samples];
var index = 0;
foreach (var timePoint in this.timeSeriesPoints)

View file

@ -1,8 +0,0 @@
namespace VoiDPlugins.Filter
{
enum ReconstructionAlg
{
ReverseMA,
ReverseEMA
}
}

View file

@ -1,100 +1,27 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using OpenTabletDriver.Plugin.Attributes;
using OpenTabletDriver.Plugin.Tablet;
using VoiDPlugins.Library;
namespace VoiDPlugins.Filter
{
[PluginName("Reconstructor")]
public class Reconstructor : IFilter
{
private RingBuffer<Vector2> truePoints;
private Vector2? lastAvg;
private ReconstructionAlg mode;
public Vector2 Filter(Vector2 point)
{
switch (mode)
{
case ReconstructionAlg.ReverseMA:
{
var truePoint = truePoints.IsFilled ? ReverseMAFunc(truePoints, point, MAWindow) : point;
truePoints.Insert(truePoint);
return truePoint;
}
case ReconstructionAlg.ReverseEMA:
{
var truePoint = lastAvg.HasValue ? ReverseEMAFunc(point, lastAvg.Value, (float)EMAWeight) : point;
lastAvg = point;
return truePoint;
}
default:
return point;
}
}
private static Vector2 ReverseMAFunc(IEnumerable<Vector2> trueHistory, Vector2 input, int window)
{
Vector2 sum = new Vector2();
foreach (var item in trueHistory)
sum += item;
return (input * window) - sum;
}
private static Vector2 ReverseEMAFunc(Vector2 currentEMA, Vector2 lastEMA, float weight)
{
return ((currentEMA - lastEMA) / weight) + lastEMA;
}
[BooleanProperty("Reverse MA", "Set to True if the tablet is using MA algorithm for smoothing/noise reduction")]
[ToolTip(
"100% reconstruction accuracy when the tablet smoothing algorithm is MA and the window is exactly known\n\n" +
"Warning: Reverse MA completely fails when tablet is not using MA + specified window"
)]
public bool ReverseMA
{
set
{
if (value)
mode = ReconstructionAlg.ReverseMA;
}
}
[BooleanProperty("Reverse EMA", "Set to True if the tablet is using EMA algorithm for smoothing/noise reduction")]
[ToolTip
(
"100% reconstruction accuracy when the tablet smoothing algorithm is EMA and the weight is exactly known\n\n" +
"Great reconstruction stability when true tablet smoothing algorithm is unknown, but determining exact weight is hard\n\n" +
"As a sidenote, hawku uses EMA for his smoothing filter"
)]
public bool ReverseEMA
{
set
{
if (value)
mode = ReconstructionAlg.ReverseEMA;
}
}
[Property("MA Window"), ToolTip
(
"Default: 3\n\n" +
"Defines in integers how many samples is considered. [Range: 2 - n]"
)]
public int MAWindow
{
set
{
window = value;
if (window > 1)
truePoints = new RingBuffer<Vector2>(value - 1);
}
get => window;
}
[Property("EMA Weight"), ToolTip
(
"Default: 0.35\n\n" +
@ -108,8 +35,6 @@ namespace VoiDPlugins.Filter
}
get => weight;
}
private int window;
private double weight;
public FilterStage FilterStage => FilterStage.PreInterpolate;

View file

@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace VoiDPlugins.Library
{
@ -28,24 +29,17 @@ namespace VoiDPlugins.Library
}
}
public T PeekFirst()
{
var currentPosition = this.head;
return this.dataStream[currentPosition];
}
public T PeekLast()
{
var last = Math.Abs(this.head - 1);
return this.dataStream[last];
}
public void Clear()
{
this.dataStream = new T[this.Size];
this.head = 0;
}
private int Wrap(int index)
{
return (index + this.Size) % this.Size;
}
IEnumerator<T> RingGetEnumerator()
{
if (this.head == 0)
@ -68,6 +62,18 @@ namespace VoiDPlugins.Library
}
}
public T this[int index]
{
get => this.dataStream[Wrap(index + this.head)];
set => this.dataStream[Wrap(index + this.head)] = value;
}
public T this[Index index]
{
get => this.dataStream[Wrap(index.IsFromEnd ? Wrap(this.head - index.Value) : Wrap(index.Value + this.head))];
set => this.dataStream[Wrap(index.IsFromEnd ? Wrap(this.head - index.Value) : Wrap(index.Value + this.head))] = value;
}
public IEnumerator<T> GetEnumerator()
{
if (!this.IsFilled)
@ -80,5 +86,15 @@ namespace VoiDPlugins.Library
{
return GetEnumerator();
}
public override string ToString()
{
string a = "";
foreach (var item in this.SkipLast(1))
a += $"{item}, ";
a += this[^1];
return a;
}
}
}

View file

@ -31,6 +31,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "VoiDPlugins.Library", "VoiD
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VMulti", "VoiDPlugins.Library\VMulti\VMulti.csproj", "{E6A1BA35-D3D4-4F28-9B59-FA946B3039C7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lagrange", "Filter\Lagrange\Lagrange.csproj", "{F1A8C6C3-0189-4D0B-9811-B72B1CBCBCCB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -152,6 +154,18 @@ Global
{E6A1BA35-D3D4-4F28-9B59-FA946B3039C7}.Release|x64.Build.0 = Release|Any CPU
{E6A1BA35-D3D4-4F28-9B59-FA946B3039C7}.Release|x86.ActiveCfg = Release|Any CPU
{E6A1BA35-D3D4-4F28-9B59-FA946B3039C7}.Release|x86.Build.0 = Release|Any CPU
{F1A8C6C3-0189-4D0B-9811-B72B1CBCBCCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F1A8C6C3-0189-4D0B-9811-B72B1CBCBCCB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F1A8C6C3-0189-4D0B-9811-B72B1CBCBCCB}.Debug|x64.ActiveCfg = Debug|Any CPU
{F1A8C6C3-0189-4D0B-9811-B72B1CBCBCCB}.Debug|x64.Build.0 = Debug|Any CPU
{F1A8C6C3-0189-4D0B-9811-B72B1CBCBCCB}.Debug|x86.ActiveCfg = Debug|Any CPU
{F1A8C6C3-0189-4D0B-9811-B72B1CBCBCCB}.Debug|x86.Build.0 = Debug|Any CPU
{F1A8C6C3-0189-4D0B-9811-B72B1CBCBCCB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F1A8C6C3-0189-4D0B-9811-B72B1CBCBCCB}.Release|Any CPU.Build.0 = Release|Any CPU
{F1A8C6C3-0189-4D0B-9811-B72B1CBCBCCB}.Release|x64.ActiveCfg = Release|Any CPU
{F1A8C6C3-0189-4D0B-9811-B72B1CBCBCCB}.Release|x64.Build.0 = Release|Any CPU
{F1A8C6C3-0189-4D0B-9811-B72B1CBCBCCB}.Release|x86.ActiveCfg = Release|Any CPU
{F1A8C6C3-0189-4D0B-9811-B72B1CBCBCCB}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{33F0E415-AB65-43F2-8AE2-3462B56B9E91} = {CFC2BC9E-CFD6-4B01-946D-43C26E6C2108}
@ -163,5 +177,6 @@ Global
{D8DBD60E-BE5C-43DE-AEB6-D6ADBAC3BE8C} = {44DF0884-9B34-45CA-92B7-2D57DCA3A1A8}
{DE2D6E1E-F04E-4E0C-BE95-7B8E8AFBCD4C} = {C3E1E369-CD2C-4030-88BE-3D4E7493F8C7}
{E6A1BA35-D3D4-4F28-9B59-FA946B3039C7} = {9B841994-6F85-4834-8C8C-04D196FE388A}
{F1A8C6C3-0189-4D0B-9811-B72B1CBCBCCB} = {6EFE4444-C6E7-4743-8EE5-7A2BE7493457}
EndGlobalSection
EndGlobal