From patchwork Wed May 6 23:07:22 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: devve X-Patchwork-Id: 26667 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 D6835C3307 for ; Wed, 6 May 2026 23:07:44 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7C3E4630BB; Thu, 7 May 2026 01:07:42 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="VyO39wYf"; dkim-atps=neutral Received: from mail-ua1-x92a.google.com (mail-ua1-x92a.google.com [IPv6:2607:f8b0:4864:20::92a]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 924D86327F for ; Thu, 7 May 2026 01:07:39 +0200 (CEST) Received: by mail-ua1-x92a.google.com with SMTP id a1e0cc1a2514c-95d0476492bso114819241.2 for ; Wed, 06 May 2026 16:07:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1778108858; x=1778713658; darn=lists.libcamera.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:from:to:cc:subject:date:message-id :reply-to; bh=iuKxaCdLNK1rpp1md2t0A+vNdZQgr0zrLCF8pVdKmlk=; b=VyO39wYfwsXUXygdkXIOKx8swGbqoFhkVBFnAb1whZT24zxp9DZ3SGik45/8/GNFGp +LTgY/Ay8+zsPkggU7mi4igVditmdnJSLJvEna2x8Ijg63y/eqWCeAzaLXaes5DxRzTG kYZxRhfKjxLz4mr7Y3PAYhLiWgeUKqmDdB3UbKj2BrTq22y/WaNPtAVge/nliY/fFb+a c8LfAHhkaAB890P5eqkdfOS//iMqSGeRyclPF2QEZoLBzvim+NuB+2u7qREMXno2Kemc DihM/vdyjstLnU43Cg2un6GCIMeXqcNdlhvrVUGlDN4kd5UHxc+/9le7zZizfWRsAM+7 BKGQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1778108858; x=1778713658; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=iuKxaCdLNK1rpp1md2t0A+vNdZQgr0zrLCF8pVdKmlk=; b=VgcUjX4yJnqiRmEZaiR/nw+1DUJRpPkqcfw0rFxYgXu6yd5kipNNTC5pNydpVyHmik bB1hGgrNTm+I0bcSR7x+Y4wJ7OFlA/p7I1BqdAK27OvikImnMlJHo7QUHse/BngXsbRW uY8wDKilV9a1jKKGYoPyFZs5OmZT8lXGBqUZ2VpzNC8DXIBMmGXh43nbR297Dx/eH2ld RsSnJC48poWquav9Pqq2l2OHaKyuwufAqBSxRt75nsbbS4XWztupFnWZJixVD0gMijQI dSSB6lqjCZEWufR7CQqqmfKpskSrb49oQLBCvTBmV2DEIo2ZU3ngXF0gRlu6ePpYs48I XHpw== X-Gm-Message-State: AOJu0YyXczirCMvcnpOkC/p2vCK4YmJ537UQz7xwz8D+KQ3BysmBxm0u ILtl+iNMffHmjn2VnHfmwlwNkdH8nKCLtJ56L8fIzpo2+DFN4MOLR7RhbmXGtA== X-Gm-Gg: AeBDiev6aRTpCityw56Wclc0x5wf8K3Z12Qqd1A6xl9OdSjFPbD/PtJML0/nSjzSsHo M5EnDQmucOlphL7bh7RV0wSGJP+8rtwMz2HwFEvl6nyh6ZAheFU/1XSpiBME9RgSeOAc17Qoczk f0meS79So/VLzqTEW4Efhyt+PxwMjmpiKlzWTFxHGqNDmavOIOx8iSQ+Y2Las+X7gdgx+NxudSv WFbAv40rtvVeCDG8kBWFCr6e0aljLZ6VzT8csJcqkFKYdFvR0X0x6xVrWcTMaDNhp7x9Imssa9O snk2vOXGipWMbK/tQegtF+cfSmn17HRY+n3YNSJn6ne5D8DcDJ58FMkDdU+J+LCa3ZmWx9wtWFx +zdRI6WNcLQUhzH2HKAdRewdxToW1EJu91YaN6m07f27uVfBA5Xxl/3flxwivg/fvHwEfmvYLtM cBfZizksgH99Udj4ILyaOJv/VN8ixXJZHlxcvs8gmWYaNI55lygGUNTWF2lubcM8E153p1agu5I fVO9wNAfECk34qbNRKGd92oU7U+RA4CZFlstJ/Zl9g= X-Received: by 2002:a67:e102:0:b0:628:5313:11a with SMTP id ada2fe7eead31-630f90ecf9bmr2955890137.28.1778108858210; Wed, 06 May 2026 16:07:38 -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 af79cd13be357-8fc2c25324esm2035266385a.23.2026.05.06.16.07.37 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 06 May 2026 16:07:37 -0700 (PDT) From: d3vv3 To: libcamera-devel@lists.libcamera.org Subject: [PATCH v2 10/10] test: ipa: libipa: Add CCM row-sum validation test Date: Thu, 7 May 2026 01:07:22 +0200 Message-ID: <20260506230722.1041596-11-devve.3@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260506230722.1041596-1-devve.3@gmail.com> References: <20260506230722.1041596-1-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']},