#ifndef EVALWIFIPATHMETHODS_H #define EVALWIFIPATHMETHODS_H #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/model/WiFiModels.h" #include "Indoor/sensors/radio/VAPGrouper.h" #include "Indoor/sensors/offline/FileReader.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 #include #include #include #include #include #include #include #include #include #include "../Structs.h" #include "../plots/Plotty.h" #include "../plots/PlotErrTime.h" #include "../plots/PlotErrFunc.h" #include "../plots/PlotWiFiGroundProb.h" //#include "CSV.h" #include #include #include "../Settings.h" /** evaluate just the wifi error by using the same model but various probability functions */ class EvalWiFiPathMethods { private: Floorplan::IndoorMap* map; BBox3 mapBBox; VAPGrouper* vap = nullptr; K::Statistics* statsOrig_m; K::Statistics* statsOther_m; PlotErrFunc* pef_m; K::Statistics* statsOrig_p; K::Statistics* statsOther_p; PlotErrFunc* pef_p; K::Statistics* statsOrig_c; K::Statistics* statsOther_c; PlotErrFunc* pef_c; WiFiModel* wiModel = nullptr; std::vector randomLoc; std::vector allAPs; int idx = -2; public: /** ctor with map and fingerprints */ EvalWiFiPathMethods(const std::string& mapFile) { // load floorplan map = Floorplan::Reader::readFromFile(mapFile); // estimate bbox mapBBox = FloorplanHelper::getBBox(map); // how to handle VAPs vap = new VAPGrouper(VAPGrouper::Mode::LAST_MAC_DIGIT_TO_ZERO, VAPGrouper::Aggregation::AVERAGE); pef_m = new PlotErrFunc("error (meter)", "measurements (%)"); pef_m->showMarkers(false, false); pef_p = new PlotErrFunc("-log(p(..))", "measurements (%)"); pef_p->showMarkers(false, false); pef_p->getPlot().getAxisX().setRange(K::GnuplotAxis::Range(K::GnuplotAxis::Range::AUTO, K::GnuplotAxis::Range::AUTO)); pef_c = new PlotErrFunc("cross error", "measurements (%)"); pef_c->showMarkers(false, false); pef_c->getPlot().getAxisX().setRange(K::GnuplotAxis::Range(K::GnuplotAxis::Range::AUTO, K::GnuplotAxis::Range::AUTO)); // generate some random locations within the walkable area std::minstd_rand gen; std::uniform_real_distribution distX(mapBBox.getMin().x, mapBBox.getMax().x); std::uniform_real_distribution distY(mapBBox.getMin().y, mapBBox.getMax().y); for (const Floorplan::Floor* f : map->floors) { std::vector floorLoc; while (floorLoc.size() < 100) { const Point3 pt(distX(gen), distY(gen), f->atHeight + 1.3); // human above ground for (const Floorplan::FloorOutlinePolygon* poly : f->outline) { // ensure the random samples are nicely placed along the floor for (const Point3& p : floorLoc) { if (p.getDistance(pt) < 4) {continue;} } HelperPoly hp(*poly); if (hp.contains(pt.xy()*100)) { floorLoc.push_back(pt); } } } for (const Point3 p : floorLoc) {randomLoc.push_back(p);} } // Plotty p(map); // p.buildFloorplan(); // for (const Point3 pt :randomLoc) { // p.points.add({pt.x, pt.y, pt.z}); // } // p.plot(); // sleep(1000); // // generate some random locations within the building // std::minstd_rand gen; // std::uniform_real_distribution distX(mapBBox.getMin().x, mapBBox.getMax().x); // std::uniform_real_distribution distY(mapBBox.getMin().y, mapBBox.getMax().y); // std::uniform_real_distribution distZ(mapBBox.getMin().z, mapBBox.getMax().z); // for (int i = 0; i < 250; ++i) { // const Point3 pt(distX(gen), distY(gen), distZ(gen)); // for (const Point3& p : randomLoc) { // if (p.getDistance(pt) < 3) {continue;} // ensure the random samples are nicely placed // } // randomLoc.push_back(pt); // } } void loadModel(const std::string& xmlFile, const std::string& modelName, const std::string& modeNameA, const std::string& modeNameB) { idx += 2; WiFiModelFactory fac(map); // setup the model this->wiModel = fac.loadXML(xmlFile); statsOrig_m = new K::Statistics(); statsOrig_p = new K::Statistics(); statsOrig_c = new K::Statistics(); statsOther_m = new K::Statistics(); statsOther_p = new K::Statistics(); statsOther_c = new K::Statistics(); pef_m->add(modeNameA, statsOrig_m); pef_m->add(modeNameB, statsOther_m); pef_m->getPlot().getAxisX().setTicsLabelFormat("%h m"); pef_p->add(modeNameA, statsOrig_p); pef_p->add(modeNameB, statsOther_p); pef_c->add(modeNameA, statsOrig_c); pef_c->add(modeNameB, statsOther_c); pef_c->getPlot().getAxisX().setTicsLabelFormat("%h %%"); this->allAPs = wiModel->getAllAPs(); for (const AccessPoint& ap : allAPs) { std::cout << ap.getMAC().asString() << std::endl; } int i = 0; (void) i; } void walks (const std::vector files, const std::vector> gtIndicies) { for (size_t i = 0; i < files.size(); ++i) { walk(files[i], gtIndicies[i]); } } void writeGP(const std::string& path, const std::string& name) { writeGP(*pef_m, K::GnuplotSize(8.6, 3.0), path + "/wifiCompare_" + name + "_meter"); writeGP(*pef_c, K::GnuplotSize(8.6, 3.0), path + "/wifiCompare_" + name + "_cross"); writeGP(*pef_p, K::GnuplotSize(8.6, 3.0), path + "/wifiCompare_" + name + "_logprob"); } void writeGP(PlotErrFunc& pef, K::GnuplotSize size, const std::string& file) { pef.getGP().setTerminal("epslatex", size); pef.getGP().setOutput(file+".tex"); pef.getPlot().getKey().setVisible(true); pef.getPlot().getKey().setSampleLength(0.5); pef.getPlot().getKey().setPosition(K::GnuplotKey::Hor::RIGHT, K::GnuplotKey::Ver::BOTTOM); pef.getPlot().getKey().setWidthIncrement(-4); pef.getPlot().getAxisY().setLabelOffset(2.5, 0); pef.getPlot().getAxisY().setTicsStep(0, 25, 95); pef.getPlot().getAxisY().setRange(K::GnuplotAxis::Range(0,95)); pef.getPlot().getAxisX().setLabel(""); pef.getPlot().setStringMod(new K::GnuplotStringModLaTeX()); pef.getGP() << "set lmargin 4.5\n"; pef.getGP() << "set tmargin 0.1\n"; pef.getGP() << "set rmargin 0.4\n"; pef.getGP() << "set bmargin 2.0\n"; pef.writePlotToFile(file+".gp"); pef.plot(); pef.writePlotToFile(""); } void walk(const std::string& fPath, const std::vector gtIndices) { Offline::FileReader reader(fPath); const Offline::FileReader::GroundTruth gtp = reader.getGroundTruth(map, gtIndices); // process each wifi entry within the offline file for (const auto wifi : reader.wifi) { // all seen APs at one timestamp const WiFiMeasurements& _mes = wifi.data; // perform vap grouping const WiFiMeasurements mes = vap->group(_mes); // error calculation auto funcOrig = [&] (const float* params) -> double { return errFuncOrig(params, mes); }; auto funcOther = [&] (const float* params) -> double { return errFuncSingleProb(params, mes); }; // ADJUST HERE // parameters (x,y,z); float paramsOrig[3] = {0,0,0}; float paramsOther[3] = {0,0,0}; // USE RANGE RANDOM WITH COOLING using LeOpt = K::NumOptAlgoRangeRandom; const std::vector valRegion = { LeOpt::MinMax(mapBBox.getMin().x, mapBBox.getMax().x), // x LeOpt::MinMax(mapBBox.getMin().y, mapBBox.getMax().y), // y LeOpt::MinMax(mapBBox.getMin().z, mapBBox.getMax().z), // z }; K::NumOptAlgoRangeRandom opt(valRegion); opt.setPopulationSize(400); opt.setNumIerations(30); opt.calculateOptimum(funcOrig, paramsOrig); opt.calculateOptimum(funcOther, paramsOther); // estimation //std::cout << params[0] << "," << params[1] << "," << params[2] << std::endl; const Point3 curEstOrig(paramsOrig[0], paramsOrig[1], paramsOrig[2]); const Point3 curEstOther(paramsOther[0], paramsOther[1], paramsOther[2]); const Timestamp ts = mes.entries.front().getTimestamp(); // groud-truth const Point3 gt = gtp.get(ts); // error in meter //const float err_m = gt.xy().getDistance(curEst.xy()); // 2D const float errOrig_m = gt.getDistance(curEstOrig); // 3D const float errOther_m = gt.getDistance(curEstOther); // 3D statsOrig_m->add(errOrig_m); //pet_m.addErr(ts, errOrig_m, idx+0); statsOther_m->add(errOther_m); //pet_m.addErr(ts, errOther_m, idx+1); // // error in -log(p) float gtFloat[3] = {gt.x, gt.y, gt.z + 1.3}; // human above ground { const double probOnGT_a = -errFuncOrig(gtFloat, mes); const double logProbOnGT_a = -std::log10(probOnGT_a); const double logProbOnGTNorm_a = (logProbOnGT_a/mes.entries.size()); statsOrig_p->add(logProbOnGTNorm_a); } { const double probOnGT_b = -errFuncOther(gtFloat, mes); const double logProbOnGT_b = -std::log10(probOnGT_b); const double logProbOnGTNorm_b = (logProbOnGT_b/mes.entries.size()); // break on huge errors if (logProbOnGTNorm_b > 2) { // std::cout << "-------------------------------"<< std::endl; // std::cout << gt.asString() << std::endl; // getVeto(gt, mes); // throw "123"; } statsOther_p->add(logProbOnGTNorm_b); } checkCross(funcOrig, gt, statsOrig_c); checkCross(funcOther, gt, statsOther_c); //pet_p.addErr(ts, logProbOnGTNorm, idx); static int xx = 0; ++xx; if ( (xx % 20) == 0) { pef_p->plot(); std::this_thread::sleep_for(std::chrono::milliseconds(1)); pef_m->plot(); std::this_thread::sleep_for(std::chrono::milliseconds(1)); pef_c->plot(); std::this_thread::sleep_for(std::chrono::milliseconds(1)); } } } private: void checkCross(std::function func, Point3 gt, K::Statistics* stats) { const float gtFloat[3] = {gt.x, gt.y, gt.z + 1.3}; // above ground const double orig_p = -func(gtFloat); float bigger = 0; int cnt = 0; float errSum = 0; int errCnt = 0; for (const Point3 pt : randomLoc) { if (pt.getDistance(gt) > 20) {continue;} float params[3] = {pt.x, pt.y, pt.z}; const double other_p = -func(params); if (pt.getDistance(gt) >= 3) { // at least 3 meter away if (other_p > orig_p*0.75) {++bigger;} } if (pt.getDistance(gt) >=5) { // at least 5 meter away if (other_p > orig_p) {errSum += pt.getDistance(gt); ++errCnt;} } ++cnt; } // for (float rad = 0; rad < 2*M_PI; rad += 0.1) { // Point3 p = gt + Point3(std::cos(rad), std::sin(rad), 0) * 5; // 5 meters around gt // float params[3] = {p.x, p.y, p.z}; // const double other_p = func(params); // if (other_p >orig_p) {++bigger;} // ++cnt; // } const float factor = bigger / (float)cnt; //const float factor = errSum / (errCnt+0.00001f); // break on huge errors if (factor > 0.12) { // std::cout << "-------------------------------"<< std::endl; // std::cout << gt.asString() << std::endl; // func(gtFloat); // const float gtFloat2[3] = {gt.x, gt.y, gt.z-1}; // func(gtFloat2); // throw "123"; } stats->add(factor * 100); // scale to "%" } double errFuncOrig(const float* params, const WiFiMeasurements& mes) { // crop z to 1 meter //params[2] = std::round(params[2]); // suggested position const Point3 pos_m(params[0], params[1], params[2]); const float sigma = 8.0; double prob = 1.0; // calculate error for above position using the currently available measurements for (const WiFiMeasurement& m : mes.entries) { // skip non-FHWS APs if (!LeHelper::isFHWS_AP(m.getAP().getMAC())) {continue;} // get model's rssi for the given location const float rssi_model = wiModel->getRSSI(m.getAP().getMAC(), pos_m); // skip APs unknown to the model if (rssi_model != rssi_model) { std::cout << "unknown ap: " << m.getAP().getMAC().asString() << std::endl; continue; } // get scan's rssi const float rssi_scan = m.getRSSI(); // likelyhood const double p = Distribution::Normal::getProbability(rssi_model, sigma, rssi_scan); //const double p = Distribution::Region::getProbability(rssi_model, sigma, rssi_scan); // adjust prob *= p; } const double err = -prob; return err; } double errFuncOther(const float* params, const WiFiMeasurements& _mes) { // crop z to 1 meter //params[2] = std::round(params[2]); // suggested position const Point3 pos_m(params[0], params[1], params[2]); const float sigma = 8.0; double prob = 1.0; WiFiMeasurements mes = _mes; const auto comp = [] (const WiFiMeasurement& m1, const WiFiMeasurement& m2) {return m1.getRSSI() < m2.getRSSI();}; //std::sort(mes.entries.begin(), mes.entries.end(), comp); const auto& min = std::min_element(mes.entries.begin(), mes.entries.end(), comp); // calculate error for above position using the currently available measurements for (const WiFiMeasurement& m : mes.entries) { // skip non-FHWS APs if (!LeHelper::isFHWS_AP(m.getAP().getMAC())) {continue;} // TESTING if (mes.entries.size() > 8) { //if (m.getRSSI() < mes.entries[1].getRSSI()-10) {continue;} //if (m.getRSSI() > -65) {continue;} if (m.getAP().getMAC() == min->getAP().getMAC()) {continue;} } const float rssi = m.getRSSI(); // const volatile float min = -100; // const volatile float max = -40; // const volatile float val = (m.getRSSI() - min) / (max-min); // get model's rssi for the given location const float rssi_model = wiModel->getRSSI(m.getAP().getMAC(), pos_m); // skip APs unknown to the model if (rssi_model != rssi_model) { std::cout << "unknown ap: " << m.getAP().getMAC().asString() << std::endl; continue; } // get scan's rssi const float rssi_scan = m.getRSSI(); // likelyhood double p = Distribution::Normal::getProbability(rssi_model, sigma, rssi_scan); // adjust prob *= p; } const double err = -prob; return err; } /** USED WITHIN THE PAPER */ double errFuncOtherExponential(const float* params, const WiFiMeasurements& mes) { // crop z to 1 meter //params[2] = std::round(params[2]); // suggested position const Point3 pos_m(params[0], params[1], params[2]); double prob = 1.0; //const auto comp = [] (const WiFiMeasurement& m1, const WiFiMeasurement& m2) {return m1.getRSSI() < m2.getRSSI();}; //const auto& min = std::min_element(mes.entries.begin(), mes.entries.end(), comp); // calculate error for above position using the currently available measurements for (const WiFiMeasurement& m : mes.entries) { // skip non-FHWS APs if (!LeHelper::isFHWS_AP(m.getAP().getMAC())) {continue;} // get model's rssi for the given location const float rssi_model = wiModel->getRSSI(m.getAP().getMAC(), pos_m); // skip APs unknown to the model if (rssi_model != rssi_model) { std::cout << "unknown ap: " << m.getAP().getMAC().asString() << std::endl; continue; } // get scan's rssi const float rssi_scan = m.getRSSI(); // likelyhood double p = Distribution::Exponential::getProbability(1.0, std::abs(rssi_model-rssi_scan)); // adjust prob *= p; } const double err = -prob; return err; } /** TESTING */ double errFuncSingleProb(const float* params, const WiFiMeasurements& mes) { // crop z to 1 meter //params[2] = std::round(params[2]); // suggested position const Point3 pos_m(params[0], params[1], params[2]); int cnt = 0; double error = 0; //const auto comp = [] (const WiFiMeasurement& m1, const WiFiMeasurement& m2) {return m1.getRSSI() < m2.getRSSI();}; //const auto& min = std::min_element(mes.entries.begin(), mes.entries.end(), comp); // calculate error for above position using the currently available measurements for (const WiFiMeasurement& m : mes.entries) { // skip non-FHWS APs if (!LeHelper::isFHWS_AP(m.getAP().getMAC())) {continue;} // get model's rssi for the given location const float rssi_model = wiModel->getRSSI(m.getAP().getMAC(), pos_m); // skip APs unknown to the model if (rssi_model != rssi_model) { std::cout << "unknown ap: " << m.getAP().getMAC().asString() << std::endl; continue; } // get scan's rssi const float rssi_scan = m.getRSSI(); ++cnt; error += std::pow(std::abs(rssi_scan - rssi_model),2); } if (cnt == 0) {return 1e-50;} double errorAvg = std::pow((error / cnt), 1.0/2.0); double prob = Distribution::Normal::getProbability(0, 4, errorAvg); const double err = -prob; return err; } double getVeto(const Point3& pos_m, const WiFiMeasurements& obs) const { struct APR { AccessPoint ap; float rssi; APR(const AccessPoint& ap, const float rssi) : ap(ap), rssi(rssi) {;} }; std::vector all; for (const AccessPoint& ap : allAPs) { const float rssi = wiModel->getRSSI(ap.getMAC(), pos_m); if (rssi != rssi) {throw Exception("should not happen?!");} // unknown to the model all.push_back(APR(ap, rssi)); } // stort by RSSI auto comp = [&] (const APR& apr1, const APR& apr2) {return apr1.rssi > apr2.rssi;}; std::sort(all.begin(), all.end(), comp); int numVetos = 0; for (int i = 0; i < 3; ++i) { const APR& apr = all[i]; if (apr.rssi < -80) {continue;} const WiFiMeasurement* mes = obs.getForMac(apr.ap.getMAC()); if (!mes) { ++numVetos; continue; //std::cout << apr.ap.getMAC().asString() << ":" << apr.rssi << std::endl; } const float rssiScan = mes->getRSSI(); const float rssiModel = apr.rssi; const float diff = std::abs(rssiScan - rssiModel); if (diff > 20) {++numVetos;} } return (numVetos < 1) ? (0.999) : (0.001); // if (numVetos == 0) {return 0.70;} // if (numVetos == 1) {return 0.70;} // else {return 0.01;} } }; #endif // EVALWIFIPATHMETHODS_H