#include "core/ir_frame.hpp" #include #include #include namespace { void test_parse_valid_frame() { // 2x1 frame: marker + counter + len + type + shutter + 2 pixels + trailing marker + tail std::vector data(0x38 + 4, 0xaa); auto w32 = [&](size_t off, uint32_t v) { data[off] = static_cast(v); data[off + 1] = static_cast(v >> 8); data[off + 2] = static_cast(v >> 16); data[off + 3] = static_cast(v >> 24); }; w32(0x00, 0x1bb1b11b); w32(0x04, 42); w32(0x08, 4); // data_length w32(0x0c, 1); // raw frame type w32(0x10, 1000); // shutter data[0x1c] = 0x34; data[0x1d] = 0x12; data[0x1e] = 0x78; data[0x1f] = 0x56; w32(0x1c + 4, 0x1bb1b11c); mag160c::core::IrFrameHeader h{}; const uint16_t* pixels = nullptr; assert(mag160c::core::parse_ir_frame(data.data(), data.size(), &h, &pixels) == MAG160C_OK); assert(h.marker == 0x1bb1b11b); assert(h.frame_counter == 42); assert(h.data_length == 4); assert(h.frame_type == 1); assert(h.period_shutter == 1000); assert(pixels[0] == 0x1234); assert(pixels[1] == 0x5678); } void test_parse_rejects_bad_marker() { std::vector data(0x40, 0); data[0] = 0xab; mag160c::core::IrFrameHeader h{}; const uint16_t* pixels = nullptr; assert(mag160c::core::parse_ir_frame(data.data(), data.size(), &h, &pixels) == MAG160C_ERR_PROTOCOL_UNKNOWN); } void test_parse_rejects_bad_type_and_trailer() { std::vector data(0x40, 0); auto w32 = [&](size_t off, uint32_t v) { data[off] = static_cast(v); data[off + 1] = static_cast(v >> 8); data[off + 2] = static_cast(v >> 16); data[off + 3] = static_cast(v >> 24); }; w32(0x00, 0x1bb1b11b); w32(0x08, 4); w32(0x0c, 2); // invalid type mag160c::core::IrFrameHeader h{}; const uint16_t* pixels = nullptr; assert(mag160c::core::parse_ir_frame(data.data(), data.size(), &h, &pixels) == MAG160C_ERR_PROTOCOL_UNKNOWN); w32(0x0c, 0); w32(0x1c + 4, 0xdeadbeef); // bad trailing marker assert(mag160c::core::parse_ir_frame(data.data(), data.size(), &h, &pixels) == MAG160C_ERR_PROTOCOL_UNKNOWN); } void test_calibrate_flat_band() { // 4 pixels, 2 bands (1 search threshold), threshold = {5} per pixel const std::vector frame{1000, 2000, 3000, 4000}; const std::vector thresholds{5, 5, 5, 5}; // per band per pixel {coeff, offset}: band0 = {0, 100}, band1 = {0, 200} const std::vector coeff{ 0, 100, 0, 100, 0, 100, 0, 100, 0, 200, 0, 200, 0, 200, 0, 200, }; mag160c::core::IrCalibrationTables tables{}; tables.thresholds = thresholds.data(); tables.coeff = coeff.data(); tables.pixel_count = 4; tables.band_count = 2; tables.baseline = nullptr; tables.has_baseline = false; std::vector out(4); mag160c::core::calibrate_frame(frame.data(), tables, out.data()); // diff = frame >> 1 = 500..2000, all > 5 → band 1 → offset 200 for (auto v : out) { assert(v == 200); } } void test_calibrate_interpolation_and_clamp() { // 2 pixels, 2 bands, threshold = {500} const std::vector frame{2000, 0x8000}; const std::vector baseline{0, 0x100}; const std::vector thresholds{500, 500}; // band0: {coeff=0x1000 (4096), offset=1000}; band1: {coeff=0x100, offset=2000} const std::vector coeff{ 0x1000, 1000, 0x1000, 1000, 0x0100, 2000, 0x0100, 2000, }; mag160c::core::IrCalibrationTables tables{}; tables.thresholds = thresholds.data(); tables.coeff = coeff.data(); tables.pixel_count = 2; tables.band_count = 2; tables.baseline = baseline.data(); tables.has_baseline = true; std::vector out(2); mag160c::core::calibrate_frame(frame.data(), tables, out.data()); // pixel 0: diff = (2000-0)>>1 = 1000 > 500 → band1: 2000 + (1000*0x100 >> 12) = 2000 + 62 assert(out[0] == 2062); // pixel 1: diff = (0x8000-0x100)>>1 = 0x3f80 (16256) > 500 → band1: 2000 + (16256*256 >> 12) // = 2000 + 1016 = 3016 assert(out[1] == 3016); } void test_calibrate_negative_diff_zero() { const std::vector frame{100}; const std::vector baseline{200}; const std::vector thresholds{5}; const std::vector coeff{0x1000, 1000, 0x1000, 1000}; mag160c::core::IrCalibrationTables tables{}; tables.thresholds = thresholds.data(); tables.coeff = coeff.data(); tables.pixel_count = 1; tables.band_count = 2; tables.baseline = baseline.data(); tables.has_baseline = true; std::vector out(1); mag160c::core::calibrate_frame(frame.data(), tables, out.data()); // diff = (int16)(100-200) >> 1 = -50; negative coeff*offset path clamps to >= 0: // band0 (diff <= 5): 1000 + (-50 * 4096 >> 12) = 1000 - 50 = 950 assert(out[0] == 950); } void test_parse_rejects_truncated_frame() { std::vector data(0x38 + 4, 0); auto w32 = [&](size_t off, uint32_t v) { data[off] = static_cast(v); data[off + 1] = static_cast(v >> 8); data[off + 2] = static_cast(v >> 16); data[off + 3] = static_cast(v >> 24); }; w32(0x00, 0x1bb1b11b); w32(0x08, 4); mag160c::core::IrFrameHeader h{}; const uint16_t* pixels = nullptr; data.resize(data.size() - 1); // truncate assert(mag160c::core::parse_ir_frame(data.data(), data.size(), &h, &pixels) == MAG160C_ERR_PROTOCOL_UNKNOWN); } } // namespace int main() { test_parse_valid_frame(); test_parse_rejects_bad_marker(); test_parse_rejects_bad_type_and_trailer(); test_parse_rejects_truncated_frame(); test_calibrate_flat_band(); test_calibrate_interpolation_and_clamp(); test_calibrate_negative_diff_zero(); return 0; }