Skip to content

Commit 1890702

Browse files
authored
Moved to a Weighed Median Filter
1 parent 389b81d commit 1890702

File tree

1 file changed

+71
-32
lines changed

1 file changed

+71
-32
lines changed

app/engine/utils/CyclicErrorFilter.js

Lines changed: 71 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,18 @@
99
*/
1010
import loglevel from 'loglevel'
1111
import { createSeries } from './Series.js'
12-
import { createWeighedSeries } from './WeighedSeries.js'
12+
import { createWeighedMedianSeries } from './WeighedMedianSeries.js'
1313

1414
const log = loglevel.getLogger('RowingEngine')
1515

1616
export 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

Comments
 (0)