fwt_software/tests/test_thermalstats.cpp

93 lines
3.5 KiB
C++

#include "fgc/ThermalStats.h"
#include <doctest/doctest.h>
#include <vector>
using namespace fgc;
namespace {
// 20.00 degC at the default 0.01 K per count.
constexpr uint16_t kRoom = 29315;
std::vector<uint16_t> flat(uint32_t w, uint32_t h, uint16_t v) {
return std::vector<uint16_t>(static_cast<size_t>(w) * h, v);
}
} // namespace
TEST_CASE("counts convert to tenths of a degree Celsius under either profile") {
CHECK(countsToDeciC(27315, 0.01) == 0); // 273.15 K
CHECK(countsToDeciC(kRoom, 0.01) == 200); // 20.0 degC
CHECK(countsToDeciC(23315, 0.01) == -400); // -40.0 degC, sky
CHECK(countsToDeciC(57315, 0.01) == 3000); // 300.0 degC
// Profile 1 doubles the step: the same temperature is half the count.
CHECK(countsToDeciC(14658, 0.02) == 200); // 293.16 K -> 20.01 rounds to 20.0
CHECK(countsToDeciC(28658, 0.02) == 3000); // 573.16 K
// Rounded to nearest, not truncated: 20.06 degC.
CHECK(countsToDeciC(29321, 0.01) == 201);
CHECK(countsToDeciC(29319, 0.01) == 200); // 20.04 degC
}
TEST_CASE("maximum, median and hot spot of a scene") {
const uint32_t w = 8, h = 4;
auto px = flat(w, h, kRoom);
px[2 * w + 5] = 57315; // 300 degC at (5, 2)
ThermalFrameInfo info;
const auto s = thermalStats(px.data(), w, h, info);
REQUIRE(s.valid);
CHECK(s.valid_pixels == w * h);
CHECK(s.max_count == 57315);
CHECK(s.tmax_decic == 3000);
CHECK(s.median_count == kRoom); // one outlier does not move the median
CHECK(s.tmed_decic == 200);
CHECK(s.hotx == doctest::Approx(5.5 / 8)); // pixel centre, normalised
CHECK(s.hoty == doctest::Approx(2.5 / 4));
}
TEST_CASE("the median is the lower middle value") {
const std::vector<uint16_t> px = {30000, 29000, 31000, 28000}; // sorted 28 29 30 31
const auto s = thermalStats(px.data(), 4, 1, ThermalFrameInfo{});
CHECK(s.median_count == 29000);
}
TEST_CASE("the first occurrence of the maximum is the hot spot") {
auto px = flat(4, 4, kRoom);
px[1 * 4 + 1] = 40000;
px[3 * 4 + 3] = 40000;
const auto s = thermalStats(px.data(), 4, 4, ThermalFrameInfo{});
CHECK(s.hotx == doctest::Approx(1.5 / 4));
CHECK(s.hoty == doctest::Approx(1.5 / 4));
}
TEST_CASE("zero and wrapped counts take no part") {
auto px = flat(4, 2, kRoom);
px[0] = 0; // the camera's start-up frames are zeros
px[1] = 65535; // past the LUT's wrap: not a temperature
px[2] = 65281;
ThermalFrameInfo info;
const auto s = thermalStats(px.data(), 4, 2, info);
REQUIRE(s.valid);
CHECK(s.valid_pixels == 5);
CHECK(s.max_count == kRoom);
// At the limit itself the count still counts.
px[3] = info.valid_count_max;
CHECK(thermalStats(px.data(), 4, 2, info).max_count == info.valid_count_max);
}
TEST_CASE("a frame with no valid pixel has no statistics") {
const auto zeros = flat(4, 4, 0);
CHECK_FALSE(thermalStats(zeros.data(), 4, 4, ThermalFrameInfo{}).valid);
CHECK_FALSE(thermalStats(nullptr, 4, 4, ThermalFrameInfo{}).valid);
CHECK_FALSE(thermalStats(zeros.data(), 0, 4, ThermalFrameInfo{}).valid);
}
TEST_CASE("the scale comes from the frame's profile") {
const auto px = flat(2, 2, 14658); // 20 degC under profile 1
ThermalFrameInfo info;
info.cal = 1;
info.kelvin_per_count = 0.02;
CHECK(thermalStats(px.data(), 2, 2, info).tmed_decic == 200);
}