This repository has been archived on 2020-04-08. You can view files and clone it, but cannot push or open issues or pull requests.
Files
OTHER2017/pf/EvalWalk.h
2017-04-18 18:03:31 +02:00

405 lines
12 KiB
C++

#ifndef EVALWALK_H
#define EVALWALK_H
#include <KLib/math/filter/particles/ParticleFilter.h>
#include <KLib/math/filter/particles/ParticleFilterEvaluation.h>
#include <KLib/math/filter/particles/ParticleFilterInitializer.h>
#include <KLib/math/filter/particles/ParticleFilterTransition.h>
#include <KLib/math/filter/particles/estimation/ParticleFilterEstimationWeightedAverage.h>
#include <KLib/math/filter/particles/resampling/ParticleFilterResamplingSimple.h>
#include <KLib/math/filter/particles/resampling/ParticleFilterResamplingPercent.h>
#include <KLib/math/filter/particles/resampling/ParticleFilterResamplingNEff.h>
#include "../plots/PlotErrFunc.h"
#include <thread>
#include "Indoor/sensors/radio/setup/WiFiOptimizer.h"
#include "Indoor/sensors/radio/setup/WiFiFingerprint.h"
#include "Indoor/sensors/radio/setup/WiFiFingerprints.h"
#include "Indoor/sensors/radio/setup/WiFiOptimizer.h"
#include "Indoor/sensors/radio/setup/WiFiOptimizerLogDistCeiling.h"
#include "Indoor/sensors/radio/VAPGrouper.h"
#include "Indoor/sensors/imu/StepDetection.h"
#include "Indoor/sensors/imu/TurnDetection.h"
#include "Indoor/sensors/activity/ActivityDetector.h"
#include "Indoor/floorplan/v2/Floorplan.h"
#include "Indoor/floorplan/v2/FloorplanReader.h"
#include "Indoor/floorplan/v2/FloorplanHelper.h"
#include "Indoor/floorplan/v2/FloorplanCeilings.h"
#include "Indoor/sensors/radio/model/WiFiModels.h"
#include "Indoor/sensors/offline/FileReader.h"
#include "../Helper.h"
#include "PF.h"
#include "../plots/PlotErrTime.h"
#include <Indoor/debug/PlotWifiMeasurements.h>
#include <Indoor/sensors/offline/FilePlayer.h>
#include <Indoor/sensors/offline/FileReader.h>
#include <Indoor/sensors/offline/Listener.h>
class EvalWalk : public Offline::Listener {
Grid<MyGridNode>* grid;
K::ParticleFilter<MyState, MyControl, MyObservation>* pf;
std::string runName;
Plotty plotty;
PlotWifiMeasurements plotWifi;
Offline::FileReader reader;
Offline::FilePlayer player;
Offline::FileReader::GroundTruth groundTruthLive;
Timestamp lastTransition;
MyObservation curObs;
MyControl curCtrl;
MyState curEst;
StepDetection stepDetect;
TurnDetection turnDetect;
ActivityDetector actDetect;
std::vector<Point3> groundTruth;
Floorplan::IndoorMap* map;
EarthMapping em;
float absHead = 0;
public:
EvalWalk(Floorplan::IndoorMap* map) : plotty(map), map(map), em(map) {
const std::string saveFile = Settings::pathData + "/grid.dat";
grid = new Grid<MyGridNode>(Settings::Grid::gridSize_cm);
// once
plotty.buildFloorplan();
// deserialize grid
std::ifstream inp(saveFile, std::ofstream::binary);
if (inp) {
grid->read(inp);
inp.close();
} else {
// build the grid
GridFactory<MyGridNode> gf(*grid);
gf.build(map);
Importance::addImportance(*grid);
std::ofstream out(saveFile, std::ofstream::binary);
grid->write(out);
out.close();
}
pf = new K::ParticleFilter<MyState, MyControl, MyObservation>( Settings::numParticles, std::unique_ptr<PFInit>(new PFInit(grid)) );
// transition
pf->setTransition( std::unique_ptr<PFTrans>( new PFTrans(grid)) );
// resampling step?
//pf->setNEffThreshold(0.15);
//pf->setResampling( std::unique_ptr<K::ParticleFilterResamplingSimple<MyState>>(new K::ParticleFilterResamplingSimple<MyState>()) );
//pf->setNEffThreshold(0.75);
//pf->setResampling( std::unique_ptr<K::ParticleFilterResamplingPercent<MyState>>(new K::ParticleFilterResamplingPercent<MyState>(0.10)) );
//pf->setNEffThreshold(0.75);
//pf->setResampling( std::unique_ptr<K::ParticleFilterResamplingPercent<MyState>>(new K::ParticleFilterResamplingPercent<MyState>(0.05)) );
K::ParticleFilterResamplingNEff<MyState>* res = new K::ParticleFilterResamplingNEff<MyState>(0.75, 0.05);
pf->setNEffThreshold(1.0);
pf->setResampling( std::unique_ptr<K::ParticleFilterResamplingNEff<MyState>>(res) );
// move during resampling. NOT ALLOWED!
// res->setDrawCallback([&] (K::Particle<MyState>& p) {
// static std::minstd_rand gen;
// static int cnt = 0; ++cnt;
// bool init = cnt < pf->getParticles().size() * 50;
// const MyGridNode* n = grid->getNodePtrFor(p.state.position);
// std::normal_distribution<float> distTurn(0, (init) ? (0.5) : (0.05));
// for (int j = 0; j < 2; ++j) {
// std::uniform_int_distribution<int> distIdx(0, n->getNumNeighbors()-1);
// const int idx = distIdx(gen);
// n = &(grid->getNeighbor(*n, idx));
// }
// p.state.position = *n;
// p.state.heading.direction += distTurn(gen);
// });
// state estimation step
//pf->setEstimation( std::unique_ptr<K::ParticleFilterEstimationWeightedAverage<MyState>>(new K::ParticleFilterEstimationWeightedAverage<MyState>()));
//pf->setEstimation( std::unique_ptr<K::ParticleFilterEstimationRegionalWeightedAverage<MyState>>(new K::ParticleFilterEstimationRegionalWeightedAverage<MyState>()));
pf->setEstimation( std::unique_ptr<K::ParticleFilterEstimationOrderedWeightedAverage<MyState>>(new K::ParticleFilterEstimationOrderedWeightedAverage<MyState>(0.25f)));
}
void walk1() {
// path1
// absHead = M_PI/2;
// const std::string path = Settings::path1b;
// const std::vector<int> pathPoints = Settings::GroundTruth::path1;
// path2
// absHead = 0;
// const std::string path = Settings::path2b;
// const std::vector<int> pathPoints = Settings::GroundTruth::path2;
// path_toni_inst_2
absHead = M_PI;
const std::string path = Settings::path_toni_inst_2b;
const std::vector<int> pathPoints = Settings::GroundTruth::path_toni_inst_2;
runName = "";
// get ground-truth
groundTruth = FloorplanHelper::getGroundTruth(map, pathPoints);
// wifi model
//WiFiModelLogDistCeiling wifiModel(map);
//wifiModel.loadXML(Settings::wifiAllFixed);
//wifiModel.loadXML(Settings::wifiEachOptParPos);
//WiFiModelPerFloor wifiModel(map);
//wifiModel.loadXML(Settings::wifiEachOptParPos_multimodel);
WiFiModelPerBBox wifiModel(map);
wifiModel.loadXML(Settings::wifiEachOptParPos_perBBox);
// eval
std::unique_ptr<PFEval> eval = std::unique_ptr<PFEval>( new PFEval(grid, wifiModel, em) );
pf->setEvaluation( std::move(eval) );
// data-file
reader.open(path);
groundTruthLive = reader.getGroundTruth(map, pathPoints);
player.setReader(&reader);
player.setListener(this);
player.start();
// wait for completion
player.join();
}
virtual void onGyroscope(const Timestamp ts, const GyroscopeData data) override {
const float delta_rad = turnDetect.addGyroscope(ts, data);
curCtrl.turnSinceLastTransition_rad += delta_rad;
}
virtual void onAccelerometer(const Timestamp ts, const AccelerometerData data) override {
turnDetect.addAccelerometer(ts, data);
const bool step = stepDetect.add(ts, data);
if (step) {
++curCtrl.numStepsSinceLastTransition;
}
gotSensorData(ts);
curCtrl.activityNew = actDetect.add(ts, data);
}
virtual void onGravity(const Timestamp ts, const GravityData data) override {
;
}
virtual void onWiFi(const Timestamp ts, const WiFiMeasurements data) override {
std::cout << "WIFI" << std::endl;
curObs.wifi = data;
plotWifi.add(Settings::WiFiModel::vg_eval.group(data));
plotWifi.plot();
}
virtual void onBarometer(const Timestamp ts, const BarometerData data) override {
;
}
virtual void onGPS(const Timestamp ts, const GPSData data) override {
curObs.gps = data;
}
virtual void onCompass(const Timestamp ts, const CompassData data) override {
const float newAzimuth =- data.azimuth + M_PI/2; // oriented towards north for our map
const float newAzimuth_safe = Angle::makeSafe_2PI(newAzimuth);
const float diff = Angle::getSignedDiffRAD_2PI(curCtrl.compassAzimuth_rad, newAzimuth_safe);
curCtrl.compassAzimuth_rad += diff * 0.01;
curCtrl.compassAzimuth_rad = Angle::makeSafe_2PI(curCtrl.compassAzimuth_rad);
curObs.compassAzimuth_rad = curCtrl.compassAzimuth_rad;
//curCtrl.compassAzimuth_rad = curCtrl.compassAzimuth_rad * 0.99 + newAzimuth * 0.01;
}
private:
/** called when any sensor has received new data */
void gotSensorData(const Timestamp ts) {
curObs.currentTime = ts;
filterUpdateIfNeeded();
}
/** check whether its time for a filter update, and if so, execute the update and return true */
bool filterUpdateIfNeeded() {
static float avgSum = 0;
static int avgCount = 0;
// fixed update rate based on incoming sensor data
// allows working with live data and faster for offline data
const Timestamp diff = curObs.currentTime - lastTransition;
if (diff >= Settings::Filter::updateEvery) {
// as the difference is slightly above the 500ms, calculate the error and incorporate it into the next one
const Timestamp err = diff - Settings::Filter::updateEvery;
lastTransition = curObs.currentTime - err;
const Timestamp ts1 = Timestamp::fromUnixTime();
filterUpdate();
const Timestamp ts2 = Timestamp::fromUnixTime();
const Timestamp tsDiff = ts2-ts1;
//const QString filterTime = QString::number(tsDiff.ms());
//avgSum += tsDiff.ms(); ++avgCount;
//Log::add("xxx", "ts:" + std::to_string(curObs.currentTime.ms()) + " avg:" + std::to_string(avgSum/avgCount));
return true;
} else {
return false;
}
}
K::Statistics<float> statsErr;
int updateCount = 0;
int getNumFHWSAPs(const WiFiMeasurements& mes) {
std::unordered_set<std::string> set;
for (const WiFiMeasurement& m : mes.entries) {
std::string mac = m.getAP().getMAC().asString();
mac.back() = '0';
set.insert(mac);
}
return set.size();
}
/** perform a filter-update (called from a background-loop) */
void filterUpdate() {
++updateCount;
static PlotErrTime pet("\\small{time (sec)}", "\\small{error (m)}", "\\small{APs visible}");
static PlotErrFunc pef("\\small{error (m)}", "\\small{updates (\\%)}");
pef.showMarkers(true);
std::cout << "update" << std::endl;
MyControl ctrlCopy = curCtrl;
//static float absHead = relHeadingOffset;
absHead += ctrlCopy.turnSinceLastTransition_rad;
//lastEst = curEst;
curEst = pf->update(&curCtrl, curObs);
const Point3 curGT = groundTruthLive.get(lastTransition);
plotty.setCurEst(curEst.position.inMeter());
plotty.setGroundTruth(curGT);
if (updateCount > 4) {
// error between ground-truth and estimation
const float estRealErr = curEst.position.inMeter().getDistance(curGT);
statsErr.add(estRealErr);
pef.clear();
pef.add("", &statsErr);
pef.plot();
// timed error
pet.addErr(lastTransition, estRealErr);
pet.addB(lastTransition, getNumFHWSAPs(curObs.wifi));
pet.plot();
// update estimated path
const K::GnuplotPoint3 p3(curEst.position.x_cm, curEst.position.y_cm, curEst.position.z_cm);
plotty.pathEst.add(p3/100);
}
std::cout << statsErr.asString() << std::endl;
// show particles
float maxWeight = 0;
float minWeight = 99;
plotty.particles.clear();
for (const auto p : pf->getParticles()) {
const K::GnuplotPoint3 p3(p.state.position.x_cm, p.state.position.y_cm, p.state.position.z_cm);
plotty.particles.add(p3/100, p.weight);
if (p.weight > maxWeight) {maxWeight = p.weight;}
if (p.weight < minWeight) {minWeight = p.weight;}
}
plotty.gp << "set cbrange [" << minWeight << ":" << maxWeight << "] \n";
// show ground-truth
plotty.pathReal.clear();
for (const Point3 pt : groundTruth) {
plotty.pathReal.add(K::GnuplotPoint3(pt.x, pt.y, pt.z));
}
std::string title =
" time " + std::to_string(curObs.currentTime.sec()) +
" steps: " + std::to_string(ctrlCopy.numStepsSinceLastTransition) +
" turn: " + std::to_string(ctrlCopy.turnSinceLastTransition_rad) +
" APs: " + std::to_string(curObs.wifi.entries.size()) +
" Act: " + std::to_string((int)curCtrl.activityNew);
plotty.setTitle(title);
// relative heading and compass
{
Point2 cen(0.1, 0.9);
Point2 dir(std::cos(absHead), std::sin(absHead));
Point2 arr = cen + dir * 0.1;
plotty.gp << "set arrow 1 from screen " << cen.x << "," << cen.y << " to screen " << arr.x << "," << arr.y << "\n";
dir = Point2(std::cos(ctrlCopy.compassAzimuth_rad), std::sin(ctrlCopy.compassAzimuth_rad));
arr = cen + dir * 0.05;
plotty.gp << "set arrow 2 from screen " << cen.x << "," << cen.y << " to screen " << arr.x << "," << arr.y << "\n";
}
// plot
plotty.plot();
std::this_thread::sleep_for(std::chrono::milliseconds(5));
curCtrl.resetAfterTransition();
// const MyGridNode* node = grid->getNodePtrFor(curEst.position);
// if (node) {
// try {
// pathToDest = trans->modDestination.getShortestPath(*node);
// } catch (...) {;}
// }
// mainController->getMapView()->showGridImportance();
}
};
#endif // EVALWALK_H