From 04ea2d7a2f9faf2f32c11858529cce2bb2ca1e24 Mon Sep 17 00:00:00 2001 From: Przemyslaw Rokosz Date: Thu, 29 Aug 2024 17:18:47 +0200 Subject: [PATCH 1/6] refactored general fields calculation --- .vscode/settings.json | 3 +- src/telemetry/field.cpp | 1 + src/telemetry/field.h | 1 + src/telemetry/parser.cpp | 114 ++++++++++++++++++--------------------- src/telemetry/parser.h | 3 +- 5 files changed, 58 insertions(+), 64 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 609cf2f..f8262bc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -90,6 +90,7 @@ "typeindex": "cpp", "util": "cpp", "export": "cpp", - "assert": "cpp" + "assert": "cpp", + "ranges": "cpp" } } \ No newline at end of file diff --git a/src/telemetry/field.cpp b/src/telemetry/field.cpp index 878b11d..b61f9cd 100644 --- a/src/telemetry/field.cpp +++ b/src/telemetry/field.cpp @@ -19,6 +19,7 @@ namespace consts { {"power", EField::Power}, {"power3s", EField::Power3s}, {"power10s", EField::Power10s}, + {"power30s", EField::Power30s}, {"respiration_rate", EField::RespirationRate}, {"heart_rate", EField::HeartRate}, {"grit", EField::Grit}, diff --git a/src/telemetry/field.h b/src/telemetry/field.h index 05cb84b..179791b 100644 --- a/src/telemetry/field.h +++ b/src/telemetry/field.h @@ -21,6 +21,7 @@ enum class EField Power, Power3s, Power10s, + Power30s, RespirationRate, HeartRate, Grit, diff --git a/src/telemetry/parser.cpp b/src/telemetry/parser.cpp index 5fbc081..29d9f06 100644 --- a/src/telemetry/parser.cpp +++ b/src/telemetry/parser.cpp @@ -1,6 +1,8 @@ #include "parser.h" #include "utils/geo.h" +#include + namespace vgraph { namespace telemetry { namespace consts { @@ -40,74 +42,57 @@ void parser::update_calculated_fields(std::shared_ptr& seq) { log.info("Calculating additional data fields"); - double total_dist = 0; - std::shared_ptr first = seq->front(); + std::shared_ptr last = first; - std::deque> last10s; - std::deque> last3s; - std::shared_ptr last; + double track_dist = 0; - for (std::shared_ptr& data : *seq) { - // handling of moving time windows - last10s.push_back(data); - last3s.push_back(data); + for (auto it = seq->begin(); it != seq->end(); it++) { + std::shared_ptr& data = *it; - if (last10s.size() > 10) { - last10s.pop_front(); - } - if (last3s.size() > 3) { - last3s.pop_front(); - } + // track distance calculation + track_dist += utils::geo_distance( + last->fields.at(EField::Latitude), last->fields.at(EField::Longitude), + data->fields.at(EField::Latitude), data->fields.at(EField::Longitude)) / 1000.0; - // handling track distance - if (last) { - total_dist += utils::geo_distance( - last->fields.at(EField::Latitude), - last->fields.at(EField::Longitude), - data->fields.at(EField::Latitude), - data->fields.at(EField::Longitude)) / 1000.0; - } + // set track distance field + data->fields[EField::TrackDistance] = track_dist; - // handling distance if not parsed from file - data->fields[EField::TrackDistance] = total_dist; + // set distance field from track distance if not parsed from file if (!data->fields.contains(EField::Distance)) { - data->fields[EField::Distance] = total_dist; - } - - // handling average power and smoothed altitude - std::optional power3s = field_avg(last3s, EField::Power); - if (power3s) { - data->fields[EField::Power3s] = *power3s; - } - std::optional power10s = field_avg(last10s, EField::Power); - if (power10s) { - data->fields[EField::Power10s] = *power10s; - } - std::optional s_alt = field_avg(last10s, EField::Altitude); - if (s_alt) { - data->fields[EField::SmoothAltitude] = *s_alt; + data->fields[EField::Distance] = track_dist; } - // handling speed if not parsed from file if (!data->fields.contains(EField::Speed)) { - double hours = std::chrono::duration_cast((data->timestamp - first->timestamp)).count() / 3600.0; - data->fields[EField::Speed] = data->fields[EField::Distance] / hours; - } + double h = std::chrono::duration_cast((data->timestamp - last->timestamp)).count() / 3600; + double km = data->fields.at(EField::Distance) - last->fields.at(EField::Distance); - bool calculate_gradient = !data->fields.contains(EField::Gradient) && - data->fields.contains(EField::SmoothAltitude) && - data->fields.contains(EField::TrackDistance); - if (calculate_gradient) { - double dist = 1000 * (last10s.back()->fields[EField::TrackDistance] - last10s.front()->fields[EField::TrackDistance]); - if (dist > consts::min_dist_gradient) { - double elev = last10s.back()->fields[EField::SmoothAltitude] - last10s.front()->fields[EField::SmoothAltitude]; - data->fields[EField::Gradient] = utils::gradient(dist, elev); - } + data->fields[EField::Speed] = km / h; } + // setting time averaged fields + set_if_ok(data, EField::Power3s, field_avg(std::make_reverse_iterator(it+1), seq->rend(), EField::Power, 3)); + set_if_ok(data, EField::Power10s, field_avg(std::make_reverse_iterator(it+1), seq->rend(), EField::Power, 10)); + set_if_ok(data, EField::Power30s, field_avg(std::make_reverse_iterator(it+1), seq->rend(), EField::Power, 30)); + set_if_ok(data, EField::SmoothAltitude, field_avg(std::make_reverse_iterator(it+1), seq->rend(), EField::Altitude, 10)); + + // store last point last = data; } + + // for (std::shared_ptr& data : *seq) { + // handling gradient + // bool calculate_gradient = !data->fields.contains(EField::Gradient) && + // data->fields.contains(EField::SmoothAltitude) && + // data->fields.contains(EField::TrackDistance); + // if (calculate_gradient) { + // double dist = 1000 * (last10s.back()->fields[EField::TrackDistance] - last10s.front()->fields[EField::TrackDistance]); + // if (dist > consts::min_dist_gradient) { + // double elev = last10s.back()->fields[EField::SmoothAltitude] - last10s.front()->fields[EField::SmoothAltitude]; + // data->fields[EField::Gradient] = utils::gradient(dist, elev); + // } + // } + // } } void parser::print_stats(std::shared_ptr& seq) @@ -157,20 +142,25 @@ void parser::print_stats(std::shared_ptr& seq) log.info("Respiration Rate Min/Avg/Max: {:7.2f} / {:7.2f} / {:7.2f} brpm", respiration.min, respiration.avg, respiration.max); } -std::optional parser::field_avg(std::deque> points, EField field) +void parser::set_if_ok(std::shared_ptr& data, EField field, std::optional value) { - bool ok = false; - double sum = 0.0; + if (value) { + data->fields[field] = *value; + } +} - for (std::shared_ptr& data : points) { - if (data->fields.contains(field)) { - sum += data->fields.at(field); - ok = true; +std::optional parser::field_avg(datapoint_sequence::reverse_iterator it, datapoint_sequence::reverse_iterator rend, EField field, int count) +{ + int i = 0; + double total = 0.0; + for (i=0; it != rend && ifields.contains(field)) { + total += (*it)->fields.at(field); } } - if (ok) { - return sum / points.size(); + if (i) { + return total/i; } return std::nullopt; } diff --git a/src/telemetry/parser.h b/src/telemetry/parser.h index 5c1ab08..6738ffa 100644 --- a/src/telemetry/parser.h +++ b/src/telemetry/parser.h @@ -28,7 +28,8 @@ class parser { void update_calculated_fields(std::shared_ptr& seq); void print_stats(std::shared_ptr& seq); - std::optional field_avg(std::deque> points, EField field); + void set_if_ok(std::shared_ptr& data, EField field, std::optional value); + std::optional field_avg(datapoint_sequence::reverse_iterator it, datapoint_sequence::reverse_iterator rend, EField field, int count); }; } // namespace telemetry From f1eb8506da0940ab8df70ee6a95230bae070bec0 Mon Sep 17 00:00:00 2001 From: Przemyslaw Rokosz Date: Thu, 29 Aug 2024 17:48:47 +0200 Subject: [PATCH 2/6] wip --- src/telemetry/parser.cpp | 54 +++++++++++++++++++++++++++------------- src/telemetry/parser.h | 3 +++ 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/src/telemetry/parser.cpp b/src/telemetry/parser.cpp index 29d9f06..3d5642b 100644 --- a/src/telemetry/parser.cpp +++ b/src/telemetry/parser.cpp @@ -7,6 +7,12 @@ namespace vgraph { namespace telemetry { namespace consts { double min_dist_gradient(10);// meters in 10s + + // gradient windows in meters + double gradient_window_behind(0.015); + double gradient_window_ahead(0.005); + double gradient_cap_window_behind(gradient_window_behind*4); + double gradient_cap_window_ahead(gradient_window_ahead*4); } struct stats { @@ -76,23 +82,22 @@ void parser::update_calculated_fields(std::shared_ptr& seq) set_if_ok(data, EField::Power30s, field_avg(std::make_reverse_iterator(it+1), seq->rend(), EField::Power, 30)); set_if_ok(data, EField::SmoothAltitude, field_avg(std::make_reverse_iterator(it+1), seq->rend(), EField::Altitude, 10)); + // calculate gradient + if (!data->fields.contains(EField::Gradient) && data->fields.contains(EField::SmoothAltitude)) { + double grad = gradient_between(seq, track_dist - consts::gradient_window_behind, track_dist + consts::gradient_window_ahead); + double cap = gradient_between(seq, track_dist - consts::gradient_cap_window_behind, track_dist + consts::gradient_cap_window_ahead); + + if (std::abs(grad) > std::abs(cap)) { + grad = grad*cap > 0 ? cap : -cap; + // convert cap to gradient sign if needed + } + + data->fields[EField::Gradient] = grad*100;//convert to pct + } + // store last point last = data; } - - // for (std::shared_ptr& data : *seq) { - // handling gradient - // bool calculate_gradient = !data->fields.contains(EField::Gradient) && - // data->fields.contains(EField::SmoothAltitude) && - // data->fields.contains(EField::TrackDistance); - // if (calculate_gradient) { - // double dist = 1000 * (last10s.back()->fields[EField::TrackDistance] - last10s.front()->fields[EField::TrackDistance]); - // if (dist > consts::min_dist_gradient) { - // double elev = last10s.back()->fields[EField::SmoothAltitude] - last10s.front()->fields[EField::SmoothAltitude]; - // data->fields[EField::Gradient] = utils::gradient(dist, elev); - // } - // } - // } } void parser::print_stats(std::shared_ptr& seq) @@ -102,7 +107,6 @@ void parser::print_stats(std::shared_ptr& seq) stats speed; stats power; stats cadence; - stats heartrate; stats respiration; for (auto data : *seq) { @@ -111,7 +115,6 @@ void parser::print_stats(std::shared_ptr& seq) speed.add(data, EField::Speed); power.add(data, EField::Power); cadence.add(data, EField::Cadence); - heartrate.add(data, EField::HeartRate); respiration.add(data, EField::RespirationRate); } @@ -138,10 +141,27 @@ void parser::print_stats(std::shared_ptr& seq) log.info(" Gradient Min/Avg/Max: {:7.2f} / {:7.2f} / {:7.2f} %", gradient.min, gradient.avg, gradient.max); log.info(" Power Min/Avg/Max: {:7.2f} / {:7.2f} / {:7.2f} W", power.min, power.avg, power.max); log.info(" Cadence Min/Avg/Max: {:7.2f} / {:7.2f} / {:7.2f} rpm", cadence.min, cadence.avg, cadence.max); - log.info(" Heart Rate Min/Avg/Max: {:7.2f} / {:7.2f} / {:7.2f} bpm", heartrate.min, heartrate.avg, heartrate.max); log.info("Respiration Rate Min/Avg/Max: {:7.2f} / {:7.2f} / {:7.2f} brpm", respiration.min, respiration.avg, respiration.max); } +double parser::gradient_between(std::shared_ptr& seq, double dist_a, double dist_b) +{ + if (dist_a <= dist_b) { + return 0; + } + + double alt_a = get_by_distance(seq, dist_a, EField::Altitude).back(); + double alt_b = get_by_distance(seq, dist_b, EField::Altitude).front(); + + return (alt_b - alt_a)/((dist_b - dist_a)*1000); +} + +std::vector parser::get_by_distance(std::shared_ptr& seq, double dist, EField field) +{ + std::vector res; + return std::move(res); +} + void parser::set_if_ok(std::shared_ptr& data, EField field, std::optional value) { if (value) { diff --git a/src/telemetry/parser.h b/src/telemetry/parser.h index 6738ffa..64b8bae 100644 --- a/src/telemetry/parser.h +++ b/src/telemetry/parser.h @@ -28,6 +28,9 @@ class parser { void update_calculated_fields(std::shared_ptr& seq); void print_stats(std::shared_ptr& seq); + double gradient_between(std::shared_ptr& seq, double dist_a, double dist_b); + std::vector get_by_distance(std::shared_ptr& seq, double dist, EField field); + void set_if_ok(std::shared_ptr& data, EField field, std::optional value); std::optional field_avg(datapoint_sequence::reverse_iterator it, datapoint_sequence::reverse_iterator rend, EField field, int count); }; From 31ae31ddf03e7312b01647d23d05110b47e3e1e4 Mon Sep 17 00:00:00 2001 From: Przemyslaw Rokosz Date: Thu, 29 Aug 2024 18:37:26 +0200 Subject: [PATCH 3/6] refactoring --- src/manager.cpp | 2 +- src/telemetry/datapoint.h | 3 ++- src/telemetry/fit_parser.cpp | 32 +++++++++++----------- src/telemetry/fit_parser.h | 6 ++--- src/telemetry/parser.cpp | 51 +++++++++++++++++++----------------- src/telemetry/parser.h | 20 +++++++------- src/telemetry/telemetry.cpp | 22 ++++++++-------- src/telemetry/telemetry.h | 10 +++---- 8 files changed, 75 insertions(+), 71 deletions(-) diff --git a/src/manager.cpp b/src/manager.cpp index 8eba337..e68fafd 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -68,7 +68,7 @@ void manager::run() log.info("Telemetry processing time: {:.3f} s", std::chrono::duration_cast(t2 - t1).count()/1000.0); - log.info("Overlay pre-setup time: {:.3f} s", + log.info("Overlay setup time: {:.3f} s", std::chrono::duration_cast(t3 - t2).count()/1000.0); log.info("Video generation time: {:.3f} s", std::chrono::duration_cast(t4 - t3).count()/1000.0); diff --git a/src/telemetry/datapoint.h b/src/telemetry/datapoint.h index f375f0b..710ee92 100644 --- a/src/telemetry/datapoint.h +++ b/src/telemetry/datapoint.h @@ -17,7 +17,8 @@ struct datapoint { std::map fields; }; -using datapoint_sequence = std::vector>; +using datapoint_ptr = std::shared_ptr; +using datapoint_seq = std::vector; } // namespace telemetry } // namespace vgraph diff --git a/src/telemetry/fit_parser.cpp b/src/telemetry/fit_parser.cpp index 376611e..dabf056 100644 --- a/src/telemetry/fit_parser.cpp +++ b/src/telemetry/fit_parser.cpp @@ -9,18 +9,18 @@ namespace consts { const unsigned int garmin_epoch = 631065600; // seconds since 1970-01-01 00:00:00 to 1989-12-31 00:00:00 } -std::shared_ptr fit_parser::parse_impl(const std::filesystem::path& path) +datapoint_seq fit_parser::parse_impl(const std::filesystem::path& path) { log.info("Decoding FIT file {}", path.string()); if (!std::filesystem::exists(path)) { log.error("File does not exist: {}", path.string()); - return nullptr; + return {}; } if (path.extension() != ".fit") { log.error("File extension is not .fit: {}", path.string()); - return nullptr; + return {}; } std::fstream file; @@ -28,18 +28,18 @@ std::shared_ptr fit_parser::parse_impl(const std::filesystem if (!file.is_open()) { log.error("Error opening file {}", path.string()); - return nullptr; + return {}; } - std::shared_ptr ptr = parse_filestream(file); - if (ptr) { + datapoint_seq seq(parse_filestream(file)); + if (!seq.empty()) { log.info("Decoded FIT file {}", path.string()); } - return ptr; + return std::move(seq); } -std::shared_ptr fit_parser::parse_filestream(std::fstream& file) +datapoint_seq fit_parser::parse_filestream(std::fstream& file) { fit::Decode decode; if (!decode.CheckIntegrity(file)) { @@ -49,8 +49,8 @@ std::shared_ptr fit_parser::parse_filestream(std::fstream& f using std::placeholders::_1; fit::MesgBroadcaster msg_broadcaster; - std::shared_ptr seq = std::make_shared(); - listener listen(std::bind(&fit_parser::handle_record, this, _1, seq)); + datapoint_seq seq; + listener listen(std::bind(&fit_parser::handle_record, this, _1, std::ref(seq))); msg_broadcaster.AddListener(listen); @@ -61,20 +61,20 @@ std::shared_ptr fit_parser::parse_filestream(std::fstream& f catch (const fit::RuntimeException& e) { log.error("Exception decoding file: {}", e.what()); - return nullptr; + return {}; } catch (...) { log.error("Exception decoding file"); - return nullptr; + return {}; } - return seq; + return std::move(seq); } -void fit_parser::handle_record(fit::RecordMesg& record, std::shared_ptr out_seq) +void fit_parser::handle_record(fit::RecordMesg& record, datapoint_seq& out_seq) { - std::shared_ptr data = std::make_shared(); + datapoint_ptr data = std::make_shared(); if (record.IsTimestampValid()) data->timestamp = parse_timestamp(record.GetTimestamp()); @@ -107,7 +107,7 @@ void fit_parser::handle_record(fit::RecordMesg& record, std::shared_ptrfields[EField::Flow] = record.GetFlow(); - out_seq->push_back(data); + out_seq.push_back(std::move(data)); } // timestamp: seconds since UTC 00:00 Dec 31 1989 diff --git a/src/telemetry/fit_parser.h b/src/telemetry/fit_parser.h index d0a05cd..0b3279e 100644 --- a/src/telemetry/fit_parser.h +++ b/src/telemetry/fit_parser.h @@ -22,9 +22,9 @@ class fit_parser : public parser { private: utils::logging::logger log{"fit_parser"}; - std::shared_ptr parse_impl(const std::filesystem::path& path) override; - std::shared_ptr parse_filestream(std::fstream& file); - void handle_record(fit::RecordMesg& record, std::shared_ptr out_seq); + datapoint_seq parse_impl(const std::filesystem::path& path) override; + datapoint_seq parse_filestream(std::fstream& file); + void handle_record(fit::RecordMesg& record, datapoint_seq& out_seq); std::chrono::time_point parse_timestamp(FIT_UINT32 timestamp); class listener : public fit::RecordMesgListener diff --git a/src/telemetry/parser.cpp b/src/telemetry/parser.cpp index 3d5642b..ad4773a 100644 --- a/src/telemetry/parser.cpp +++ b/src/telemetry/parser.cpp @@ -21,7 +21,7 @@ struct stats { double avg = 0; int count = 0; - void add(std::shared_ptr& data, EField field) { + void add(const datapoint_ptr& data, EField field) { if (data->fields.contains(field)) { double val = data->fields.at(field); @@ -32,29 +32,29 @@ struct stats { } }; -std::shared_ptr parser::parse(const std::filesystem::path& path) +datapoint_seq parser::parse(const std::filesystem::path& path) { - std::shared_ptr seq = parse_impl(path); + datapoint_seq seq = parse_impl(path); - if (seq) { + if (!seq.empty()) { update_calculated_fields(seq); print_stats(seq); } - return seq; + return std::move(seq); } -void parser::update_calculated_fields(std::shared_ptr& seq) +void parser::update_calculated_fields(datapoint_seq& seq) { log.info("Calculating additional data fields"); - std::shared_ptr first = seq->front(); - std::shared_ptr last = first; + datapoint_ptr first = seq.front(); + datapoint_ptr last = first; double track_dist = 0; - for (auto it = seq->begin(); it != seq->end(); it++) { - std::shared_ptr& data = *it; + for (auto it = seq.begin(); it != seq.end(); it++) { + datapoint_ptr& data = *it; // track distance calculation track_dist += utils::geo_distance( @@ -77,10 +77,10 @@ void parser::update_calculated_fields(std::shared_ptr& seq) } // setting time averaged fields - set_if_ok(data, EField::Power3s, field_avg(std::make_reverse_iterator(it+1), seq->rend(), EField::Power, 3)); - set_if_ok(data, EField::Power10s, field_avg(std::make_reverse_iterator(it+1), seq->rend(), EField::Power, 10)); - set_if_ok(data, EField::Power30s, field_avg(std::make_reverse_iterator(it+1), seq->rend(), EField::Power, 30)); - set_if_ok(data, EField::SmoothAltitude, field_avg(std::make_reverse_iterator(it+1), seq->rend(), EField::Altitude, 10)); + set_if_ok(*it, EField::Power3s, field_avg(std::make_reverse_iterator(it+1), seq.rend(), EField::Power, 3)); + set_if_ok(*it, EField::Power10s, field_avg(std::make_reverse_iterator(it+1), seq.rend(), EField::Power, 10)); + set_if_ok(*it, EField::Power30s, field_avg(std::make_reverse_iterator(it+1), seq.rend(), EField::Power, 30)); + set_if_ok(*it, EField::SmoothAltitude, field_avg(std::make_reverse_iterator(it+1), seq.rend(), EField::Altitude, 10)); // calculate gradient if (!data->fields.contains(EField::Gradient) && data->fields.contains(EField::SmoothAltitude)) { @@ -100,7 +100,7 @@ void parser::update_calculated_fields(std::shared_ptr& seq) } } -void parser::print_stats(std::shared_ptr& seq) +void parser::print_stats(datapoint_seq& seq) { stats altitude; stats gradient; @@ -109,7 +109,7 @@ void parser::print_stats(std::shared_ptr& seq) stats cadence; stats respiration; - for (auto data : *seq) { + for (auto data : seq) { altitude.add(data, EField::Altitude); gradient.add(data, EField::Gradient); speed.add(data, EField::Speed); @@ -118,16 +118,16 @@ void parser::print_stats(std::shared_ptr& seq) respiration.add(data, EField::RespirationRate); } - auto first = seq->front(); - auto last = seq->back(); + const datapoint_ptr& first = seq.front(); + const datapoint_ptr& last = seq.back(); double distance = 0; if (last->fields.contains(EField::Distance)) - distance = last->fields[EField::Distance]; + distance = last->fields.at(EField::Distance); double track_distance = 0; if (last->fields.contains(EField::TrackDistance)) - track_distance = last->fields[EField::TrackDistance]; + track_distance = last->fields.at(EField::TrackDistance); auto time = last->timestamp - first->timestamp; double hours = std::chrono::duration_cast((time)).count() / 3600.0; @@ -144,7 +144,7 @@ void parser::print_stats(std::shared_ptr& seq) log.info("Respiration Rate Min/Avg/Max: {:7.2f} / {:7.2f} / {:7.2f} brpm", respiration.min, respiration.avg, respiration.max); } -double parser::gradient_between(std::shared_ptr& seq, double dist_a, double dist_b) +double parser::gradient_between(const datapoint_seq& seq, double dist_a, double dist_b) { if (dist_a <= dist_b) { return 0; @@ -156,20 +156,23 @@ double parser::gradient_between(std::shared_ptr& seq, double return (alt_b - alt_a)/((dist_b - dist_a)*1000); } -std::vector parser::get_by_distance(std::shared_ptr& seq, double dist, EField field) +std::vector parser::get_by_distance(const datapoint_seq& seq, double dist, EField field) { std::vector res; + //TODO return std::move(res); } -void parser::set_if_ok(std::shared_ptr& data, EField field, std::optional value) +void parser::set_if_ok(datapoint_ptr& data, EField field, std::optional value) { if (value) { data->fields[field] = *value; } } -std::optional parser::field_avg(datapoint_sequence::reverse_iterator it, datapoint_sequence::reverse_iterator rend, EField field, int count) +std::optional parser::field_avg(datapoint_seq::const_reverse_iterator it, + datapoint_seq::const_reverse_iterator rend, + EField field, int count) { int i = 0; double total = 0.0; diff --git a/src/telemetry/parser.h b/src/telemetry/parser.h index 64b8bae..9c303fd 100644 --- a/src/telemetry/parser.h +++ b/src/telemetry/parser.h @@ -6,8 +6,6 @@ #include "utils/logging/logger.h" #include -#include -#include namespace vgraph { namespace telemetry { @@ -17,22 +15,24 @@ class parser { parser() = default; ~parser() = default; - std::shared_ptr parse(const std::filesystem::path& path); + datapoint_seq parse(const std::filesystem::path& path); protected: - virtual std::shared_ptr parse_impl(const std::filesystem::path& path) = 0; + virtual datapoint_seq parse_impl(const std::filesystem::path& path) = 0; private: utils::logging::logger log{"parser"}; - void update_calculated_fields(std::shared_ptr& seq); - void print_stats(std::shared_ptr& seq); + void update_calculated_fields(datapoint_seq& seq); + void print_stats(datapoint_seq& seq); - double gradient_between(std::shared_ptr& seq, double dist_a, double dist_b); - std::vector get_by_distance(std::shared_ptr& seq, double dist, EField field); + double gradient_between(const datapoint_seq& seq, double dist_a, double dist_b); + std::vector get_by_distance(const datapoint_seq& seq, double dist, EField field); - void set_if_ok(std::shared_ptr& data, EField field, std::optional value); - std::optional field_avg(datapoint_sequence::reverse_iterator it, datapoint_sequence::reverse_iterator rend, EField field, int count); + void set_if_ok(datapoint_ptr& data, EField field, std::optional value); + std::optional field_avg(datapoint_seq::const_reverse_iterator it, + datapoint_seq::const_reverse_iterator rend, + EField field, int count); }; } // namespace telemetry diff --git a/src/telemetry/telemetry.cpp b/src/telemetry/telemetry.cpp index 9824155..f4f9d68 100644 --- a/src/telemetry/telemetry.cpp +++ b/src/telemetry/telemetry.cpp @@ -11,21 +11,21 @@ namespace helper { } } -telemetry::telemetry(std::shared_ptr seq, long offset) : - points(*seq) +telemetry::telemetry(datapoint_seq seq, long offset) : + points(seq) { - if (!seq) { + if (points.empty()) { log.warning("No telemetry loaded"); return; } - auto first_timestamp = seq->front()->timestamp; - auto prev_timestamp = seq->front()->timestamp; + auto first_timestamp = points.front()->timestamp; + auto prev_timestamp = points.front()->timestamp; long sum = 0; bool first = true; - for (auto& data : *seq) { + for (auto& data : points) { long us = std::chrono::duration_cast(data->timestamp - first_timestamp).count(); time_points[us+offset] = data; @@ -37,10 +37,10 @@ telemetry::telemetry(std::shared_ptr seq, long offset) : first = false; } - avg_interval = first / seq->size(); + avg_interval = first / points.size(); } -std::shared_ptr telemetry::get(double timestamp) const +datapoint_ptr telemetry::get(double timestamp) const { long us = helper::in_microseconds(timestamp); @@ -54,7 +54,7 @@ std::shared_ptr telemetry::get(double timestamp) const return (--time_points.lower_bound(us))->second; } -const std::vector>& telemetry::get_all() const +const std::vector& telemetry::get_all() const { return points; } @@ -63,11 +63,11 @@ std::shared_ptr telemetry::load(const std::string& path, std::optiona { utils::logging::logger log{"telemetry::load"}; - std::shared_ptr seq; + datapoint_seq seq; if (path.ends_with(".fit")) { seq = fit_parser().parse(path); - if (!seq) { + if (seq.empty()) { log.error("Failed to parse FIT file: {}", path); return nullptr; } diff --git a/src/telemetry/telemetry.h b/src/telemetry/telemetry.h index e12f87e..e3aa85c 100644 --- a/src/telemetry/telemetry.h +++ b/src/telemetry/telemetry.h @@ -11,11 +11,11 @@ namespace telemetry { class telemetry { public: - telemetry(std::shared_ptr seq, long offset=0); + telemetry(datapoint_seq seq, long offset=0); ~telemetry() = default; - std::shared_ptr get(double timestamp) const; - const std::vector>& get_all() const; + datapoint_ptr get(double timestamp) const; + const datapoint_seq& get_all() const; static std::shared_ptr load(const std::string& path, std::optional offset); // optional offset in seconds @@ -24,8 +24,8 @@ class telemetry { long avg_interval = 0; - std::vector> points; - std::map> time_points; + datapoint_seq points; + std::map time_points; }; } // namespace telemetry From 7527e7aaa7af7102aac5da6e90531a866729115a Mon Sep 17 00:00:00 2001 From: Przemyslaw Rokosz Date: Thu, 29 Aug 2024 18:41:08 +0200 Subject: [PATCH 4/6] cleanup --- src/telemetry/CMakeLists.txt | 2 -- src/telemetry/value.h | 17 ----------------- src/utils/argument_parser.cpp | 2 +- src/video/generator.cpp | 2 +- test/telemetry/fit_parser_test.cpp | 2 -- test/utils/logging/logger_test.cpp | 2 -- 6 files changed, 2 insertions(+), 25 deletions(-) delete mode 100644 src/telemetry/value.h diff --git a/src/telemetry/CMakeLists.txt b/src/telemetry/CMakeLists.txt index 87134a7..2005320 100644 --- a/src/telemetry/CMakeLists.txt +++ b/src/telemetry/CMakeLists.txt @@ -9,9 +9,7 @@ target_sources(vgraph_lib PUBLIC datapoint.h field.h - value.h parser.h fit_parser.h telemetry.h - ) diff --git a/src/telemetry/value.h b/src/telemetry/value.h deleted file mode 100644 index f95878e..0000000 --- a/src/telemetry/value.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef VALUE_H -#define VALUE_H - -namespace vgraph { -namespace telemetry { - -class value { -public: - value() = default; - ~value() = default; -}; -//TODO to be implemented, templated for different types of values - -} // namespace telemetry -} // namespace vgraph - -#endif // VALUE_H \ No newline at end of file diff --git a/src/utils/argument_parser.cpp b/src/utils/argument_parser.cpp index ed9a287..6e2d4fe 100644 --- a/src/utils/argument_parser.cpp +++ b/src/utils/argument_parser.cpp @@ -170,7 +170,7 @@ int argument_parser::get(const std::string& key) const throw argument_exception(std::format("Value '{}' of argument '{}' is out of range", val[0], key)); } return ret; -}//FIXME to be refactored after merging with improved arguments parser +} template <> std::vector argument_parser::get(const std::string& key) const diff --git a/src/video/generator.cpp b/src/video/generator.cpp index 0a84025..9f61bb5 100644 --- a/src/video/generator.cpp +++ b/src/video/generator.cpp @@ -362,7 +362,7 @@ bool generator::link_elements(const pipeline_element& src, const pipeline_elemen } bool generator::config_elements() -{//FIXME try to incorporate congig params into path vectors and auto configure them (note: different types) +{//FIXME try to incorporate config params into path vectors and auto configure them (note: different types) log.debug("Pipeline elements_ basic config"); bool ok = true; diff --git a/test/telemetry/fit_parser_test.cpp b/test/telemetry/fit_parser_test.cpp index f387467..55d9cdf 100644 --- a/test/telemetry/fit_parser_test.cpp +++ b/test/telemetry/fit_parser_test.cpp @@ -49,8 +49,6 @@ TEST_F(fit_parser_test, parse_correct_file_returns_sequence) { auto retval = uut->parse(consts::fit_path); ASSERT_NE(nullptr, retval); - - //TODO to be implemented } } // namespace telemetry diff --git a/test/utils/logging/logger_test.cpp b/test/utils/logging/logger_test.cpp index 44eb049..1f65a42 100644 --- a/test/utils/logging/logger_test.cpp +++ b/test/utils/logging/logger_test.cpp @@ -249,8 +249,6 @@ TEST_F(logger_test, error_sink_receives_error_logs_only_with_args) ASSERT_EQ(consts::expected_error_args, sink_stream.str()); } -//FIXME tests for set_log_level - } // namespace logging } // namespace utils } // namespace vgraph From f4008c3b000d1fe04a7c4794b8f93fc2a89e4b1a Mon Sep 17 00:00:00 2001 From: Przemyslaw Rokosz Date: Thu, 29 Aug 2024 18:42:57 +0200 Subject: [PATCH 5/6] cleanup --- src/telemetry/datapoint.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/telemetry/datapoint.h b/src/telemetry/datapoint.h index 710ee92..544130b 100644 --- a/src/telemetry/datapoint.h +++ b/src/telemetry/datapoint.h @@ -2,7 +2,6 @@ #define DATAPOINT_H #include "field.h" -#include "value.h" #include #include From 637418e91b0f48af559ebc0b2e5aebc14d84849f Mon Sep 17 00:00:00 2001 From: Przemyslaw Rokosz Date: Thu, 29 Aug 2024 22:15:32 +0200 Subject: [PATCH 6/6] gradient calculation improvements --- src/telemetry/parser.cpp | 81 ++++++++++++++++++++++-------- src/telemetry/parser.h | 7 ++- test/telemetry/fit_parser_test.cpp | 12 ++--- test/testdata/layout.xml | 19 +++++-- 4 files changed, 84 insertions(+), 35 deletions(-) diff --git a/src/telemetry/parser.cpp b/src/telemetry/parser.cpp index ad4773a..fa66459 100644 --- a/src/telemetry/parser.cpp +++ b/src/telemetry/parser.cpp @@ -2,6 +2,8 @@ #include "utils/geo.h" #include +#include +#include namespace vgraph { namespace telemetry { @@ -9,10 +11,17 @@ namespace consts { double min_dist_gradient(10);// meters in 10s // gradient windows in meters - double gradient_window_behind(0.015); - double gradient_window_ahead(0.005); - double gradient_cap_window_behind(gradient_window_behind*4); - double gradient_cap_window_ahead(gradient_window_ahead*4); + int gradient_window_behind(15); + int gradient_window_ahead(15); + int gradient_cap_window_behind(gradient_window_behind*4); + int gradient_cap_window_ahead(gradient_window_ahead*4); +} +namespace helper { + int km_to_m(double km) + { + return static_cast(std::round(km*1000)); + } + } struct stats { @@ -52,6 +61,7 @@ void parser::update_calculated_fields(datapoint_seq& seq) datapoint_ptr last = first; double track_dist = 0; + std::multimap seq_by_dist; for (auto it = seq.begin(); it != seq.end(); it++) { datapoint_ptr& data = *it; @@ -77,15 +87,24 @@ void parser::update_calculated_fields(datapoint_seq& seq) } // setting time averaged fields - set_if_ok(*it, EField::Power3s, field_avg(std::make_reverse_iterator(it+1), seq.rend(), EField::Power, 3)); - set_if_ok(*it, EField::Power10s, field_avg(std::make_reverse_iterator(it+1), seq.rend(), EField::Power, 10)); - set_if_ok(*it, EField::Power30s, field_avg(std::make_reverse_iterator(it+1), seq.rend(), EField::Power, 30)); - set_if_ok(*it, EField::SmoothAltitude, field_avg(std::make_reverse_iterator(it+1), seq.rend(), EField::Altitude, 10)); + set_if_ok(data, EField::Power3s, field_avg(std::make_reverse_iterator(it+1), seq.rend(), EField::Power, 3)); + set_if_ok(data, EField::Power10s, field_avg(std::make_reverse_iterator(it+1), seq.rend(), EField::Power, 10)); + set_if_ok(data, EField::Power30s, field_avg(std::make_reverse_iterator(it+1), seq.rend(), EField::Power, 30)); + set_if_ok(data, EField::SmoothAltitude, field_avg(std::make_reverse_iterator(it+1), seq.rend(), EField::Altitude, 10)); + + // store point into sequence by distance + seq_by_dist.insert(std::make_pair(helper::km_to_m(data->fields.at(EField::Distance)), data)); + + // store last point + last = data; + } - // calculate gradient + log.info("Calculating gradients"); + for (datapoint_ptr& data : seq) { if (!data->fields.contains(EField::Gradient) && data->fields.contains(EField::SmoothAltitude)) { - double grad = gradient_between(seq, track_dist - consts::gradient_window_behind, track_dist + consts::gradient_window_ahead); - double cap = gradient_between(seq, track_dist - consts::gradient_cap_window_behind, track_dist + consts::gradient_cap_window_ahead); + int dst_m = helper::km_to_m(data->fields.at(EField::Distance)); + double grad = gradient_between(seq_by_dist, dst_m - consts::gradient_window_behind, dst_m + consts::gradient_window_ahead); + double cap = gradient_between(seq_by_dist, dst_m - consts::gradient_cap_window_behind, dst_m + consts::gradient_cap_window_ahead); if (std::abs(grad) > std::abs(cap)) { grad = grad*cap > 0 ? cap : -cap; @@ -94,9 +113,6 @@ void parser::update_calculated_fields(datapoint_seq& seq) data->fields[EField::Gradient] = grad*100;//convert to pct } - - // store last point - last = data; } } @@ -144,23 +160,44 @@ void parser::print_stats(datapoint_seq& seq) log.info("Respiration Rate Min/Avg/Max: {:7.2f} / {:7.2f} / {:7.2f} brpm", respiration.min, respiration.avg, respiration.max); } -double parser::gradient_between(const datapoint_seq& seq, double dist_a, double dist_b) +double parser::gradient_between(const std::multimap& dist_points, int dist_a, int dist_b) { - if (dist_a <= dist_b) { + if (dist_a >= dist_b) { return 0; } - double alt_a = get_by_distance(seq, dist_a, EField::Altitude).back(); - double alt_b = get_by_distance(seq, dist_b, EField::Altitude).front(); + double alt_a = get_by_distance(dist_points, dist_a, EField::SmoothAltitude); + double alt_b = get_by_distance(dist_points, dist_b, EField::SmoothAltitude); - return (alt_b - alt_a)/((dist_b - dist_a)*1000); + double grad = (alt_b - alt_a)/(dist_b - dist_a); + return grad; } -std::vector parser::get_by_distance(const datapoint_seq& seq, double dist, EField field) +double parser::get_by_distance(const std::multimap& dist_points, int dist, EField field, bool last) { std::vector res; - //TODO - return std::move(res); + + double ret = 0; + + if (dist_points.contains(dist)) { + auto [it1, it2] = dist_points.equal_range(dist); + + if (last) { + ret = (--it2)->second->fields.at(field); + } else { + ret = it1->second->fields.at(field); + } + } else { + auto it = dist_points.upper_bound(dist); + + if (it == dist_points.end()) { + ret = (--it)->second->fields.at(field); + } else { + ret = it->second->fields.at(field); + } + } + + return ret; } void parser::set_if_ok(datapoint_ptr& data, EField field, std::optional value) diff --git a/src/telemetry/parser.h b/src/telemetry/parser.h index 9c303fd..a147166 100644 --- a/src/telemetry/parser.h +++ b/src/telemetry/parser.h @@ -6,6 +6,7 @@ #include "utils/logging/logger.h" #include +#include namespace vgraph { namespace telemetry { @@ -26,8 +27,10 @@ class parser { void update_calculated_fields(datapoint_seq& seq); void print_stats(datapoint_seq& seq); - double gradient_between(const datapoint_seq& seq, double dist_a, double dist_b); - std::vector get_by_distance(const datapoint_seq& seq, double dist, EField field); + double gradient_between(const std::multimap& dist_points, int dist_a, int dist_b); + + //last - if there are multiple values for key, return last, otherwise return first + double get_by_distance(const std::multimap& dist_points, int dist, EField field, bool last=true); void set_if_ok(datapoint_ptr& data, EField field, std::optional value); std::optional field_avg(datapoint_seq::const_reverse_iterator it, diff --git a/test/telemetry/fit_parser_test.cpp b/test/telemetry/fit_parser_test.cpp index 55d9cdf..0c90595 100644 --- a/test/telemetry/fit_parser_test.cpp +++ b/test/telemetry/fit_parser_test.cpp @@ -31,24 +31,24 @@ class fit_parser_test : public ::testing::Test { TEST_F(fit_parser_test, parse_incorrect_path_returns_nullptr) { - ASSERT_EQ(nullptr, uut->parse(consts::nonexistant_path)); + ASSERT_EQ(0, uut->parse(consts::nonexistant_path).size()); } TEST_F(fit_parser_test, parse_wrong_file_extension_returns_nullptr) { - ASSERT_EQ(nullptr, uut->parse(consts::gpx_path)); + ASSERT_EQ(0, uut->parse(consts::gpx_path).size()); } TEST_F(fit_parser_test, parse_broken_file_returns_nullptr) { - auto retval = uut->parse(consts::broken_fit_path); - ASSERT_EQ(nullptr, retval); + auto retval = uut->parse(consts::broken_fit_path).size(); + ASSERT_EQ(0, retval); } TEST_F(fit_parser_test, parse_correct_file_returns_sequence) { - auto retval = uut->parse(consts::fit_path); - ASSERT_NE(nullptr, retval); + auto retval = uut->parse(consts::fit_path).size(); + ASSERT_NE(0, retval); } } // namespace telemetry diff --git a/test/testdata/layout.xml b/test/testdata/layout.xml index eb2008e..9d43627 100644 --- a/test/testdata/layout.xml +++ b/test/testdata/layout.xml @@ -14,32 +14,41 @@ - + + + + + - + - + - + + + + + - + +