fwt_software/tests/test_settlerule.cpp

139 lines
4.6 KiB
C++

#include "fgc/SettleRule.h"
#include <doctest/doctest.h>
#include <algorithm>
#include <cmath>
#include <fstream>
#include <map>
#include <sstream>
#include <string>
#include <vector>
using namespace fgc;
namespace {
FrameStats frame(double b, double e = 20000, double g = 15) {
static uint64_t seq = 0;
return FrameStats{++seq, b, e, g};
}
struct Row {
double t, exposure, gain, brightness;
};
// The 24 auto-exposure steps recorded on towers 121 and 111 (tests/data/ae_steps.csv).
std::map<int, std::vector<Row>> recordedSteps() {
std::map<int, std::vector<Row>> runs;
std::ifstream f(std::string(FGC_SOURCE_DIR) + "/tests/data/ae_steps.csv");
std::string line;
while (std::getline(f, line)) {
if (line.empty() || line[0] == '#') continue;
std::stringstream ss(line);
std::string field;
std::vector<std::string> v;
while (std::getline(ss, field, ',')) v.push_back(field);
if (v.size() != 8) continue;
runs[std::stoi(v[0])].push_back({std::stod(v[4]), std::stod(v[5]), std::stod(v[6]),
std::stod(v[7])});
}
return runs;
}
} // namespace
TEST_CASE("settled: in the camera's band and steady for 3 frames") {
SettleRule r; // band 50 +- 5 % of 255 = 114.75 .. 140.25
CHECK(r.add(frame(120)) == SettleRule::Verdict::Waiting);
CHECK(r.add(frame(121)) == SettleRule::Verdict::Waiting);
CHECK(r.add(frame(121.5)) == SettleRule::Verdict::Settled);
}
TEST_CASE("not settled while brightness is still moving through the band") {
SettleRule r;
r.add(frame(139));
r.add(frame(131)); // -6%: still coming down
CHECK(r.add(frame(124)) == SettleRule::Verdict::Waiting);
CHECK(r.add(frame(122)) == SettleRule::Verdict::Waiting); // 124 -> 122 steady, 131 -> 124 not
CHECK(r.add(frame(122)) == SettleRule::Verdict::Settled);
}
TEST_CASE("a pause outside the band is not settled (auto-exposure stalls mid-way)") {
SettleRule r;
for (int i = 0; i < 10; ++i) CHECK(r.add(frame(157, 20000, 19.6)) == SettleRule::Verdict::Waiting);
}
TEST_CASE("steady but too dark at full exposure and gain: waiting cannot help") {
SettleRule::Params p;
p.exposure_max_us = 20000;
p.gain_max_db = 24;
SettleRule r(p);
r.add(frame(40, 20000, 24));
r.add(frame(40.2, 20000, 24));
CHECK(r.add(frame(40.1, 20000, 24)) == SettleRule::Verdict::AtLimit);
// Below the limits it is just slow, not stuck.
SettleRule s(p);
s.add(frame(40, 20000, 12));
s.add(frame(40, 20000, 12));
CHECK(s.add(frame(40, 20000, 12)) == SettleRule::Verdict::Waiting);
}
TEST_CASE("steady but too bright at minimum exposure and gain") {
SettleRule::Params p;
p.exposure_min_us = 82;
p.gain_min_db = 0;
SettleRule r(p);
r.add(frame(200, 82, 0));
r.add(frame(200, 82, 0));
CHECK(r.add(frame(200, 82, 0)) == SettleRule::Verdict::AtLimit);
}
TEST_CASE("a frame without brightness starts the count again") {
SettleRule r;
r.add(frame(120));
r.add(frame(120));
CHECK(r.add(frame(-1)) == SettleRule::Verdict::Waiting);
CHECK(r.add(frame(120)) == SettleRule::Verdict::Waiting);
CHECK(r.add(frame(120)) == SettleRule::Verdict::Waiting);
CHECK(r.add(frame(120)) == SettleRule::Verdict::Settled);
}
TEST_CASE("the band follows the camera's target and tolerance") {
SettleRule::Params p;
p.target_pct = 40;
p.tolerance_pct = 10;
SettleRule r(p);
CHECK(r.bandLow() == doctest::Approx(76.5));
CHECK(r.bandHigh() == doctest::Approx(127.5));
}
TEST_CASE("replayed on the 24 recorded exposure steps, it keeps a good frame at least 21 times") {
const auto runs = recordedSteps();
REQUIRE(runs.size() == 24);
int correct = 0, early = 0, never = 0;
for (const auto& [id, rows] : runs) {
// Where the run finally settles: median brightness over its last 2 s.
std::vector<double> tail;
for (const auto& r : rows)
if (r.t >= rows.back().t - 2.0) tail.push_back(r.brightness);
std::sort(tail.begin(), tail.end());
const double final_level = tail[tail.size() / 2];
SettleRule rule;
bool fired = false;
for (const auto& r : rows) {
if (rule.add(FrameStats{0, r.brightness, r.exposure, r.gain}) !=
SettleRule::Verdict::Waiting) {
fired = true;
(std::fabs(r.brightness / final_level - 1.0) <= 0.10 ? correct : early)++;
break;
}
}
never += !fired;
}
INFO("correct " << correct << ", early " << early << ", never " << never);
CHECK(correct >= 21);
CHECK(never == 0);
}