Skip to content

Commit 40c141c

Browse files
Merge pull request #2 from InfinityGhost/noisereductionfilter
Add hawku/TabletDriver TabletFilterNoiseReduction
2 parents 3003200 + 22758f0 commit 40c141c

1 file changed

Lines changed: 190 additions & 0 deletions

File tree

HawkuFilters/NoiseReduction.cs

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Numerics;
4+
using OpenTabletDriver.Plugin.Attributes;
5+
using OpenTabletDriver.Plugin.Tablet;
6+
7+
namespace OpenTabletDriver.Plugin
8+
{
9+
[PluginName("TabletDriver Noise Reduction")]
10+
public class TabletDriverNoiseReduction : IFilter
11+
{
12+
private readonly LinkedList<Vector2> buffer = new LinkedList<Vector2>();
13+
private float distThreshold, distMax;
14+
private const int iterations = 10;
15+
private int samples = 10;
16+
private Vector2 lastPoint;
17+
18+
public Vector2 Filter(Vector2 point)
19+
{
20+
SetTarget(point);
21+
22+
if (this.buffer.Count <= 1)
23+
{
24+
return SetOutput(point);
25+
}
26+
27+
// Calculate geometric median from the buffer positions
28+
this.lastPoint = GetGeometricMedianVector(this.lastPoint);
29+
30+
// Distance between latest position and ring buffer
31+
var distance = Vector2.Distance(point, this.lastPoint);
32+
33+
// Distance larger than threshold -> modify the ring buffer
34+
if (distance > DistThreshold)
35+
{
36+
// Ratio between current distance and maximum distance
37+
double distanceRatio;
38+
39+
// Distance ratio should be between 0.0 and 1.0
40+
// 0.0 -> distance == distanceThreshold
41+
// 1.0 -> distance == distanceMaximum
42+
distanceRatio = (distance - DistThreshold) / (this.distMax - DistThreshold);
43+
44+
if (distanceRatio >= 1f)
45+
{
46+
// Distance larger than maximum -> fill buffer with the latest target position
47+
var bufCount = this.buffer.Count;
48+
this.buffer.Clear();
49+
for (int i = 0; i < bufCount; i++)
50+
this.buffer.AddLast(point);
51+
return SetOutput(point);
52+
}
53+
else
54+
{
55+
// Move buffer positions and current position towards the latest target using linear interpolation
56+
// Amount of movement is the distance ratio between threshold and maximum
57+
58+
var bufNode = buffer.First;
59+
60+
while (bufNode != null)
61+
{
62+
var bufPoint = bufNode.Value;
63+
bufPoint.X += (float)((point.X - bufPoint.X) * distanceRatio);
64+
bufPoint.Y += (float)((point.Y - bufPoint.Y) * distanceRatio);
65+
bufNode.Value = bufPoint;
66+
bufNode = bufNode.Next;
67+
}
68+
69+
this.lastPoint.X += (float)((point.X - this.lastPoint.X) * distanceRatio);
70+
this.lastPoint.Y += (float)((point.Y - this.lastPoint.Y) * distanceRatio);
71+
72+
return this.lastPoint;
73+
}
74+
}
75+
return SetOutput(point);
76+
}
77+
78+
private void SetTarget(Vector2 point)
79+
{
80+
buffer.AddLast(point);
81+
while (buffer.Count > Samples)
82+
buffer.RemoveFirst();
83+
}
84+
85+
private Vector2 SetOutput(Vector2 point)
86+
{
87+
this.lastPoint = point;
88+
return point;
89+
}
90+
91+
private Vector2 GetGeometricMedianVector(Vector2 point)
92+
{
93+
var candidate = new Vector2();
94+
var next = new Vector2();
95+
var minimumDistance = 0.001;
96+
97+
double denominator, weight, distance;
98+
99+
// Calculate the starting position
100+
if (!GetAverageVector(ref candidate))
101+
return this.lastPoint;
102+
103+
// Iterate
104+
for (int iteration = 0; iteration < iterations; iteration++)
105+
{
106+
denominator = 0;
107+
108+
// Loop through the buffer and calculate a denominator.
109+
foreach (var bufferPoint in buffer)
110+
{
111+
distance = Vector2.Distance(candidate, bufferPoint);
112+
113+
if (distance > minimumDistance)
114+
denominator += 1.0 / distance;
115+
else
116+
denominator += 1.0 / minimumDistance;
117+
}
118+
119+
// Reset the next vector
120+
next.X = 0;
121+
next.Y = 0;
122+
123+
// Loop through the buffer and calculate a weighted average
124+
foreach (var bufferPoint in buffer)
125+
{
126+
distance = Vector2.Distance(candidate, bufferPoint);
127+
128+
if (distance > minimumDistance)
129+
weight = 1.0 / distance;
130+
else
131+
weight = 1.0 / minimumDistance;
132+
133+
next.X += (float)(bufferPoint.X * weight / denominator);
134+
next.Y += (float)(bufferPoint.Y * weight / denominator);
135+
}
136+
137+
// Set the new candidate vector
138+
candidate.X = next.X;
139+
candidate.Y = next.Y;
140+
}
141+
142+
// Set output
143+
point.X = candidate.X;
144+
point.Y = candidate.Y;
145+
return point;
146+
}
147+
148+
private bool GetAverageVector(ref Vector2 point)
149+
{
150+
if (buffer.Count == 0)
151+
return false;
152+
153+
point.X = 0;
154+
point.Y = 0;
155+
156+
foreach (var bufferPoint in buffer)
157+
{
158+
point.X += bufferPoint.X;
159+
point.Y += bufferPoint.Y;
160+
}
161+
162+
point.X /= buffer.Count;
163+
point.Y /= buffer.Count;
164+
return true;
165+
}
166+
167+
[Property("Buffer")]
168+
public int Samples
169+
{
170+
set
171+
{
172+
this.samples = Math.Clamp(value, 0, 20);
173+
}
174+
get => this.samples;
175+
}
176+
177+
[Property("Distance Threshold"), Unit("px")]
178+
public float DistThreshold
179+
{
180+
set
181+
{
182+
this.distThreshold = Math.Clamp(value, 0, 10);
183+
distMax = value * 2;
184+
}
185+
get => this.distThreshold;
186+
}
187+
188+
public FilterStage FilterStage => FilterStage.PostTranspose;
189+
}
190+
}

0 commit comments

Comments
 (0)