1
0
mirror of https://github.com/tumic0/GPXSee.git synced 2025-02-07 12:05:14 +01:00

Outlier detection is now based on acceleration instead of speed

This commit is contained in:
Martin Tůma 2018-06-19 23:56:36 +02:00
parent 026cc68bf2
commit c5a060ed6b

View File

@ -1,7 +1,5 @@
#include "track.h" #include "track.h"
#define OUTLIER_WINDOW 21
int Track::_elevationWindow = 3; int Track::_elevationWindow = 3;
int Track::_speedWindow = 5; int Track::_speedWindow = 5;
int Track::_heartRateWindow = 3; int Track::_heartRateWindow = 3;
@ -14,13 +12,13 @@ int Track::_pauseInterval = 10;
bool Track::_outlierEliminate = true; bool Track::_outlierEliminate = true;
static qreal median(QVector<qreal> v) static qreal median(QVector<qreal> &v)
{ {
qSort(v.begin(), v.end()); qSort(v.begin(), v.end());
return v.at(v.size() / 2); return v.at(v.size() / 2);
} }
static qreal MAD(QVector<qreal> v, qreal m) static qreal MAD(QVector<qreal> &v, qreal m)
{ {
for (int i = 0; i < v.size(); i++) for (int i = 0; i < v.size(); i++)
v[i] = qAbs(v.at(i) - m); v[i] = qAbs(v.at(i) - m);
@ -28,21 +26,17 @@ static qreal MAD(QVector<qreal> v, qreal m)
return v.at(v.size() / 2); return v.at(v.size() / 2);
} }
static QSet<int> eliminate(const QVector<qreal> &v, int window) static QSet<int> eliminate(const QVector<qreal> &v)
{ {
QSet<int> rm; QSet<int> rm;
qreal m, M;
QVector<qreal> w(v);
qreal m = median(w);
qreal M = MAD(w, m);
if (v.size() < window) for (int i = 0; i < v.size(); i++)
return rm; if (qAbs((0.6745 * (v.at(i) - m)) / M) > 5)
for (int i = window/2; i < v.size() - window/2; i++) {
m = median(v.mid(i - window/2, window));
M = MAD(v.mid(i - window/2, window), m);
if (qAbs((0.6745 * (v.at(i) - m)) / M) > 3.5)
rm.insert(i); rm.insert(i);
}
return rm; return rm;
} }
@ -74,18 +68,17 @@ static Graph filter(const Graph &g, int window)
Track::Track(const TrackData &data) : _data(data) Track::Track(const TrackData &data) : _data(data)
{ {
qreal dt, ds, total; QVector<qreal> acceleration;
int last;
_time.append(0); _time.append(0);
_distance.append(0); _distance.append(0);
_speed.append(0); _speed.append(0);
acceleration.append(0);
last = 0; int last = 0;
for (int i = 1; i < _data.count(); i++) { for (int i = 1; i < _data.count(); i++) {
ds = _data.at(i).coordinates().distanceTo(_data.at(i-1).coordinates()); qreal ds = _data.at(i).coordinates().distanceTo(_data.at(i-1).coordinates());
_distance.append(ds); _distance.append(ds);
if (_data.first().hasTimestamp() && _data.at(i).hasTimestamp() if (_data.first().hasTimestamp() && _data.at(i).hasTimestamp()
@ -99,12 +92,16 @@ Track::Track(const TrackData &data) : _data(data)
if (std::isnan(_time.at(i)) || std::isnan(_time.at(i-1))) if (std::isnan(_time.at(i)) || std::isnan(_time.at(i-1)))
_speed.append(NAN); _speed.append(NAN);
else { else {
dt = _time.at(i) - _time.at(i-1); qreal dt = _time.at(i) - _time.at(i-1);
if (dt < 1e-3) { if (dt < 1e-3) {
_speed.append(_speed.at(i-1)); _speed.append(_speed.at(i-1));
acceleration.append(acceleration.at(i-1));
continue; continue;
} }
_speed.append(ds / dt); _speed.append(ds / dt);
qreal dv = _speed.at(i) - _speed.at(i-1);
acceleration.append(dv / dt);
} }
} }
@ -119,13 +116,13 @@ Track::Track(const TrackData &data) : _data(data)
} }
if (_outlierEliminate) if (_outlierEliminate)
_outliers = eliminate(_speed, OUTLIER_WINDOW); _outliers = eliminate(acceleration);
QSet<int>::const_iterator it; QSet<int>::const_iterator it;
for (it = _stop.constBegin(); it != _stop.constEnd(); ++it) for (it = _stop.constBegin(); it != _stop.constEnd(); ++it)
_outliers.remove(*it); _outliers.remove(*it);
total = 0; qreal total = 0;
for (int i = 0; i < _data.size(); i++) { for (int i = 0; i < _data.size(); i++) {
if (_outliers.contains(i)) if (_outliers.contains(i))
continue; continue;
@ -151,13 +148,13 @@ Graph Track::speed() const
{ {
Graph raw, filtered; Graph raw, filtered;
qreal v; qreal v;
QSet<int> stop; QList<int> stop;
for (int i = 0; i < _data.size(); i++) { for (int i = 0; i < _data.size(); i++) {
if (_stop.contains(i) && (!std::isnan(_speed.at(i)) if (_stop.contains(i) && (!std::isnan(_speed.at(i))
|| _data.at(i).hasSpeed())) { || _data.at(i).hasSpeed())) {
v = 0; v = 0;
stop.insert(raw.size()); stop.append(raw.size());
} else if (_data.at(i).hasSpeed() && !_outliers.contains(i)) } else if (_data.at(i).hasSpeed() && !_outliers.contains(i))
v = _data.at(i).speed(); v = _data.at(i).speed();
else if (!std::isnan(_speed.at(i)) && !_outliers.contains(i)) else if (!std::isnan(_speed.at(i)) && !_outliers.contains(i))
@ -170,9 +167,8 @@ Graph Track::speed() const
filtered = filter(raw, _speedWindow); filtered = filter(raw, _speedWindow);
QSet<int>::const_iterator it; for (int i = 0; i < stop.size(); i++)
for (it = stop.constBegin(); it != stop.constEnd(); ++it) filtered[stop.at(i)].setY(0);
filtered[*it].setY(0);
return filtered; return filtered;
} }
@ -204,13 +200,13 @@ Graph Track::temperature() const
Graph Track::cadence() const Graph Track::cadence() const
{ {
Graph raw, filtered; Graph raw, filtered;
QSet<int> stop; QList<int> stop;
qreal c; qreal c;
for (int i = 0; i < _data.size(); i++) { for (int i = 0; i < _data.size(); i++) {
if (_data.at(i).hasCadence() && _stop.contains(i)) { if (_data.at(i).hasCadence() && _stop.contains(i)) {
c = 0; c = 0;
stop.insert(raw.size()); stop.append(raw.size());
} else if (_data.at(i).hasCadence() && !_outliers.contains(i)) } else if (_data.at(i).hasCadence() && !_outliers.contains(i))
c = _data.at(i).cadence(); c = _data.at(i).cadence();
else else
@ -221,9 +217,8 @@ Graph Track::cadence() const
filtered = filter(raw, _cadenceWindow); filtered = filter(raw, _cadenceWindow);
QSet<int>::const_iterator it; for (int i = 0; i < stop.size(); i++)
for (it = stop.constBegin(); it != stop.constEnd(); ++it) filtered[stop.at(i)].setY(0);
filtered[*it].setY(0);
return filtered; return filtered;
} }
@ -231,13 +226,13 @@ Graph Track::cadence() const
Graph Track::power() const Graph Track::power() const
{ {
Graph raw, filtered; Graph raw, filtered;
QSet<int> stop; QList<int> stop;
qreal p; qreal p;
for (int i = 0; i < _data.size(); i++) { for (int i = 0; i < _data.size(); i++) {
if (_data.at(i).hasPower() && _stop.contains(i)) { if (_data.at(i).hasPower() && _stop.contains(i)) {
p = 0; p = 0;
stop.insert(raw.size()); stop.append(raw.size());
} else if (_data.at(i).hasPower() && !_outliers.contains(i)) } else if (_data.at(i).hasPower() && !_outliers.contains(i))
p = _data.at(i).power(); p = _data.at(i).power();
else else
@ -248,22 +243,29 @@ Graph Track::power() const
filtered = filter(raw, _powerWindow); filtered = filter(raw, _powerWindow);
QSet<int>::const_iterator it; for (int i = 0; i < stop.size(); i++)
for (it = stop.constBegin(); it != stop.constEnd(); ++it) filtered[stop.at(i)].setY(0);
filtered[*it].setY(0);
return filtered; return filtered;
} }
qreal Track::distance() const qreal Track::distance() const
{ {
return _distance.isEmpty() ? 0 : _distance.last(); for (int i = _distance.size() - 1; i >= 0; i--)
if (!_outliers.contains(i))
return _distance.at(i);
return 0;
} }
qreal Track::time() const qreal Track::time() const
{ {
return (_data.size() < 2) ? 0 : for (int i = _data.size() - 1; i >= 0; i--)
(_data.first().timestamp().msecsTo(_data.last().timestamp()) / 1000.0); if (!_outliers.contains(i))
return _data.first().timestamp().msecsTo(_data.at(i).timestamp())
/ 1000.0;
return 0;
} }
qreal Track::movingTime() const qreal Track::movingTime() const