#include "fgc/ThermalStats.h" #include #include using namespace fgc; namespace { // 20.00 degC at the default 0.01 K per count. constexpr uint16_t kRoom = 29315; std::vector flat(uint32_t w, uint32_t h, uint16_t v) { return std::vector(static_cast(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 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); }