fwt_software/tests/test_rawimage.cpp

134 lines
5.3 KiB
C++

#include "fgc/RawImage.h"
#include <doctest/doctest.h>
#include <random>
#include <vector>
using namespace fgc;
namespace {
// Pack 12-bit values the way the camera does (GenICam 12p).
std::vector<uint8_t> pack12p(const std::vector<uint16_t>& v) {
std::vector<uint8_t> out((v.size() * 3 + 1) / 2);
for (size_t i = 0, o = 0; i < v.size(); i += 2, o += 3) {
const uint16_t p0 = v[i];
const uint16_t p1 = i + 1 < v.size() ? v[i + 1] : 0;
out[o] = static_cast<uint8_t>(p0 & 0xFF);
out[o + 1] = static_cast<uint8_t>(((p0 >> 8) & 0x0F) | ((p1 & 0x0F) << 4));
if (o + 2 < out.size()) out[o + 2] = static_cast<uint8_t>(p1 >> 4);
}
return out;
}
} // namespace
TEST_CASE("layout names round-trip, and unknown formats are refused") {
for (PixelLayout l : {PixelLayout::Mono8, PixelLayout::RGB8, PixelLayout::BayerRG8,
PixelLayout::BayerRG12p})
CHECK(layoutFromName(layoutName(l)) == l);
CHECK(layoutFromName("BayerRG12") == PixelLayout::Unknown); // unpacked: not handled
CHECK(layoutFromName("YCbCr422_8_CbYCrY") == PixelLayout::Unknown);
CHECK(std::string(layoutName(PixelLayout::Unknown)) == "Unknown");
}
TEST_CASE("layout properties") {
CHECK(layoutIsBayer(PixelLayout::BayerRG8));
CHECK(layoutIsBayer(PixelLayout::BayerRG12p));
CHECK_FALSE(layoutIsBayer(PixelLayout::RGB8));
CHECK(layoutBits(PixelLayout::BayerRG12p) == 12);
CHECK(layoutBits(PixelLayout::RGB8) == 8);
CHECK(layoutBits(PixelLayout::Unknown) == 0);
// The 1620c's full frame, as measured: 24.3 / 16.2 / 48.6 MB.
CHECK(layoutFrameBytes(PixelLayout::BayerRG12p, 5328, 3040) == 24295680);
CHECK(layoutFrameBytes(PixelLayout::BayerRG8, 5328, 3040) == 16197120);
CHECK(layoutFrameBytes(PixelLayout::RGB8, 5328, 3040) == 48591360);
CHECK(layoutFrameBytes(PixelLayout::BayerRG12p, 3, 1) == 5); // odd pixel count rounds up
}
TEST_CASE("unpack12p matches GenICam 12p, by hand") {
// p0 = 0xABC, p1 = 0x123: bytes BC, 3A, 12.
const uint8_t in[3] = {0xBC, 0x3A, 0x12};
uint16_t out[2] = {};
unpack12p(in, out, 2);
CHECK(out[0] == 0xABC);
CHECK(out[1] == 0x123);
}
TEST_CASE("unpack12p round-trips every value, odd counts included") {
std::mt19937 rng(12);
std::vector<uint16_t> src(1001);
for (auto& v : src) v = static_cast<uint16_t>(rng() % 4096);
src[0] = 0;
src[1] = 4095;
for (size_t n : {size_t(1000), size_t(1001)}) {
CAPTURE(n);
std::vector<uint16_t> part(src.begin(), src.begin() + n);
const auto packed = pack12p(part);
std::vector<uint16_t> out(n, 0xFFFF);
unpack12p(packed.data(), out.data(), n);
CHECK(out == part);
}
}
TEST_CASE("brightness sample: 8-bit layouts") {
const uint32_t w = 400, h = 300;
SUBCASE("mid grey") {
std::vector<uint8_t> f(w * h, 100);
auto s = sampleBrightness(f.data(), f.size(), PixelLayout::BayerRG8, w, h);
REQUIRE(s.valid());
CHECK(s.mean == doctest::Approx(100));
CHECK(s.clipped == 0);
}
SUBCASE("half blown out, RGB8 mixes the channels") {
std::vector<uint8_t> f(w * h * 3, 20);
for (size_t i = 0; i < f.size() / 2; ++i) f[i] = 255;
auto s = sampleBrightness(f.data(), f.size(), PixelLayout::RGB8, w, h);
REQUIRE(s.valid());
CHECK(s.clipped == doctest::Approx(0.5).epsilon(0.02));
CHECK(s.mean == doctest::Approx((255 + 20) / 2.0).epsilon(0.02));
}
}
TEST_CASE("brightness sample: 12-bit is reported on the 0..255 scale") {
const uint32_t w = 400, h = 300;
std::vector<uint16_t> v(w * h, 1600); // 1600 / 16 = 100
auto f = pack12p(v);
auto s = sampleBrightness(f.data(), f.size(), PixelLayout::BayerRG12p, w, h);
REQUIRE(s.valid());
CHECK(s.mean == doctest::Approx(100));
CHECK(s.clipped == 0);
std::fill(v.begin(), v.end(), 4095);
f = pack12p(v);
s = sampleBrightness(f.data(), f.size(), PixelLayout::BayerRG12p, w, h);
CHECK(s.mean == doctest::Approx(4095 / 16.0));
CHECK(s.clipped == 1);
}
TEST_CASE("brightness sample refuses what it cannot read") {
std::vector<uint8_t> f(100, 50);
CHECK_FALSE(sampleBrightness(f.data(), f.size(), PixelLayout::Unknown, 10, 10).valid());
CHECK_FALSE(sampleBrightness(f.data(), f.size(), PixelLayout::RGB8, 10, 10).valid()); // too short
CHECK_FALSE(sampleBrightness(nullptr, 0, PixelLayout::Mono8, 10, 10).valid());
CHECK(sampleBrightness(f.data(), f.size(), PixelLayout::Mono8, 10, 10).valid());
}
#include "fgc/mock/MockCameraSource.h"
TEST_CASE("the mock camera delivers raw frames like a raw-capture profile") {
for (PixelLayout l : {PixelLayout::RGB8, PixelLayout::BayerRG8, PixelLayout::BayerRG12p}) {
CAPTURE(layoutName(l));
MockCameraSource cam(1, 64, 48, l);
Frame got;
cam.setFrameCallback([&got](const Frame& f) { got = f; });
REQUIRE(cam.trigger());
CHECK(got.layout == l);
CHECK(got.channels == (l == PixelLayout::RGB8 ? 3 : 1));
CHECK(got.data.size() == layoutFrameBytes(l, 64, 48));
REQUIRE(got.brightness.valid());
// The scene's mid grey (110) whatever the layout and bit depth.
CHECK(got.brightness.mean == doctest::Approx(110).epsilon(0.15));
}
}