99 */
1010import loglevel from 'loglevel'
1111import { createSeries } from './Series.js'
12- import { createWeighedSeries } from './WeighedSeries .js'
12+ import { createWeighedMedianSeries } from './WeighedMedianSeries .js'
1313
1414const log = loglevel . getLogger ( 'RowingEngine' )
1515
1616export function createCyclicErrorFilter ( numberOfMagnets , flankLength , minimumDragFactorSamples , agressiveness , deltaTime ) {
17+ const upperBound = 1.01
18+ const lowerBound = 0.99
1719 const _numberOfMagnets = numberOfMagnets
1820 const _flankLength = flankLength
19- const _numberOfFilterSamples = ( minimumDragFactorSamples / numberOfMagnets ) * 3
21+ const _numberOfFilterSamples = ( minimumDragFactorSamples / numberOfMagnets ) * 2
2022 const raw = createSeries ( _flankLength )
23+ const magnet = createSeries ( _flankLength )
2124 const clean = createSeries ( _flankLength )
2225 const _agressiveness = agressiveness
2326 const linearRegressor = deltaTime
@@ -27,14 +30,17 @@ export function createCyclicErrorFilter (numberOfMagnets, flankLength, minimumDr
2730 let recordedAbsolutePosition = [ ]
2831 let recordedRawValue = [ ]
2932 let startPosition
30- let _cursor
33+ let cursor
34+ let totalNumberOfDatapointsProcessed
3135 let filterSum = _numberOfMagnets
3236 let weightCorrection = 1
37+ let offset = 0
3338 reset ( )
3439
3540 function applyFilter ( rawValue , position ) {
3641 if ( startPosition === undefined ) { startPosition = position + _flankLength }
3742 raw . push ( rawValue )
43+ magnet . push ( position % _numberOfMagnets )
3844 clean . push ( rawValue * filterConfig [ position % _numberOfMagnets ] * weightCorrection )
3945 }
4046
@@ -45,68 +51,101 @@ export function createCyclicErrorFilter (numberOfMagnets, flankLength, minimumDr
4551 }
4652
4753 function processNextRawDatapoint ( ) {
48- if ( _cursor === undefined ) { _cursor = Math . ceil ( recordedRelativePosition . length * 0.25 ) }
49- if ( _cursor < Math . floor ( recordedRelativePosition . length * 0.75 ) ) {
50- const perfectCurrentDt = linearRegressor . projectX ( recordedAbsolutePosition [ _cursor ] )
51- const weight = linearRegressor . goodnessOfFit ( )
52- updateFilter ( _cursor , recordedRelativePosition [ _cursor ] , recordedRawValue [ _cursor ] , perfectCurrentDt , weight )
53- _cursor ++
54+ if ( cursor === undefined ) { cursor = Math . ceil ( recordedRelativePosition . length * 0.25 ) }
55+ if ( cursor < Math . floor ( recordedRelativePosition . length * 0.75 ) ) {
56+ const perfectCurrentDt = linearRegressor . projectX ( recordedAbsolutePosition [ cursor ] )
57+ const correctionFactor = ( perfectCurrentDt / recordedRawValue [ cursor ] )
58+ const weightCorrectedCorrectionFactor = ( ( correctionFactor - 1 ) * _agressiveness ) + 1
59+ const nextPerfectCurrentDt = linearRegressor . projectX ( recordedAbsolutePosition [ cursor + 1 ] )
60+ const nextCorrectionFactor = ( perfectCurrentDt / recordedRawValue [ cursor + 1 ] )
61+ const nextWeightCorrectedCorrectionFactor = ( ( correctionFactor - 1 ) * _agressiveness ) + 1
62+ const GoF = linearRegressor . goodnessOfFit ( ) * linearRegressor . localGoodnessOfFit ( cursor )
63+ updateFilter ( recordedRelativePosition [ cursor ] , weightCorrectedCorrectionFactor , nextWeightCorrectedCorrectionFactor , GoF )
64+ cursor ++
5465 }
5566 }
5667
57- function updateFilter ( cursor , position , rawValue , cleanValue , goodnessOfFit ) {
68+ function updateFilter ( position , weightCorrectedCorrectionFactor , nextWeightCorrectedCorrectionFactor , goodnessOfFit ) {
5869 if ( position > startPosition ) {
59- const correctionFactor = ( cleanValue / rawValue )
60- const weightCorrectedCorrectionFactor = ( ( correctionFactor - 1 ) * _agressiveness ) + 1
70+ let workPosition = ( position + offset ) % _numberOfMagnets
71+ const leftDistance = Math . abs ( filterConfig [ ( workPosition - 1 ) % _numberOfMagnets ] - weightCorrectedCorrectionFactor )
72+ const middleDistance = Math . abs ( filterConfig [ workPosition ] - weightCorrectedCorrectionFactor )
73+ const rightDistance = Math . abs ( filterConfig [ ( workPosition + 1 ) % _numberOfMagnets ] - weightCorrectedCorrectionFactor )
74+ const nextLeftDistance = Math . abs ( filterConfig [ ( workPosition ) % _numberOfMagnets ] - nextWeightCorrectedCorrectionFactor )
75+ const nextMiddleDistance = Math . abs ( filterConfig [ workPosition + 1 ] - nextWeightCorrectedCorrectionFactor )
76+ const nextRightDistance = Math . abs ( filterConfig [ ( workPosition + 2 ) % _numberOfMagnets ] - nextWeightCorrectedCorrectionFactor )
77+ const aboveUpperBound = ( weightCorrectedCorrectionFactor > ( filterConfig [ workPosition ] * upperBound ) )
78+ const belowLowerBound = ( weightCorrectedCorrectionFactor < ( filterConfig [ workPosition ] * lowerBound ) )
79+ const outsideBounds = ( aboveUpperBound || belowLowerBound )
6180 switch ( true ) {
6281 // Prevent a single measurement to add radical change (measurement error), offsetting the entire filter
63- case ( weightCorrectedCorrectionFactor >= ( filterConfig [ position % _numberOfMagnets ] * 0.9 ) && ( filterConfig [ position % _numberOfMagnets ] * 1.1 ) >= weightCorrectedCorrectionFactor ) :
64- filterArray [ position % _numberOfMagnets ] . push ( weightCorrectedCorrectionFactor , goodnessOfFit )
82+ case ( totalNumberOfDatapointsProcessed < ( _numberOfFilterSamples * _numberOfMagnets ) ) :
83+ // We are still at filter startup
84+ filterArray [ position % _numberOfMagnets ] . push ( position , weightCorrectedCorrectionFactor , goodnessOfFit )
85+ break
86+ case ( ! outsideBounds ) :
87+ filterArray [ workPosition ] . push ( position , weightCorrectedCorrectionFactor , goodnessOfFit )
6588 break
66- case ( weightCorrectedCorrectionFactor < ( filterConfig [ position % _numberOfMagnets ] * 0.9 ) ) :
67- log . debug ( `*** WARNING: cyclic error filter detected a radical decrease of ${ weightCorrectedCorrectionFactor } , where about ${ filterConfig [ position % _numberOfMagnets ] } is expected, clipping` )
68- // As this probably is a huge outlier in the recovery calculation, we need to explicitly consider localGoodnessOfFit
69- filterArray [ position % _numberOfMagnets ] . push ( filterConfig [ position % _numberOfMagnets ] * 0.9 , goodnessOfFit * linearRegressor . localGoodnessOfFit ( cursor ) )
89+ case ( outsideBounds && leftDistance < middleDistance && leftDistance < rightDistance && nextLeftDistance < nextMiddleDistance && nextLeftDistance < nextRightDistance ) :
90+ // We're outside the usual boundaries, and it seems that the previous point is a better match than the current one, potentially due to a missing datapoint
91+ log . debug ( `*** WARNING: cyclic error filter detected a positive shift at magnet ${ workPosition } , most likely due to an unhandled switch bounce` )
92+ offset --
93+ workPosition = ( position + offset ) % _numberOfMagnets
94+ filterArray [ workPosition ] . push ( position , weightCorrectedCorrectionFactor , goodnessOfFit )
7095 break
71- case ( weightCorrectedCorrectionFactor > filterConfig [ position % _numberOfMagnets ] * 1.1 ) :
72- log . debug ( `*** WARNING: cyclic error filter detected a radical increase of ${ weightCorrectedCorrectionFactor } , where about ${ filterConfig [ position % _numberOfMagnets ] } is expected, clipping` )
73- // As this probably is a huge outlier in the recovery calculation, we need to explicitly consider localGoodnessOfFit
74- filterArray [ position % _numberOfMagnets ] . push ( filterConfig [ position % _numberOfMagnets ] * 1.1 , goodnessOfFit * linearRegressor . localGoodnessOfFit ( cursor ) )
96+ case ( outsideBounds && rightDistance < middleDistance && rightDistance < leftDistance && nextRightDistance < nextMiddleDistance && nextRightDistance < nextLeftDistance ) :
97+ // We're outside the usual boundaries, and it seems that the next datapoint is a better match than the current one, potentially due to a switch bounce
98+ log . debug ( `*** WARNING: cyclic error filter detected a negative shift at magnet ${ workPosition } , most likely due to a missing datapoint` )
99+ offset ++
100+ workPosition = ( position + offset ) % _numberOfMagnets
101+ filterArray [ workPosition ] . push ( position , weightCorrectedCorrectionFactor , goodnessOfFit )
75102 break
76- // no default
103+ case ( belowLowerBound ) :
104+ log . debug ( `*** WARNING: cyclic error filter detected a radical decrease of ${ weightCorrectedCorrectionFactor } at magnet ${ workPosition } , where about ${ filterConfig [ workPosition ] } is expected, clipping` )
105+ filterArray [ workPosition ] . push ( position , filterConfig [ workPosition ] * lowerBound , goodnessOfFit )
106+ break
107+ case ( aboveUpperBound ) :
108+ log . debug ( `*** WARNING: cyclic error filter detected a radical increase of ${ weightCorrectedCorrectionFactor } at magnet ${ workPosition } , where about ${ filterConfig [ workPosition ] } is expected, clipping` )
109+ filterArray [ workPosition ] . push ( position , filterConfig [ workPosition ] * upperBound , goodnessOfFit )
110+ break
111+ default :
112+ filterArray [ workPosition ] . push ( position , weightCorrectedCorrectionFactor , goodnessOfFit )
77113 }
78114 filterSum -= filterConfig [ position % _numberOfMagnets ]
79- filterConfig [ position % _numberOfMagnets ] = filterArray [ position % _numberOfMagnets ] . weighedAverage ( )
115+ filterConfig [ position % _numberOfMagnets ] = filterArray [ position % _numberOfMagnets ] . weighedMedian ( )
80116 filterSum += filterConfig [ position % _numberOfMagnets ]
81117 if ( filterSum !== 0 ) { weightCorrection = _numberOfMagnets / filterSum }
118+ totalNumberOfDatapointsProcessed ++
82119 }
83120 }
84121
85122 function restart ( ) {
86123 recordedRelativePosition = [ ]
87124 recordedAbsolutePosition = [ ]
88125 recordedRawValue = [ ]
89- _cursor = undefined
126+ cursor = undefined
90127 }
91128
92129 function reset ( ) {
130+ if ( totalNumberOfDatapointsProcessed > 0 ) { log . debug ( '*** WARNING: cyclic error filter is reset' ) }
93131 restart ( )
94132 startPosition = undefined
95133 let i = 0
96- let j = 0
97134 while ( i < _numberOfMagnets ) {
98135 filterArray [ i ] = { }
99- filterArray [ i ] = createWeighedSeries ( _numberOfFilterSamples , 1 )
100- j = 0
101- while ( j < Math . ceil ( _numberOfFilterSamples / 5 ) ) {
102- filterArray [ i ] . push ( 1 , 1 )
103- j ++
104- }
136+ filterArray [ i ] = createWeighedMedianSeries ( _numberOfFilterSamples )
137+ filterArray [ i ] . push ( 1 , 1 , 0.5 )
138+ filterArray [ i ] . push ( 2 , 0.90 , 0.5 )
139+ filterArray [ i ] . push ( 3 , 0.95 , 0.5 )
140+ filterArray [ i ] . push ( 4 , 1.05 , 0.5 )
141+ filterArray [ i ] . push ( 5 , 1.10 , 0.5 )
105142 filterConfig [ i ] = 1
106143 i ++
107144 }
108145 filterSum = _numberOfMagnets
109146 weightCorrection = 1
147+ totalNumberOfDatapointsProcessed = 0
148+ offset = 0
110149 }
111150
112151 return {
0 commit comments