| Message ID | 20260506230722.1041596-11-devve.3@gmail.com |
|---|---|
| State | New |
| Headers | show |
| Series |
|
| Related | show |
d3vv3 <devve.3@gmail.com> writes: > 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<Matrix> > - detection of a bad entry in YAML I'm not sure I get the purpose of the test -- it looks like it tests mostly stuff defined in the test itself. What libcamera functionality related to row sums is it supposed to test? > Tolerance is 5e-4 to accommodate 4-decimal-place rounding in tuning files. > > Signed-off-by: d3vv3 <devve.3@gmail.com> > --- > 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 <cmath> > +#include <iostream> > +#include <string> > +#include <unistd.h> > + > +#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<float, 3, 3> &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<ssize_t>(content.size()); > + } > + > + std::unique_ptr<ValueNode> 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<float, 3, 3> 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<float, 3, 3> bad{ { 2, 0, 0, > + 0, 1, 0, > + 0, 0, 1 } }; > + ASSERT_TRUE(!allRowsSumToOne(bad)); > + > + /* --- 3. Typical calibrated CCM (D65, OV01A10) --- */ > + Matrix<float, 3, 3> 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<Matrix<float, 3, 3>> 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<Matrix<float, 3, 3>> 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']},
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 <cmath> +#include <iostream> +#include <string> +#include <unistd.h> + +#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<float, 3, 3> &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<ssize_t>(content.size()); + } + + std::unique_ptr<ValueNode> 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<float, 3, 3> 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<float, 3, 3> bad{ { 2, 0, 0, + 0, 1, 0, + 0, 0, 1 } }; + ASSERT_TRUE(!allRowsSumToOne(bad)); + + /* --- 3. Typical calibrated CCM (D65, OV01A10) --- */ + Matrix<float, 3, 3> 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<Matrix<float, 3, 3>> 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<Matrix<float, 3, 3>> 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']},
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<Matrix> - detection of a bad entry in YAML Tolerance is 5e-4 to accommodate 4-decimal-place rounding in tuning files. Signed-off-by: d3vv3 <devve.3@gmail.com> --- 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