93 lines
3.5 KiB
C++
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);
|
|
}
|