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