Skip to content

Commit

Permalink
Complete out loading into TestData
Browse files Browse the repository at this point in the history
  • Loading branch information
PeterJohnson committed Jan 2, 2024
1 parent f505476 commit 5721c8e
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 45 deletions.
177 changes: 137 additions & 40 deletions sysid/src/main/native/cpp/view/DataSelector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,16 @@
#include <wpi/Logger.h>
#include <wpi/StringExtras.h>

#include "sysid/Util.h"
#include "sysid/analysis/AnalysisType.h"
#include "sysid/analysis/Storage.h"
#include "wpi/DataLogReader.h"

using namespace sysid;

static constexpr const char* kAnalysisTypes[] = {"Drivetrain", "Elevator",
"Arm", "Simple"};

static bool EmitEntryTarget(const char* name, bool isString,
const glass::DataLogReaderEntry** entry) {
if (*entry) {
Expand All @@ -36,15 +44,51 @@ static bool EmitEntryTarget(const char* name, bool isString,
}

void DataSelector::Display() {
using namespace std::chrono_literals;

// building test data is modal (due to async access)
if (m_testdataFuture.valid()) {
if (m_testdataFuture.wait_for(0s) == std::future_status::ready) {
TestData data = m_testdataFuture.get();
for (auto&& motordata : data.motorData) {
m_testdataStats.emplace_back(
fmt::format("Test State: {}", motordata.first()));
int i = 0;
for (auto&& run : motordata.second.runs) {
m_testdataStats.emplace_back(fmt::format(
" Run {} samples: {} Volt {} Pos {} Vel", ++i,
run.voltage.size(), run.position.size(), run.velocity.size()));
}
}
if (testdata) {
testdata(std::move(data));
}
}
ImGui::Text("Loading data...");
return;
}

if (!m_testdataStats.empty()) {
for (auto&& line : m_testdataStats) {
ImGui::TextUnformatted(line.c_str());
}
if (ImGui::Button("Ok")) {
m_testdataStats.clear();
}
return;
}

if (EmitEntryTarget("Test State", true, &m_testStateEntry)) {
LoadTests(*m_testStateEntry);
m_testsFuture =
std::async(std::launch::async, [testStateEntry = m_testStateEntry] {
return LoadTests(*testStateEntry);
});
}

if (!m_testStateEntry) {
return;
}

using namespace std::chrono_literals;
if (m_testsFuture.valid() &&
m_testsFuture.wait_for(0s) == std::future_status::ready) {
m_tests = m_testsFuture.get();
Expand All @@ -69,69 +113,122 @@ void DataSelector::Display() {
ImGui::EndCombo();
}

ImGui::Combo("Analysis Type", &m_selectedAnalysis, kAnalysisTypes,
IM_ARRAYSIZE(kAnalysisTypes));

// DND targets
EmitEntryTarget("Velocity", false, &m_velocityEntry);
EmitEntryTarget("Position", false, &m_positionEntry);
EmitEntryTarget("Voltage", false, &m_voltageEntry);

ImGui::SetNextItemWidth(ImGui::GetFontSize() * 7);
ImGui::Combo("Units", &m_selectedUnit, kUnits, IM_ARRAYSIZE(kUnits));

if (!m_selectedTest.empty() && m_velocityEntry && m_positionEntry &&
m_voltageEntry) {
if (ImGui::Button("Load")) {
m_testdataFuture =
std::async(std::launch::async, [this] { return BuildTestData(); });
}
}
}

void DataSelector::Reset() {
m_tests = {};
m_testsFuture = {};
m_tests.clear();
m_selectedTest.clear();
m_testStateEntry = nullptr;
m_velocityEntry = nullptr;
m_positionEntry = nullptr;
m_voltageEntry = nullptr;
m_testdataFuture = {};
}

void DataSelector::LoadTests(const glass::DataLogReaderEntry& testStateEntry) {
m_testsFuture = std::async(std::launch::async, [&] {
Tests rv;
for (auto&& range : testStateEntry.ranges) {
std::string_view prevState;
Runs* curRuns = nullptr;
wpi::log::DataLogReader::iterator lastStart = range.begin();
for (auto it = range.begin(), end = range.end(); it != end; ++it) {
std::string_view testState;
if (it->GetEntry() != testStateEntry.entry ||
!it->GetString(&testState)) {
continue;
}
DataSelector::Tests DataSelector::LoadTests(
const glass::DataLogReaderEntry& testStateEntry) {
Tests tests;
for (auto&& range : testStateEntry.ranges) {
std::string_view prevState;
Runs* curRuns = nullptr;
wpi::log::DataLogReader::iterator lastStart = range.begin();
for (auto it = range.begin(), end = range.end(); it != end; ++it) {
std::string_view testState;
if (it->GetEntry() != testStateEntry.entry ||
!it->GetString(&testState)) {
continue;
}

if (testState == "none") {
continue;
}
if (testState == "none") {
continue;
}

auto [testName, direction] = wpi::rsplit(testState, '-');
auto [testName, direction] = wpi::rsplit(testState, '-');

// track runs as iterator ranges of the same test
if (curRuns && testState != prevState) {
curRuns->emplace_back(lastStart, it);
lastStart = it;
}
auto testIt = rv.find(testName);
if (testIt == rv.end()) {
testIt = rv.emplace(std::string{testName}, State{}).first;
}
auto stateIt = testIt->second.find(testState);
if (stateIt == testIt->second.end()) {
stateIt =
testIt->second.emplace(std::string{testState}, Runs{}).first;
}
curRuns = &stateIt->second;
prevState = testState;
// track runs as iterator ranges of the same test
if (curRuns && testState != prevState) {
curRuns->emplace_back(lastStart, it);
lastStart = it;
}
auto testIt = tests.find(testName);
if (testIt == tests.end()) {
testIt = tests.emplace(std::string{testName}, State{}).first;
}
auto stateIt = testIt->second.find(testState);
if (stateIt == testIt->second.end()) {
stateIt = testIt->second.emplace(std::string{testState}, Runs{}).first;
}
curRuns = &stateIt->second;
prevState = testState;
}

if (curRuns) {
curRuns->emplace_back(lastStart, range.end());
if (curRuns) {
curRuns->emplace_back(lastStart, range.end());
}
}
return tests;
}

template <typename T>
static void AddSample(std::vector<MotorData::Run::Sample<T>>& samples,
const wpi::log::DataLogRecord& record, bool isDouble) {
if (isDouble) {
double val;
if (record.GetDouble(&val)) {
samples.emplace_back(units::second_t{record.GetTimestamp() * 1.0e-6},
T{val});
}
} else {
float val;
if (record.GetFloat(&val)) {
samples.emplace_back(units::second_t{record.GetTimestamp() * 1.0e-6},
T{static_cast<double>(val)});
}
}
}

TestData DataSelector::BuildTestData() {
TestData data;
data.distanceUnit = kUnits[m_selectedUnit];
data.mechanismType = analysis::FromName(kAnalysisTypes[m_selectedAnalysis]);
bool voltageDouble = m_voltageEntry->type == "double";
bool positionDouble = m_positionEntry->type == "double";
bool velocityDouble = m_velocityEntry->type == "double";

for (auto&& state : m_tests[m_selectedTest]) {
auto& motorData = data.motorData[state.first];
for (auto&& range : state.second) {
auto& run = motorData.runs.emplace_back();
for (auto&& record : range) {
if (record.GetEntry() == m_voltageEntry->entry) {
AddSample(run.voltage, record, voltageDouble);
} else if (record.GetEntry() == m_positionEntry->entry) {
AddSample(run.position, record, positionDouble);
} else if (record.GetEntry() == m_velocityEntry->entry) {
AddSample(run.velocity, record, velocityDouble);
}
}
}
return rv;
});
}

return data;
}
19 changes: 16 additions & 3 deletions sysid/src/main/native/include/sysid/view/DataSelector.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#pragma once

#include <functional>
#include <future>
#include <map>
#include <string>
Expand All @@ -13,6 +14,8 @@
#include <glass/support/DataLogReaderThread.h>
#include <wpi/StringMap.h>

#include "sysid/analysis/Storage.h"

namespace glass {
class DataLogReaderEntry;
class Storage;
Expand Down Expand Up @@ -47,19 +50,29 @@ class DataSelector : public glass::View {
*/
void Reset();

private:
void LoadTests(const glass::DataLogReaderEntry& testStateEntry);
/**
* Called when new test data is loaded.
*/
std::function<void(TestData)> testdata;

private:
// wpi::Logger& m_logger;
using Runs = std::vector<glass::DataLogReaderRange>;
using State = std::map<std::string, Runs, std::less<>>; // full name
using Tests = std::map<std::string, State, std::less<>>; // e.g. "dynamic"
std::future<Tests> m_testsFuture;
std::string m_selectedTest;
Tests m_tests;
std::string m_selectedTest;
const glass::DataLogReaderEntry* m_testStateEntry = nullptr;
const glass::DataLogReaderEntry* m_velocityEntry = nullptr;
const glass::DataLogReaderEntry* m_positionEntry = nullptr;
const glass::DataLogReaderEntry* m_voltageEntry = nullptr;
int m_selectedUnit = 0;
int m_selectedAnalysis = 0;
std::future<TestData> m_testdataFuture;
std::vector<std::string> m_testdataStats;

static Tests LoadTests(const glass::DataLogReaderEntry& testStateEntry);
TestData BuildTestData();
};
} // namespace sysid
3 changes: 2 additions & 1 deletion sysid/src/main/native/include/sysid/view/LogLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ class LogLoader : public glass::View {
void Display() override;

/**
* Signal called when the current file is unloaded (invalidates any LogEntry*).
* Signal called when the current file is unloaded (invalidates any
* LogEntry*).
*/
wpi::sig::Signal<> unload;

Expand Down
2 changes: 1 addition & 1 deletion sysid/src/main/native/include/sysid/view/UILayout.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ inline constexpr Vector2d kLeftColSize{

// Left column contents
inline constexpr Vector2d kLogLoaderWindowPos = kLeftColPos;
inline constexpr Vector2d kLogLoaderWindowSize{kLeftColSize.x, 550};
inline constexpr Vector2d kLogLoaderWindowSize{kLeftColSize.x, 500};
inline constexpr Vector2d kDataSelectorWindowPos =
kLogLoaderWindowPos + Vector2d{0, kLogLoaderWindowSize.y + kWindowGap};
inline constexpr Vector2d kDataSelectorWindowSize{
Expand Down

0 comments on commit 5721c8e

Please sign in to comment.