From patchwork Wed May 6 22:17:57 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: devve X-Patchwork-Id: 26646 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 8F3DABDCB5 for ; Wed, 6 May 2026 22:18:03 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 83A8063022; Thu, 7 May 2026 00:18:02 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="HzP9EGjn"; dkim-atps=neutral Received: from mail-wr1-x429.google.com (mail-wr1-x429.google.com [IPv6:2a00:1450:4864:20::429]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 5CC0E6301A for ; Thu, 7 May 2026 00:18:01 +0200 (CEST) Received: by mail-wr1-x429.google.com with SMTP id ffacd0b85a97d-44c4cc7c1cfso140116f8f.0 for ; Wed, 06 May 2026 15:18:01 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1778105881; x=1778710681; darn=lists.libcamera.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=iuKxaCdLNK1rpp1md2t0A+vNdZQgr0zrLCF8pVdKmlk=; b=HzP9EGjnAb17i0l7vXAKldiFblIqOyPyYvu60lser9iaZhMzRNuKC/LGAVqZx+2cVs 1cqkBTQ0qroMF8OAFmjkgt1hq1m9DKwRYaXZsm3aZGOBzY+eZvnBgXi2SQcWA0EcX7r+ 6a1izcUPUj9hAh05eTmPk1PBH4+IWnQ+xaqsmoVznjLX+VBo9HQGn3lRRfdBgbcyZNQh UbvbtB/wn/i+Tu4EAtVkfR9btjD8+IKeAmEw7Ol/eGGnjvBvs00iKC9Xkk3hqcBOEU9Y TKQJGBXEmeZ/I+3PKKnb9TorAQWBe3o7j+1Ahz5R6jmcHvqwyaw0TscsKjOF/sTiCcr0 nu2w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1778105881; x=1778710681; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=iuKxaCdLNK1rpp1md2t0A+vNdZQgr0zrLCF8pVdKmlk=; b=BwxVJ1dXynKAPhCnKbKeoT8Xwtbng/WSuVNhGH2qAtIAAwuN8rVXTjG8qwqfUEMPBG P5fEcZj/UdUxB9kVySp8yvy8u1vda4FFXTXzcW+g7aQ+0X0v3RTwrElHFSeq3eEeHKgZ gIx+WsGK/t6EjNv9a2EgWD67GR1sCrDEoPKTP92exjayW8Ds1LxO3ju5Yp7kk3RPGg1Q 6TCIflBUOR8RBhflfzVGoeFIEvKujOo8EOAW6XL+g8BBLI91IN2diOL2hvcsY7hIOx8G qf1UoIiu/+RY76cF0/AJX7td+E6FSruB5BanqJGoiCtbXOcX8TyfGBwXKIKn0cYzn1XZ yPyg== X-Gm-Message-State: AOJu0YwHXJnzaz1jSEHB7AAe0i/xhhHCe94QHeVoIFrHcWn+0XnadbMa frR489RMzDm+CFcfoAiwqiX2AuvFTwpqBYRr0FspK6Nz/d+7/wi19d9ot/gZZQ== X-Gm-Gg: AeBDievA5oBINVlTNmAT6Bpxm1nZtjBZ0blHwK/+GwRyOAJJl4YoWDZVdXz53LEaKQn qgBAgHH08joD+UcGLx/F0VwfVe92U3YRai+De/YEz3bTQE9kvp0vBsLjDAcWT2TuLhar4bf9Xq1 whYwAypM23dBA0WoFnbIVb3z7TF8Be58V8XdiER2u8zwU4O1jakmOhmlDfqdIxDVWUg6GI9eot4 Zb1rspUlnskIBVjedliVQ98AsqD1JYGIpexNg6S1gFssy4HEdzi62otW/xGOwn/re7K9zCuz2lj ee4WGW158YZHvycQN7v8adXNxUGMr6nsj2eYmyoIrnRGN7Ex1gYnGMXm8+wLsoklJmWt49qMtfm J2BnP9zCvm7fLfqnlnp8H0B5+zc4kgIAmNb0r8e6j/nstmQyNj3vuGbpkfAsMpODN5sso1HFqUW n8Z77e+5+26SIhvhICriRaQ68zaSHfqxW5Gm996gRQzk1CTSPoD+rJUkl7lor0IebFMEHWsbg8G R+ol6g+/Gl3H6o97TyVuZctVMAEAHer X-Received: by 2002:a05:6000:611:b0:44a:247e:67b4 with SMTP id ffacd0b85a97d-4515b9f31camr9070930f8f.18.1778105880763; Wed, 06 May 2026 15:18:00 -0700 (PDT) Received: from dexps.speedport.ip (p200300eda74453cf3cf3f7929e513b94.dip0.t-ipconnect.de. [2003:ed:a744:53cf:3cf3:f792:9e51:3b94]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-450524833e1sm15709388f8f.2.2026.05.06.15.18.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 06 May 2026 15:18:00 -0700 (PDT) From: d3vv3 To: libcamera-devel@lists.libcamera.org Cc: d3vv3 Subject: [PATCH v2] test: ipa: libipa: Add CCM row-sum validation test Date: Thu, 7 May 2026 00:17:57 +0200 Message-ID: <20260506221758.880117-1-devve.3@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260501191400.985920-3-devve.3@gmail.com> References: <20260501191400.985920-3-devve.3@gmail.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Verify that each row of a colour correction matrix sums to 1.0 (luminance preservation property). Tests cover: - identity and known-bad inline matrices - a real OV01A10 D65 calibrated CCM - parsing a multi-entry CCM YAML table via Interpolator - detection of a bad entry in YAML Tolerance is 5e-4 to accommodate 4-decimal-place rounding in tuning files. Signed-off-by: d3vv3 --- test/ipa/libipa/ccm.cpp | 158 ++++++++++++++++++++++++++++++++++++ test/ipa/libipa/meson.build | 1 + 2 files changed, 159 insertions(+) create mode 100644 test/ipa/libipa/ccm.cpp diff --git a/test/ipa/libipa/ccm.cpp b/test/ipa/libipa/ccm.cpp new file mode 100644 index 00000000..efc0035a --- /dev/null +++ b/test/ipa/libipa/ccm.cpp @@ -0,0 +1,158 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2024-2026, Red Hat Inc. + * + * CCM matrix row-sum validation tests + * + * Each row of a colour correction matrix must sum to 1.0 (luminance + * preservation). This test verifies that property for inline matrix data + * and for matrices parsed from a YAML CCM table. + */ + +#include "../../../src/ipa/libipa/interpolator.h" + +#include +#include +#include +#include + +#include "libcamera/base/file.h" +#include "libcamera/internal/matrix.h" +#include "libcamera/internal/yaml_parser.h" + +#include "test.h" + +using namespace std; +using namespace libcamera; +using namespace ipa; + +/* Tolerance for floating-point row-sum comparison. + * CCM values in tuning files are typically given to 4 decimal places, + * which can introduce up to ~0.5e-3 rounding error per row. */ +static constexpr float kRowSumTolerance = 5e-4f; + +#define ASSERT_TRUE(cond) \ + do { \ + if (!(cond)) { \ + cerr << "FAIL: " #cond "\n"; \ + return TestFail; \ + } \ + } while (0) + +static bool allRowsSumToOne(const Matrix &m) +{ + for (unsigned int row = 0; row < 3; row++) { + float sum = 0.0f; + for (unsigned int col = 0; col < 3; col++) + sum += m[row][col]; + if (std::abs(sum - 1.0f) > kRowSumTolerance) + return false; + } + return true; +} + +class CcmRowSumTest : public Test +{ +protected: + bool writeTempYaml(const std::string &content, std::string &filename) + { + filename = "/tmp/libcamera.ccm.test.XXXXXX"; + int fd = mkstemp(&filename.front()); + if (fd == -1) + return false; + ssize_t ret = write(fd, content.c_str(), content.size()); + close(fd); + return ret == static_cast(content.size()); + } + + std::unique_ptr parseYaml(const std::string &content) + { + std::string filename; + if (!writeTempYaml(content, filename)) + return nullptr; + + File file{ filename }; + if (!file.open(File::OpenModeFlag::ReadOnly)) + return nullptr; + + auto root = YamlParser::parse(file); + unlink(filename.c_str()); + return root; + } + + int run() + { + /* --- 1. Known-good identity matrix --- */ + Matrix identity{ { 1, 0, 0, + 0, 1, 0, + 0, 0, 1 } }; + ASSERT_TRUE(allRowsSumToOne(identity)); + + /* --- 2. Known-bad matrix (rows do not sum to 1) --- */ + Matrix bad{ { 2, 0, 0, + 0, 1, 0, + 0, 0, 1 } }; + ASSERT_TRUE(!allRowsSumToOne(bad)); + + /* --- 3. Typical calibrated CCM (D65, OV01A10) --- */ + Matrix d65{ { 1.8163f, -0.7062f, -0.1100f, + -0.1640f, 1.5736f, -0.4096f, + -0.0084f, -0.8294f, 1.8378f } }; + ASSERT_TRUE(allRowsSumToOne(d65)); + + /* --- 4. Parse a valid CCM table from YAML and validate --- */ + const std::string validYaml = + "- ct: 2856\n" + " ccm: [ 1.1248, 0.2210, -0.3458,\n" + " -0.4616, 1.7736, -0.3120,\n" + " -0.4342, -0.9348, 2.3690 ]\n" + "- ct: 6500\n" + " ccm: [ 1.8163, -0.7062, -0.1100,\n" + " -0.1640, 1.5736, -0.4096,\n" + " -0.0084, -0.8294, 1.8378 ]\n" + "- ct: 7500\n" + " ccm: [ 1.8953, -0.7980, -0.0973,\n" + " -0.1539, 1.6001, -0.4462,\n" + " -0.0101, -0.7800, 1.7902 ]\n"; + + auto root = parseYaml(validYaml); + ASSERT_TRUE(root); + + Interpolator> interp; + ASSERT_TRUE(interp.readYaml(*root, "ct", "ccm") == 0); + ASSERT_TRUE(interp.data().size() == 3); + + for (const auto &[ct, m] : interp.data()) { + if (!allRowsSumToOne(m)) { + cerr << "CCM at ct=" << ct + << " has a row that does not sum to 1.0\n"; + return TestFail; + } + } + + /* --- 5. Detect a bad entry in YAML --- */ + const std::string badYaml = + "- ct: 5000\n" + " ccm: [ 2.0000, -0.7062, -0.1100,\n" + " -0.1640, 1.5736, -0.4096,\n" + " -0.0084, -0.8294, 1.8378 ]\n"; + + auto badRoot = parseYaml(badYaml); + ASSERT_TRUE(badRoot); + + Interpolator> badInterp; + ASSERT_TRUE(badInterp.readYaml(*badRoot, "ct", "ccm") == 0); + + for (const auto &[ct, m] : badInterp.data()) { + if (allRowsSumToOne(m)) { + cerr << "Expected bad CCM at ct=" << ct + << " to fail row-sum check, but it passed\n"; + return TestFail; + } + } + + return TestPass; + } +}; + +TEST_REGISTER(CcmRowSumTest) diff --git a/test/ipa/libipa/meson.build b/test/ipa/libipa/meson.build index c3e25587..8c36800c 100644 --- a/test/ipa/libipa/meson.build +++ b/test/ipa/libipa/meson.build @@ -1,6 +1,7 @@ # SPDX-License-Identifier: CC0-1.0 libipa_test = [ + {'name': 'ccm', 'sources': ['ccm.cpp']}, {'name': 'fixedpoint', 'sources': ['fixedpoint.cpp']}, {'name': 'histogram', 'sources': ['histogram.cpp']}, {'name': 'interpolator', 'sources': ['interpolator.cpp']},