From patchwork Fri Apr 26 07:36:09 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 19956 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 5FECAC32A2 for ; Fri, 26 Apr 2024 07:36:29 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 647916340D; Fri, 26 Apr 2024 09:36:28 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="onqdqfw8"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 9225A633F4 for ; Fri, 26 Apr 2024 09:36:25 +0200 (CEST) Received: from pyrite.hamster-moth.ts.net (h175-177-049-156.catv02.itscom.jp [175.177.49.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 59D9B66B; Fri, 26 Apr 2024 09:35:31 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1714116932; bh=e/TrmoAkx77DdxTWVebeHf0ezflvviv2l75xERVY1IA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=onqdqfw8Ax7Bc/1LXWf4XaZo4LuS1zfj5R+uUZ+8SJVMfHtqQfBHwoVV3M+UWbdIM zvlwjYnBtLogcedIrIiY7MgwJPF2hDgOyJK2L0LFmn65L6+HKFnqwHKkgwUwFyJ2/r Fo6G4MKvPxMvZEL0K7VnVcdI61YEuCdk0YzvwYEo= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder Subject: [PATCH v2 1/4] libcamera: geometry: Add floating-point version of Point class Date: Fri, 26 Apr 2024 16:36:09 +0900 Message-Id: <20240426073612.1230283-2-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240426073612.1230283-1-paul.elder@ideasonboard.com> References: <20240426073612.1230283-1-paul.elder@ideasonboard.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" The piecewise linear function (Pwl) class from the Raspberry Pi IPA has its own Point class while one already exists in libcamera's geometry.h header. The reason is because libcamera's Point is on the plane of integer, while Raspberry Pi's is on the plane of reals. While making this a template class might be cleaner, it was deemed to be too intrusive of a change, and it might not feel nice to need to specify the type from a public API point of view. Hence we introduce a FPoint class to designate a Point class on the plane of reals. This is in preparation for copying/moving the Pwl class from the Raspberry Pi IPA to libipa. Signed-off-by: Paul Elder Reviewed-by: Stefan Klug --- Changes in v2: - renamed FPoint to PointF - add documentation - add tests --- include/libcamera/geometry.h | 65 +++++++ src/libcamera/geometry.cpp | 123 +++++++++++- test/geometry.cpp | 355 +++++++++++++++++++++++++++++++++++ 3 files changed, 542 insertions(+), 1 deletion(-) diff --git a/include/libcamera/geometry.h b/include/libcamera/geometry.h index d7fdbe70..6aef1f39 100644 --- a/include/libcamera/geometry.h +++ b/include/libcamera/geometry.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include #include @@ -49,6 +50,70 @@ static inline bool operator!=(const Point &lhs, const Point &rhs) std::ostream &operator<<(std::ostream &out, const Point &p); +struct PointF { + constexpr PointF() + : x(0), y(0) + { + } + + constexpr PointF(double _x, double _y) + : x(_x), y(_y) + { + } + + constexpr PointF operator-() const + { + return PointF{ -x, -y }; + } + + constexpr PointF operator-(const PointF &p) const + { + return PointF(x - p.x, y - p.y); + } + + constexpr PointF operator+(const PointF &p) const + { + return PointF(x + p.x, y + p.y); + } + + constexpr double operator%(const PointF &p) const + { + return x * p.x + y * p.y; + } + + constexpr PointF operator*(double f) const + { + return PointF(x * f, y * f); + } + + constexpr PointF operator/(double f) const + { + return PointF(x / f, y / f); + } + + constexpr double len2() const + { + return x * x + y * y; + } + + constexpr double len() const + { + return std::sqrt(len2()); + } + + const std::string toString() const; + + double x, y; +}; + +bool operator==(const PointF &lhs, const PointF &rhs); +static inline bool operator!=(const PointF &lhs, const PointF &rhs) +{ + return !(lhs == rhs); +} + +std::ostream &operator<<(std::ostream &out, const PointF &p); + class Size { public: diff --git a/src/libcamera/geometry.cpp b/src/libcamera/geometry.cpp index 8d85b758..90b81282 100644 --- a/src/libcamera/geometry.cpp +++ b/src/libcamera/geometry.cpp @@ -21,7 +21,7 @@ namespace libcamera { /** * \class Point - * \brief Describe a point in two-dimensional space + * \brief Describe a point in two-dimensional integer space * * The Point structure defines a point in two-dimensional space with integer * precision. The coordinates of a Point may be negative as well as positive. @@ -94,6 +94,127 @@ std::ostream &operator<<(std::ostream &out, const Point &p) return out; } +/** + * \class PointF + * \brief Describe a point in two-dimensional real space + * + * The Point structure defines a point in two-dimensional space with double + * precision. The coordinates of a Point may be negative as well as positive. + * + * This class exists separately from Point to not require all users of the + * Point class to have to use template parameters to specify a type. + */ + +/** + * \fn PointF::PointF() + * \copydoc libcamera::Point::Point + */ + +/** + * \fn PointF::PointF(double _x, double _y) + * \brief Construct a PointF at given \a _x and \a _y values + * \param[in] _x The x-coordinate + * \param[in] _y The y-coordinate + */ + +/** + * \var PointF::x + * \copydoc libcamera::Point::x + */ + +/** + * \var PointF::y + * \copydoc libcamera::Point::y + */ + +/** + * \fn constexpr PointF PointF::operator-() const + * \copydoc libcamera::Point::operator- + */ + +/** + * \fn constexpr PointF PointF::operator-(PointF const &p) const + * \brief Subtract one point from another, as if they were vectors + * \param[in] p The other point + * \return The difference of p from this point + */ + +/** + * \fn PointF::operator+() + * \brief Add two points together, as if they were vectors + * \param[in] p The other point + * \return The sum of the two points + */ + +/** + * \fn PointF::operator%() + * \brief Compute the dot product, treating the points as vectors + * \param[in] p The other point + * \return The dot product of the two points + */ + +/** + * \fn PointF::operator*() + * \brief Scale up the point, as if it were a vector + * \param[in] f The factor + * \return The scaled point + */ + +/** + * \fn PointF::operator/() + * \brief Scale down the point, as if it were a vector + * \param[in] f The factor + * \return The scaled point + */ + +/** + * \fn PointF::len2() + * \brief Get the squared length of the point, as if it were a vector + * \return The squared length of the point + */ + +/** + * \fn PointF::len() + * \brief Get the length of the point, as if it were a vector + * \return The length of the point + */ + +/** + * \copydoc Point::toString() + */ +const std::string PointF::toString() const +{ + std::stringstream ss; + ss << *this; + + return ss.str(); +} + +/** + * \copydoc operator==(const Point &lhs, const Point &rhs) + */ +bool operator==(const PointF &lhs, const PointF &rhs) +{ + return lhs.x == rhs.x && lhs.y == rhs.y; +} + +/** + * \fn bool operator!=(const Point &lhs, const Point &rhs) + * \copydoc libcamera::Point::operator!= + */ + +/** + * \brief Insert a text representation of a PointF into an output stream + * \param[in] out The output stream + * \param[in] p The point + * \return The output stream \a out + */ +std::ostream &operator<<(std::ostream &out, const PointF &p) +{ + out << "(" << p.x << ", " << p.y << ")"; + return out; +} + /** * \class Size * \brief Describe a two-dimensional size diff --git a/test/geometry.cpp b/test/geometry.cpp index 008d51ea..7f1f5b1a 100644 --- a/test/geometry.cpp +++ b/test/geometry.cpp @@ -33,6 +33,53 @@ protected: return true; } + template + bool compareF(const T &lhs, const U &rhs, + V (PointF::*op)(const U &) const, + const char *opName, V expect) + { + V result = (lhs.*op)(rhs); + + if (result != expect) { + cout << lhs << opName << " " << rhs + << "test failed" << std::endl; + return false; + } + + return true; + } + + template + bool compareFScale(const T &lhs, const U &rhs, + V (PointF::*op)(U) const, + const char *opName, V expect) + { + V result = (lhs.*op)(rhs); + + if (result != expect) { + cout << lhs << opName << " " << rhs + << "test failed" << std::endl; + return false; + } + + return true; + } + + template + bool compareFLen(const T &lhs, double (PointF::*op)() const, + const char *opName, double expect) + { + double result = (lhs.*op)(); + + if (result != expect) { + cout << lhs << opName + << "test failed" << std::endl; + return false; + } + + return true; + } + int run() { /* @@ -88,6 +135,314 @@ protected: return TestFail; } + /* + * PointF tests + */ + + /* Equality */ + if (!compare(PointF(50.1, 100.1), PointF(50.1, 100.1), &operator==, "==", true)) + return TestFail; + + if (!compare(PointF(-50.1, 100.1), PointF(-50.1, 100.1), &operator==, "==", true)) + return TestFail; + + if (!compare(PointF(50.1, -100.1), PointF(50.1, -100.1), &operator==, "==", true)) + return TestFail; + + if (!compare(PointF(-50.1, -100.1), PointF(-50.1, -100.1), &operator==, "==", true)) + return TestFail; + + if (!compare(PointF(-50.1, -100.0), PointF(-50.1, -100), &operator==, "==", true)) + return TestFail; + + /* Inequality */ + if (!compare(PointF(50.1, 100.1), PointF(50.1, 100.2), &operator!=, "!=", true)) + return TestFail; + + if (!compare(PointF(-50.1, 100.1), PointF(-50.1, 100.01), &operator!=, "!=", true)) + return TestFail; + + if (!compare(PointF(-50.1, 100.1), PointF(-50.1, 100.1), &operator!=, "!=", false)) + return TestFail; + + if (!compare(PointF(50.1, -100.1), PointF(50.2, -100.1), &operator!=, "!=", true)) + return TestFail; + + if (!compare(PointF(-50.1, -100.1), PointF(-50.01, -100.0), &operator!=, "!=", true)) + return TestFail; + + if (!compare(PointF(-50.1, 100.1), PointF(50.1, 100.1), &operator!=, "!=", true)) + return TestFail; + + if (!compare(PointF(50.1, -100.1), PointF(50.1, 100.1), &operator!=, "!=", true)) + return TestFail; + + if (!compare(PointF(-50.1, -100.1), PointF(-50.1, -100.1), &operator!=, "!=", false)) + return TestFail; + + /* Negation */ + if (PointF(50.1, 100.1) != -PointF(-50.1, -100.1) || + PointF(50.1, 100.1) == -PointF(50.1, -100.1) || + PointF(50.1, 100.1) == -PointF(-50.1, 100.1)) { + cout << "PointF negation test failed" << endl; + return TestFail; + } + + typedef PointF (PointF::*pointfOp)(const PointF &) const; + typedef double (PointF::*pointfDotProd)(const PointF &) const; + typedef PointF (PointF::*pointfScale)(double) const; + typedef double (PointF::*pointfLen)() const; + + /* Subtraction */ + if (!compareF(PointF(50.1, 100.1), PointF(50.1, 100.1), + static_cast(&PointF::operator-), "-", + PointF(0, 0))) + return TestFail; + + if (!compareF(PointF(-50.1, 100.1), PointF(-50.1, 100.1), + static_cast(&PointF::operator-), "-", + PointF(0, 0))) + return TestFail; + + if (!compareF(PointF(50.1, -100.1), PointF(50.1, -100.1), + static_cast(&PointF::operator-), "-", + PointF(0, 0))) + return TestFail; + + if (!compareF(PointF(-50.1, -100.1), PointF(-50.1, -100.1), + static_cast(&PointF::operator-), "-", + PointF(0, 0))) + return TestFail; + + if (!compareF(PointF(50.1, 100.1), PointF(-50.1, 100.1), + static_cast(&PointF::operator-), "-", + PointF(100.2, 0))) + return TestFail; + + if (!compareF(PointF(50.1, 100.1), PointF(50.1, -100.1), + static_cast(&PointF::operator-), "-", + PointF(0, 200.2))) + return TestFail; + + if (!compareF(PointF(50.1, 100.1), PointF(-50.1, -100.1), + static_cast(&PointF::operator-), "-", + PointF(100.2, 200.2))) + return TestFail; + + if (!compareF(PointF(-50.1, 100.1), PointF(50.1, 100.1), + static_cast(&PointF::operator-), "-", + PointF(-100.2, 0))) + return TestFail; + + /* Addition */ + if (!compareF(PointF(50.1, 100.1), PointF(50.1, 100.1), + static_cast(&PointF::operator+), "+", + PointF(100.2, 200.2))) + return TestFail; + + if (!compareF(PointF(-50.1, 100.1), PointF(-50.1, 100.1), + static_cast(&PointF::operator+), "+", + PointF(-100.2, 200.2))) + return TestFail; + + if (!compareF(PointF(50.1, -100.1), PointF(50.1, -100.1), + static_cast(&PointF::operator+), "+", + PointF(100.2, -200.2))) + return TestFail; + + if (!compareF(PointF(-50.1, -100), PointF(-50.1, -100.1), + static_cast(&PointF::operator+), "+", + PointF(-100.2, -200.1))) + return TestFail; + + if (!compareF(PointF(50.1, 100.1), PointF(-50.1, 100.1), + static_cast(&PointF::operator+), "+", + PointF(0, 200.2))) + return TestFail; + + if (!compareF(PointF(50.1, 100.0), PointF(50.1, -100.1), + static_cast(&PointF::operator+), "+", + PointF(100.2, -0.09999999999999432))) + return TestFail; + + if (!compareF(PointF(50.1, 100.1), PointF(-50.1, -100.1), + static_cast(&PointF::operator+), "+", + PointF(0, 0))) + return TestFail; + + if (!compareF(PointF(-50.1, 100.1), PointF(50.1, 100.1), + static_cast(&PointF::operator+), "+", + PointF(0, 200.2))) + return TestFail; + + /* Dot product */ + if (!compareF(PointF(50.1, 100.1), PointF(50.1, 100.1), + static_cast(&PointF::operator%), "%", + 12530.019999999999)) + return TestFail; + + if (!compareF(PointF(50.1, 100.1), PointF(50.1, -100.1), + static_cast(&PointF::operator%), "%", + -7509.999999999998)) + return TestFail; + + if (!compareF(PointF(50.1, 100.1), PointF(-50.1, 100.1), + static_cast(&PointF::operator%), "%", + 7509.999999999998)) + return TestFail; + + if (!compareF(PointF(50.1, 100.1), PointF(-50.1, -100.1), + static_cast(&PointF::operator%), "%", + -12530.019999999999)) + return TestFail; + + if (!compareF(PointF(-50.1, 100.1), PointF(-50.1, 100.1), + static_cast(&PointF::operator%), "%", + 12530.019999999999)) + return TestFail; + + if (!compareF(PointF(50.1, -100.1), PointF(50.1, -100.1), + static_cast(&PointF::operator%), "%", + 12530.019999999999)) + return TestFail; + + if (!compareF(PointF(-50.1, -100.1), PointF(-50.1, -100.1), + static_cast(&PointF::operator%), "%", + 12530.019999999999)) + return TestFail; + + if (!compareF(PointF(0, 0), PointF(0, 0), + static_cast(&PointF::operator%), "%", + 0)) + return TestFail; + + + /* Scaling up */ + if (!compareFScale(PointF(10.5, -100.1), 0, + static_cast(&PointF::operator*), "*", + PointF(0, 0))) + return TestFail; + + if (!compareFScale(PointF(10.5, -100.1), 1, + static_cast(&PointF::operator*), "*", + PointF(10.5, -100.1))) + return TestFail; + + if (!compareFScale(PointF(10.5, -100.1), 1.0, + static_cast(&PointF::operator*), "*", + PointF(10.5, -100.1))) + return TestFail; + + if (!compareFScale(PointF(10.5, -100.1), -4.2, + static_cast(&PointF::operator*), "*", + PointF(-44.1, 420.42))) + return TestFail; + + if (!compareFScale(PointF(10.5, -100.1), 4.2, + static_cast(&PointF::operator*), "*", + PointF(44.1, -420.42))) + return TestFail; + + if (!compareFScale(PointF(0, -100.1), 4.2, + static_cast(&PointF::operator*), "*", + PointF(0, -420.42))) + return TestFail; + + if (!compareFScale(PointF(-50.1, -100.1), 4.2, + static_cast(&PointF::operator*), "*", + PointF(-210.42000000000002, -420.42))) + return TestFail; + + /* Scaling down */ + if (!compareFScale(PointF(10.5, -100.1), 1, + static_cast(&PointF::operator/), "/", + PointF(10.5, -100.1))) + return TestFail; + + if (!compareFScale(PointF(10.5, -100.1), 1.0, + static_cast(&PointF::operator/), "/", + PointF(10.5, -100.1))) + return TestFail; + + if (!compareFScale(PointF(10.5, -100.1), -4.2, + static_cast(&PointF::operator/), "/", + PointF(-2.5, 23.833333333333332))) + return TestFail; + + if (!compareFScale(PointF(10.5, -100.1), 4.2, + static_cast(&PointF::operator/), "/", + PointF(2.5, -23.833333333333332))) + return TestFail; + + if (!compareFScale(PointF(0, -100.1), 4.2, + static_cast(&PointF::operator/), "/", + PointF(0, -23.833333333333332))) + return TestFail; + + if (!compareFScale(PointF(-50.1, -100.1), 4.2, + static_cast(&PointF::operator/), "/", + PointF(-11.928571428571429, -23.833333333333332))) + return TestFail; + + + /* Squared length */ + if (!compareFLen(PointF(0, 0), + static_cast(&PointF::len2), "len2", + 0)) + return TestFail; + + if (!compareFLen(PointF(10.4, 0), + static_cast(&PointF::len2), "len2", + 108.16000000000001)) + return TestFail; + + if (!compareFLen(PointF(10.4, 50.1), + static_cast(&PointF::len2), "len2", + 2618.17)) + return TestFail; + + if (!compareFLen(PointF(-10.4, 50.1), + static_cast(&PointF::len2), "len2", + 2618.17)) + return TestFail; + + if (!compareFLen(PointF(-10.4, -50.1), + static_cast(&PointF::len2), "len2", + 2618.17)) + return TestFail; + + /* Length */ + if (!compareFLen(PointF(0, 0), + static_cast(&PointF::len), "len", + 0)) + return TestFail; + + if (!compareFLen(PointF(10.4, 0), + static_cast(&PointF::len), "len", + 10.4)) + return TestFail; + + if (!compareFLen(PointF(10.4, 50.1), + static_cast(&PointF::len), "len", + 51.16805644149483)) + return TestFail; + + if (!compareFLen(PointF(-10.4, 50.1), + static_cast(&PointF::len), "len", + 51.16805644149483)) + return TestFail; + + if (!compareFLen(PointF(-10.4, -50.1), + static_cast(&PointF::len), "len", + 51.16805644149483)) + return TestFail; + + /* Default constructor */ + if (PointF() != PointF(0, 0)) { + cout << "Default constructor test failed" << endl; + return TestFail; + } + /* * Size tests */ From patchwork Fri Apr 26 07:36:10 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 19957 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 1B7D2C3220 for ; Fri, 26 Apr 2024 07:36:32 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 95A1F63415; Fri, 26 Apr 2024 09:36:31 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="USmWwk1A"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id D2430633EA for ; Fri, 26 Apr 2024 09:36:27 +0200 (CEST) Received: from pyrite.hamster-moth.ts.net (h175-177-049-156.catv02.itscom.jp [175.177.49.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 4CA6266B; Fri, 26 Apr 2024 09:35:32 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1714116934; bh=3K4KFP1JTyWl3WRkRXYnGCU6FgmW3DcEeBBX/EP1yfc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=USmWwk1A+4JnDVV38fh16xgSLR25WzaGqHxV0nS+/9L4xGsT9o1Yg9tbN+cviDQzI XZpBF4LsSPQbwJ0TIWvPDvKsjJK0WxjJvek7luLrG8w6ZAc2y+ZYAaxZ/+BiE1eh1K Wee8LCMkuvrYNEiAsx4eVLnYjrErIemPXvGV7Yaw= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder , Stefan Klug Subject: [PATCH v2 2/4] ipa: libipa: Copy pwl from rpi Date: Fri, 26 Apr 2024 16:36:10 +0900 Message-Id: <20240426073612.1230283-3-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240426073612.1230283-1-paul.elder@ideasonboard.com> References: <20240426073612.1230283-1-paul.elder@ideasonboard.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" Copy the piecewise linear function code from Raspberry Pi. Signed-off-by: Paul Elder Reviewed-by: Stefan Klug --- No change in v2 --- src/ipa/libipa/meson.build | 2 + src/ipa/libipa/pwl.cpp | 267 +++++++++++++++++++++++++++++++++++++ src/ipa/libipa/pwl.h | 123 +++++++++++++++++ 3 files changed, 392 insertions(+) create mode 100644 src/ipa/libipa/pwl.cpp create mode 100644 src/ipa/libipa/pwl.h diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build index 7ce885da..1b3faf8d 100644 --- a/src/ipa/libipa/meson.build +++ b/src/ipa/libipa/meson.build @@ -8,6 +8,7 @@ libipa_headers = files([ 'fc_queue.h', 'histogram.h', 'module.h', + 'pwl.h', ]) libipa_sources = files([ @@ -18,6 +19,7 @@ libipa_sources = files([ 'fc_queue.cpp', 'histogram.cpp', 'module.cpp', + 'pwl.cpp' ]) libipa_includes = include_directories('..') diff --git a/src/ipa/libipa/pwl.cpp b/src/ipa/libipa/pwl.cpp new file mode 100644 index 00000000..09f5d65c --- /dev/null +++ b/src/ipa/libipa/pwl.cpp @@ -0,0 +1,267 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (C) 2019, Raspberry Pi Ltd + * + * pwl.cpp - piecewise linear functions + */ + +#include +#include +#include + +#include "pwl.h" + +int Pwl::read(const libcamera::YamlObject ¶ms) +{ + if (!params.size() || params.size() % 2) + return -EINVAL; + + const auto &list = params.asList(); + + for (auto it = list.begin(); it != list.end(); it++) { + auto x = it->get(); + if (!x) + return -EINVAL; + if (it != list.begin() && *x <= points_.back().x) + return -EINVAL; + + auto y = (++it)->get(); + if (!y) + return -EINVAL; + + points_.push_back(Point(*x, *y)); + } + + return 0; +} + +void Pwl::append(double x, double y, const double eps) +{ + if (points_.empty() || points_.back().x + eps < x) + points_.push_back(Point(x, y)); +} + +void Pwl::prepend(double x, double y, const double eps) +{ + if (points_.empty() || points_.front().x - eps > x) + points_.insert(points_.begin(), Point(x, y)); +} + +Pwl::Interval Pwl::domain() const +{ + return Interval(points_[0].x, points_[points_.size() - 1].x); +} + +Pwl::Interval Pwl::range() const +{ + double lo = points_[0].y, hi = lo; + for (auto &p : points_) + lo = std::min(lo, p.y), hi = std::max(hi, p.y); + return Interval(lo, hi); +} + +bool Pwl::empty() const +{ + return points_.empty(); +} + +double Pwl::eval(double x, int *spanPtr, bool updateSpan) const +{ + int span = findSpan(x, spanPtr && *spanPtr != -1 ? *spanPtr : points_.size() / 2 - 1); + if (spanPtr && updateSpan) + *spanPtr = span; + return points_[span].y + + (x - points_[span].x) * (points_[span + 1].y - points_[span].y) / + (points_[span + 1].x - points_[span].x); +} + +int Pwl::findSpan(double x, int span) const +{ + /* + * Pwls are generally small, so linear search may well be faster than + * binary, though could review this if large PWls start turning up. + */ + int lastSpan = points_.size() - 2; + /* + * some algorithms may call us with span pointing directly at the last + * control point + */ + span = std::max(0, std::min(lastSpan, span)); + while (span < lastSpan && x >= points_[span + 1].x) + span++; + while (span && x < points_[span].x) + span--; + return span; +} + +Pwl::PerpType Pwl::invert(Point const &xy, Point &perp, int &span, + const double eps) const +{ + assert(span >= -1); + bool prevOffEnd = false; + for (span = span + 1; span < (int)points_.size() - 1; span++) { + Point spanVec = points_[span + 1] - points_[span]; + double t = ((xy - points_[span]) % spanVec) / spanVec.len2(); + if (t < -eps) /* off the start of this span */ + { + if (span == 0) { + perp = points_[span]; + return PerpType::Start; + } else if (prevOffEnd) { + perp = points_[span]; + return PerpType::Vertex; + } + } else if (t > 1 + eps) /* off the end of this span */ + { + if (span == (int)points_.size() - 2) { + perp = points_[span + 1]; + return PerpType::End; + } + prevOffEnd = true; + } else /* a true perpendicular */ + { + perp = points_[span] + spanVec * t; + return PerpType::Perpendicular; + } + } + return PerpType::None; +} + +Pwl Pwl::inverse(bool *trueInverse, const double eps) const +{ + bool appended = false, prepended = false, neither = false; + Pwl inverse; + + for (Point const &p : points_) { + if (inverse.empty()) + inverse.append(p.y, p.x, eps); + else if (std::abs(inverse.points_.back().x - p.y) <= eps || + std::abs(inverse.points_.front().x - p.y) <= eps) + /* do nothing */; + else if (p.y > inverse.points_.back().x) { + inverse.append(p.y, p.x, eps); + appended = true; + } else if (p.y < inverse.points_.front().x) { + inverse.prepend(p.y, p.x, eps); + prepended = true; + } else + neither = true; + } + + /* + * This is not a proper inverse if we found ourselves putting points + * onto both ends of the inverse, or if there were points that couldn't + * go on either. + */ + if (trueInverse) + *trueInverse = !(neither || (appended && prepended)); + + return inverse; +} + +Pwl Pwl::compose(Pwl const &other, const double eps) const +{ + double thisX = points_[0].x, thisY = points_[0].y; + int thisSpan = 0, otherSpan = other.findSpan(thisY, 0); + Pwl result({ { thisX, other.eval(thisY, &otherSpan, false) } }); + while (thisSpan != (int)points_.size() - 1) { + double dx = points_[thisSpan + 1].x - points_[thisSpan].x, + dy = points_[thisSpan + 1].y - points_[thisSpan].y; + if (std::abs(dy) > eps && + otherSpan + 1 < (int)other.points_.size() && + points_[thisSpan + 1].y >= + other.points_[otherSpan + 1].x + eps) { + /* + * next control point in result will be where this + * function's y reaches the next span in other + */ + thisX = points_[thisSpan].x + + (other.points_[otherSpan + 1].x - + points_[thisSpan].y) * + dx / dy; + thisY = other.points_[++otherSpan].x; + } else if (std::abs(dy) > eps && otherSpan > 0 && + points_[thisSpan + 1].y <= + other.points_[otherSpan - 1].x - eps) { + /* + * next control point in result will be where this + * function's y reaches the previous span in other + */ + thisX = points_[thisSpan].x + + (other.points_[otherSpan + 1].x - + points_[thisSpan].y) * + dx / dy; + thisY = other.points_[--otherSpan].x; + } else { + /* we stay in the same span in other */ + thisSpan++; + thisX = points_[thisSpan].x, + thisY = points_[thisSpan].y; + } + result.append(thisX, other.eval(thisY, &otherSpan, false), + eps); + } + return result; +} + +void Pwl::map(std::function f) const +{ + for (auto &pt : points_) + f(pt.x, pt.y); +} + +void Pwl::map2(Pwl const &pwl0, Pwl const &pwl1, + std::function f) +{ + int span0 = 0, span1 = 0; + double x = std::min(pwl0.points_[0].x, pwl1.points_[0].x); + f(x, pwl0.eval(x, &span0, false), pwl1.eval(x, &span1, false)); + while (span0 < (int)pwl0.points_.size() - 1 || + span1 < (int)pwl1.points_.size() - 1) { + if (span0 == (int)pwl0.points_.size() - 1) + x = pwl1.points_[++span1].x; + else if (span1 == (int)pwl1.points_.size() - 1) + x = pwl0.points_[++span0].x; + else if (pwl0.points_[span0 + 1].x > pwl1.points_[span1 + 1].x) + x = pwl1.points_[++span1].x; + else + x = pwl0.points_[++span0].x; + f(x, pwl0.eval(x, &span0, false), pwl1.eval(x, &span1, false)); + } +} + +Pwl Pwl::combine(Pwl const &pwl0, Pwl const &pwl1, + std::function f, + const double eps) +{ + Pwl result; + map2(pwl0, pwl1, [&](double x, double y0, double y1) { + result.append(x, f(x, y0, y1), eps); + }); + return result; +} + +void Pwl::matchDomain(Interval const &domain, bool clip, const double eps) +{ + int span = 0; + prepend(domain.start, eval(clip ? points_[0].x : domain.start, &span), + eps); + span = points_.size() - 2; + append(domain.end, eval(clip ? points_.back().x : domain.end, &span), + eps); +} + +Pwl &Pwl::operator*=(double d) +{ + for (auto &pt : points_) + pt.y *= d; + return *this; +} + +void Pwl::debug(FILE *fp) const +{ + fprintf(fp, "Pwl {\n"); + for (auto &p : points_) + fprintf(fp, "\t(%g, %g)\n", p.x, p.y); + fprintf(fp, "}\n"); +} diff --git a/src/ipa/libipa/pwl.h b/src/ipa/libipa/pwl.h new file mode 100644 index 00000000..7a6a6452 --- /dev/null +++ b/src/ipa/libipa/pwl.h @@ -0,0 +1,123 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (C) 2019, Raspberry Pi Ltd + * + * pwl.h - piecewise linear functions interface + */ +#pragma once + +#include +#include +#include + +#include "libcamera/internal/yaml_parser.h" + +class Pwl +{ +public: + struct Interval { + Interval(double _start, double _end) + : start(_start), end(_end) + { + } + double start, end; + bool contains(double value) + { + return value >= start && value <= end; + } + double clip(double value) + { + return value < start ? start + : (value > end ? end : value); + } + double len() const { return end - start; } + }; + struct Point { + Point() : x(0), y(0) {} + Point(double _x, double _y) + : x(_x), y(_y) {} + double x, y; + Point operator-(Point const &p) const + { + return Point(x - p.x, y - p.y); + } + Point operator+(Point const &p) const + { + return Point(x + p.x, y + p.y); + } + double operator%(Point const &p) const + { + return x * p.x + y * p.y; + } + Point operator*(double f) const { return Point(x * f, y * f); } + Point operator/(double f) const { return Point(x / f, y / f); } + double len2() const { return x * x + y * y; } + double len() const { return sqrt(len2()); } + }; + Pwl() {} + Pwl(std::vector const &points) : points_(points) {} + int read(const libcamera::YamlObject ¶ms); + void append(double x, double y, const double eps = 1e-6); + void prepend(double x, double y, const double eps = 1e-6); + Interval domain() const; + Interval range() const; + bool empty() const; + /* + * Evaluate Pwl, optionally supplying an initial guess for the + * "span". The "span" may be optionally be updated. If you want to know + * the "span" value but don't have an initial guess you can set it to + * -1. + */ + double eval(double x, int *spanPtr = nullptr, + bool updateSpan = true) const; + /* + * Find perpendicular closest to xy, starting from span+1 so you can + * call it repeatedly to check for multiple closest points (set span to + * -1 on the first call). Also returns "pseudo" perpendiculars; see + * PerpType enum. + */ + enum class PerpType { + None, /* no perpendicular found */ + Start, /* start of Pwl is closest point */ + End, /* end of Pwl is closest point */ + Vertex, /* vertex of Pwl is closest point */ + Perpendicular /* true perpendicular found */ + }; + PerpType invert(Point const &xy, Point &perp, int &span, + const double eps = 1e-6) const; + /* + * Compute the inverse function. Indicate if it is a proper (true) + * inverse, or only a best effort (e.g. input was non-monotonic). + */ + Pwl inverse(bool *trueInverse = nullptr, const double eps = 1e-6) const; + /* Compose two Pwls together, doing "this" first and "other" after. */ + Pwl compose(Pwl const &other, const double eps = 1e-6) const; + /* Apply function to (x,y) values at every control point. */ + void map(std::function f) const; + /* + * Apply function to (x, y0, y1) values wherever either Pwl has a + * control point. + */ + static void map2(Pwl const &pwl0, Pwl const &pwl1, + std::function f); + /* + * Combine two Pwls, meaning we create a new Pwl where the y values are + * given by running f wherever either has a knot. + */ + static Pwl + combine(Pwl const &pwl0, Pwl const &pwl1, + std::function f, + const double eps = 1e-6); + /* + * Make "this" match (at least) the given domain. Any extension my be + * clipped or linear. + */ + void matchDomain(Interval const &domain, bool clip = true, + const double eps = 1e-6); + Pwl &operator*=(double d); + void debug(FILE *fp = stdout) const; + +private: + int findSpan(double x, int span) const; + std::vector points_; +}; From patchwork Fri Apr 26 07:36:11 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 19958 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 B3B8AC32A2 for ; Fri, 26 Apr 2024 07:36:33 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2578763412; Fri, 26 Apr 2024 09:36:33 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="djkZArhF"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id E794B633F8 for ; Fri, 26 Apr 2024 09:36:29 +0200 (CEST) Received: from pyrite.hamster-moth.ts.net (h175-177-049-156.catv02.itscom.jp [175.177.49.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id A3331EA5; Fri, 26 Apr 2024 09:35:35 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1714116936; bh=rL/G7sKgGP3bCGilX2n0vq79rz5sRhgemetlKEXQuCA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=djkZArhFb//3FbSym2TrpExWirdUht278X81uNovcxoqRgVPGVKzpPyauyF8/+cMX J7Uci9QmU/BuxgJofbiJzyWBtmOXcpoVZ23MKj68/B6vr2s5zWodvibanTwkoQ6Z+6 0+yAbkf6kTlhqYxMg7Ff/p/bFIgT7vAm5gfj29w8= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder Subject: [PATCH v2 3/4] ipa: libipa: pwl: Clean up Pwl class to match libcamera Date: Fri, 26 Apr 2024 16:36:11 +0900 Message-Id: <20240426073612.1230283-4-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240426073612.1230283-1-paul.elder@ideasonboard.com> References: <20240426073612.1230283-1-paul.elder@ideasonboard.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" Clean up the Pwl class copied from the Raspberry Pi IPA to align it more with the libcamera style. Signed-off-by: Paul Elder Reviewed-by: Stefan Klug --- Changes in v2: - s/FPoint/PointF/g - improve documentation - s/matchDomain/extendDomain/ --- src/ipa/libipa/pwl.cpp | 156 ++++++++++++++++++++++++++++++++++------- src/ipa/libipa/pwl.h | 117 ++++++++++++------------------- 2 files changed, 176 insertions(+), 97 deletions(-) diff --git a/src/ipa/libipa/pwl.cpp b/src/ipa/libipa/pwl.cpp index 09f5d65c..4faf9c31 100644 --- a/src/ipa/libipa/pwl.cpp +++ b/src/ipa/libipa/pwl.cpp @@ -1,17 +1,45 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2019, Raspberry Pi Ltd + * Copyright (C) 2024, Ideas on Board Oy * * pwl.cpp - piecewise linear functions */ +#include "pwl.h" + #include #include +#include #include -#include "pwl.h" +#include + +namespace libcamera { + +namespace ipa { + +/* + * \enum Pwl::PerpType + * \brief Type of perpendicular found when inverting a piecewise linear function + * + * \var None + * \brief no perpendicular found + * + * \var Start + * \brief start of Pwl is closest point + * + * \var End + * \brief end of Pwl is closest point + * + * \var Vertex + * \brief vertex of Pwl is closest point + * + * \var Perpendicular + * \brief true perpendicular found + */ -int Pwl::read(const libcamera::YamlObject ¶ms) +int Pwl::readYaml(const libcamera::YamlObject ¶ms) { if (!params.size() || params.size() % 2) return -EINVAL; @@ -29,7 +57,7 @@ int Pwl::read(const libcamera::YamlObject ¶ms) if (!y) return -EINVAL; - points_.push_back(Point(*x, *y)); + points_.push_back(PointF(*x, *y)); } return 0; @@ -38,13 +66,13 @@ int Pwl::read(const libcamera::YamlObject ¶ms) void Pwl::append(double x, double y, const double eps) { if (points_.empty() || points_.back().x + eps < x) - points_.push_back(Point(x, y)); + points_.push_back(PointF(x, y)); } void Pwl::prepend(double x, double y, const double eps) { if (points_.empty() || points_.front().x - eps > x) - points_.insert(points_.begin(), Point(x, y)); + points_.insert(points_.begin(), PointF(x, y)); } Pwl::Interval Pwl::domain() const @@ -65,6 +93,19 @@ bool Pwl::empty() const return points_.empty(); } +/* + * \brief Evaluate the piecewise linear function + * \param[in] x The x value to input into the function + * \param[inout] spanPtr Initial guess for span + * \param[in] updateSpan Set to true to update spanPtr + * + * Evaluate Pwl, optionally supplying an initial guess for the + * "span". The "span" may be optionally be updated. If you want to know + * the "span" value but don't have an initial guess you can set it to + * -1. + * + * \return The result of evaluating the piecewise linear function at position \a x + */ double Pwl::eval(double x, int *spanPtr, bool updateSpan) const { int span = findSpan(x, spanPtr && *spanPtr != -1 ? *spanPtr : points_.size() / 2 - 1); @@ -94,16 +135,29 @@ int Pwl::findSpan(double x, int span) const return span; } -Pwl::PerpType Pwl::invert(Point const &xy, Point &perp, int &span, +/* + * \brief Find perpendicular closest to a given point + * \param[in] xy Point to find the perpendicular to + * \param[out] perp The found perpendicular + * \param[inout] span The span+1 to start searching from + * \param[in] eps Epsilon + * + * Find perpendicular closest to \a xy, starting from \a span+1 so you can call + * it repeatedly to check for multiple closest points (set span to -1 on the + * first call). Also returns "pseudo" perpendiculars; see PerpType enum. + * + * \return Type of perpendicular found + */ +Pwl::PerpType Pwl::invert(PointF const &xy, PointF &perp, int &span, const double eps) const { assert(span >= -1); bool prevOffEnd = false; for (span = span + 1; span < (int)points_.size() - 1; span++) { - Point spanVec = points_[span + 1] - points_[span]; + PointF spanVec = points_[span + 1] - points_[span]; double t = ((xy - points_[span]) % spanVec) / spanVec.len2(); - if (t < -eps) /* off the start of this span */ - { + if (t < -eps) { + /* off the start of this span */ if (span == 0) { perp = points_[span]; return PerpType::Start; @@ -111,15 +165,15 @@ Pwl::PerpType Pwl::invert(Point const &xy, Point &perp, int &span, perp = points_[span]; return PerpType::Vertex; } - } else if (t > 1 + eps) /* off the end of this span */ - { + } else if (t > 1 + eps) { + /* off the end of this span */ if (span == (int)points_.size() - 2) { perp = points_[span + 1]; return PerpType::End; } prevOffEnd = true; - } else /* a true perpendicular */ - { + } else { + /* a true perpendicular */ perp = points_[span] + spanVec * t; return PerpType::Perpendicular; } @@ -127,25 +181,36 @@ Pwl::PerpType Pwl::invert(Point const &xy, Point &perp, int &span, return PerpType::None; } +/* + * \brief Compute the inverse function + * \param[out] trueInverse True if the result is a proper/true inverse + * \param[in] eps Epsilon (optional) + * + * Indicate if it is a proper (true) inverse, or only a best effort (e.g. + * input was non-monotonic). + * + * \return The inverse piecewise linear function + */ Pwl Pwl::inverse(bool *trueInverse, const double eps) const { bool appended = false, prepended = false, neither = false; Pwl inverse; - for (Point const &p : points_) { - if (inverse.empty()) + for (PointF const &p : points_) { + if (inverse.empty()) { inverse.append(p.y, p.x, eps); - else if (std::abs(inverse.points_.back().x - p.y) <= eps || - std::abs(inverse.points_.front().x - p.y) <= eps) + } else if (std::abs(inverse.points_.back().x - p.y) <= eps || + std::abs(inverse.points_.front().x - p.y) <= eps) { /* do nothing */; - else if (p.y > inverse.points_.back().x) { + } else if (p.y > inverse.points_.back().x) { inverse.append(p.y, p.x, eps); appended = true; } else if (p.y < inverse.points_.front().x) { inverse.prepend(p.y, p.x, eps); prepended = true; - } else + } else { neither = true; + } } /* @@ -159,18 +224,27 @@ Pwl Pwl::inverse(bool *trueInverse, const double eps) const return inverse; } +/* + * \brief Compose two piecewise linear functions together + * \param[in] other The "other" piecewise linear function + * \param[in] eps Epsilon (optiona) + * + * The "this" function is done first, and "other" after. + * + * \return The composed piecewise linear function + */ Pwl Pwl::compose(Pwl const &other, const double eps) const { double thisX = points_[0].x, thisY = points_[0].y; int thisSpan = 0, otherSpan = other.findSpan(thisY, 0); Pwl result({ { thisX, other.eval(thisY, &otherSpan, false) } }); + while (thisSpan != (int)points_.size() - 1) { double dx = points_[thisSpan + 1].x - points_[thisSpan].x, dy = points_[thisSpan + 1].y - points_[thisSpan].y; if (std::abs(dy) > eps && otherSpan + 1 < (int)other.points_.size() && - points_[thisSpan + 1].y >= - other.points_[otherSpan + 1].x + eps) { + points_[thisSpan + 1].y >= other.points_[otherSpan + 1].x + eps) { /* * next control point in result will be where this * function's y reaches the next span in other @@ -204,18 +278,24 @@ Pwl Pwl::compose(Pwl const &other, const double eps) const return result; } +/* \brief Apply function to (x,y) values at every control point. */ void Pwl::map(std::function f) const { for (auto &pt : points_) f(pt.x, pt.y); } +/* + * \brief Apply function to (x, y0, y1) values wherever either Pwl has a + * control point. + */ void Pwl::map2(Pwl const &pwl0, Pwl const &pwl1, std::function f) { int span0 = 0, span1 = 0; double x = std::min(pwl0.points_[0].x, pwl1.points_[0].x); f(x, pwl0.eval(x, &span0, false), pwl1.eval(x, &span1, false)); + while (span0 < (int)pwl0.points_.size() - 1 || span1 < (int)pwl1.points_.size() - 1) { if (span0 == (int)pwl0.points_.size() - 1) @@ -230,6 +310,12 @@ void Pwl::map2(Pwl const &pwl0, Pwl const &pwl1, } } +/* + * \brief Combine two Pwls + * + * Create a new Pwl where the y values are given by running f wherever either + * has a knot. + */ Pwl Pwl::combine(Pwl const &pwl0, Pwl const &pwl1, std::function f, const double eps) @@ -241,7 +327,19 @@ Pwl Pwl::combine(Pwl const &pwl0, Pwl const &pwl1, return result; } -void Pwl::matchDomain(Interval const &domain, bool clip, const double eps) +/* + * \brief Extend the domain of the piecewise linear function + * \param[in] domain The domain to extend to + * \param[in] clip True to keep the existing edge y values, false to extrapolate + * \param[in] eps Epsilon + * + * Extend the domain of the piecewise linear function to match \a domain. If \a + * clip is set to true then the y values of the new edges will be the same as + * the existing y values of the edge points of the pwl. If false, then the y + * values will be extrapolated linearly from the existing edge points of the + * pwl. + */ +void Pwl::extendDomain(Interval const &domain, bool clip, const double eps) { int span = 0; prepend(domain.start, eval(clip ? points_[0].x : domain.start, &span), @@ -258,10 +356,16 @@ Pwl &Pwl::operator*=(double d) return *this; } -void Pwl::debug(FILE *fp) const +std::string Pwl::toString() const { - fprintf(fp, "Pwl {\n"); + std::stringstream ss; + ss << "Pwl { "; for (auto &p : points_) - fprintf(fp, "\t(%g, %g)\n", p.x, p.y); - fprintf(fp, "}\n"); + ss << "(" << p.x << ", " << p.y << ") "; + ss << "}"; + return ss.str(); } + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/pwl.h b/src/ipa/libipa/pwl.h index 7a6a6452..8c8abb51 100644 --- a/src/ipa/libipa/pwl.h +++ b/src/ipa/libipa/pwl.h @@ -8,116 +8,91 @@ #include #include +#include #include +#include + #include "libcamera/internal/yaml_parser.h" +namespace libcamera { + +namespace ipa { + class Pwl { public: + enum class PerpType { + None, + Start, + End, + Vertex, + Perpendicular, + }; + struct Interval { Interval(double _start, double _end) - : start(_start), end(_end) - { - } - double start, end; + : start(_start), end(_end) {} + bool contains(double value) { return value >= start && value <= end; } - double clip(double value) + + double clamp(double value) { return value < start ? start : (value > end ? end : value); } + double len() const { return end - start; } + + double start, end; }; - struct Point { - Point() : x(0), y(0) {} - Point(double _x, double _y) - : x(_x), y(_y) {} - double x, y; - Point operator-(Point const &p) const - { - return Point(x - p.x, y - p.y); - } - Point operator+(Point const &p) const - { - return Point(x + p.x, y + p.y); - } - double operator%(Point const &p) const - { - return x * p.x + y * p.y; - } - Point operator*(double f) const { return Point(x * f, y * f); } - Point operator/(double f) const { return Point(x / f, y / f); } - double len2() const { return x * x + y * y; } - double len() const { return sqrt(len2()); } - }; + Pwl() {} - Pwl(std::vector const &points) : points_(points) {} - int read(const libcamera::YamlObject ¶ms); + Pwl(std::vector const &points) + : points_(points) {} + int readYaml(const libcamera::YamlObject ¶ms); + void append(double x, double y, const double eps = 1e-6); void prepend(double x, double y, const double eps = 1e-6); + Interval domain() const; Interval range() const; + bool empty() const; - /* - * Evaluate Pwl, optionally supplying an initial guess for the - * "span". The "span" may be optionally be updated. If you want to know - * the "span" value but don't have an initial guess you can set it to - * -1. - */ + double eval(double x, int *spanPtr = nullptr, bool updateSpan = true) const; - /* - * Find perpendicular closest to xy, starting from span+1 so you can - * call it repeatedly to check for multiple closest points (set span to - * -1 on the first call). Also returns "pseudo" perpendiculars; see - * PerpType enum. - */ - enum class PerpType { - None, /* no perpendicular found */ - Start, /* start of Pwl is closest point */ - End, /* end of Pwl is closest point */ - Vertex, /* vertex of Pwl is closest point */ - Perpendicular /* true perpendicular found */ - }; - PerpType invert(Point const &xy, Point &perp, int &span, + + PerpType invert(PointF const &xy, PointF &perp, int &span, const double eps = 1e-6) const; - /* - * Compute the inverse function. Indicate if it is a proper (true) - * inverse, or only a best effort (e.g. input was non-monotonic). - */ Pwl inverse(bool *trueInverse = nullptr, const double eps = 1e-6) const; - /* Compose two Pwls together, doing "this" first and "other" after. */ Pwl compose(Pwl const &other, const double eps = 1e-6) const; - /* Apply function to (x,y) values at every control point. */ + void map(std::function f) const; - /* - * Apply function to (x, y0, y1) values wherever either Pwl has a - * control point. - */ + static void map2(Pwl const &pwl0, Pwl const &pwl1, std::function f); - /* - * Combine two Pwls, meaning we create a new Pwl where the y values are - * given by running f wherever either has a knot. - */ + static Pwl combine(Pwl const &pwl0, Pwl const &pwl1, std::function f, const double eps = 1e-6); - /* - * Make "this" match (at least) the given domain. Any extension my be - * clipped or linear. - */ - void matchDomain(Interval const &domain, bool clip = true, - const double eps = 1e-6); + + void extendDomain(Interval const &domain, bool clip = true, + const double eps = 1e-6); + Pwl &operator*=(double d); - void debug(FILE *fp = stdout) const; + + std::string toString() const; private: int findSpan(double x, int span) const; - std::vector points_; + std::vector points_; }; + +} /* namespace ipa */ + +} /* namespace libcamera */ From patchwork Fri Apr 26 07:36:12 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 19959 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 71759C3220 for ; Fri, 26 Apr 2024 07:36:36 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0257B6341A; Fri, 26 Apr 2024 09:36:36 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="M2/p2ymo"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 3DC47633EA for ; Fri, 26 Apr 2024 09:36:32 +0200 (CEST) Received: from pyrite.hamster-moth.ts.net (h175-177-049-156.catv02.itscom.jp [175.177.49.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7898DD7E; Fri, 26 Apr 2024 09:35:37 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1714116939; bh=cDxkqQQD+vjgW0nrxNDQby6qhBBnoH2zk/axXJYBLEk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=M2/p2ymoTeaPrjKBJklnkRPIjKK/AVt8hrBK6M7A5RyQRl4geJBFXUInOgqLjviYh nFufUpHyKHE3yyJ/OnZzEJpyV5UR17SjjTIdG2BdJQhrXIZvr7rRkUy94Y6RF39zwn DlvuwFKMt9vHhoccurSdo8f6kf8ZfGv8fqtkCSzE= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder , Stefan Klug Subject: [PATCH v2 4/4] ipa: rpi: controller: Use libipa's Pwl class Date: Fri, 26 Apr 2024 16:36:12 +0900 Message-Id: <20240426073612.1230283-5-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240426073612.1230283-1-paul.elder@ideasonboard.com> References: <20240426073612.1230283-1-paul.elder@ideasonboard.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" To reduce code duplication, use the Pwl class from libipa. This also removes the Pwl class from the Raspberry Pi IPA. Signed-off-by: Paul Elder Reviewed-by: Stefan Klug --- Changes in v2: - s/FPoint/PointF/g --- src/ipa/rpi/controller/cac_status.h | 2 - src/ipa/rpi/controller/contrast_status.h | 4 +- src/ipa/rpi/controller/meson.build | 2 +- src/ipa/rpi/controller/pwl.cpp | 269 --------------------- src/ipa/rpi/controller/pwl.h | 127 ---------- src/ipa/rpi/controller/rpi/af.cpp | 4 +- src/ipa/rpi/controller/rpi/af.h | 5 +- src/ipa/rpi/controller/rpi/agc_channel.cpp | 8 +- src/ipa/rpi/controller/rpi/agc_channel.h | 7 +- src/ipa/rpi/controller/rpi/awb.cpp | 40 +-- src/ipa/rpi/controller/rpi/awb.h | 23 +- src/ipa/rpi/controller/rpi/ccm.cpp | 4 +- src/ipa/rpi/controller/rpi/ccm.h | 5 +- src/ipa/rpi/controller/rpi/contrast.cpp | 14 +- src/ipa/rpi/controller/rpi/contrast.h | 5 +- src/ipa/rpi/controller/rpi/geq.cpp | 5 +- src/ipa/rpi/controller/rpi/geq.h | 4 +- src/ipa/rpi/controller/rpi/hdr.cpp | 6 +- src/ipa/rpi/controller/rpi/hdr.h | 9 +- src/ipa/rpi/controller/rpi/tonemap.cpp | 2 +- src/ipa/rpi/controller/rpi/tonemap.h | 5 +- src/ipa/rpi/controller/tonemap_status.h | 4 +- 22 files changed, 83 insertions(+), 471 deletions(-) delete mode 100644 src/ipa/rpi/controller/pwl.cpp delete mode 100644 src/ipa/rpi/controller/pwl.h diff --git a/src/ipa/rpi/controller/cac_status.h b/src/ipa/rpi/controller/cac_status.h index 475d4c5c..adffce41 100644 --- a/src/ipa/rpi/controller/cac_status.h +++ b/src/ipa/rpi/controller/cac_status.h @@ -6,8 +6,6 @@ */ #pragma once -#include "pwl.h" - struct CacStatus { std::vector lutRx; std::vector lutRy; diff --git a/src/ipa/rpi/controller/contrast_status.h b/src/ipa/rpi/controller/contrast_status.h index fb9fe4ba..c9fbc3f6 100644 --- a/src/ipa/rpi/controller/contrast_status.h +++ b/src/ipa/rpi/controller/contrast_status.h @@ -6,7 +6,7 @@ */ #pragma once -#include "pwl.h" +#include "libipa/pwl.h" /* * The "contrast" algorithm creates a gamma curve, optionally doing a little bit @@ -14,7 +14,7 @@ */ struct ContrastStatus { - RPiController::Pwl gammaCurve; + libcamera::ipa::Pwl gammaCurve; double brightness; double contrast; }; diff --git a/src/ipa/rpi/controller/meson.build b/src/ipa/rpi/controller/meson.build index 32a4d31c..74b74888 100644 --- a/src/ipa/rpi/controller/meson.build +++ b/src/ipa/rpi/controller/meson.build @@ -5,7 +5,6 @@ rpi_ipa_controller_sources = files([ 'controller.cpp', 'device_status.cpp', 'histogram.cpp', - 'pwl.cpp', 'rpi/af.cpp', 'rpi/agc.cpp', 'rpi/agc_channel.cpp', @@ -32,4 +31,5 @@ rpi_ipa_controller_deps = [ ] rpi_ipa_controller_lib = static_library('rpi_ipa_controller', rpi_ipa_controller_sources, + include_directories : libipa_includes, dependencies : rpi_ipa_controller_deps) diff --git a/src/ipa/rpi/controller/pwl.cpp b/src/ipa/rpi/controller/pwl.cpp deleted file mode 100644 index 70c2e24b..00000000 --- a/src/ipa/rpi/controller/pwl.cpp +++ /dev/null @@ -1,269 +0,0 @@ -/* SPDX-License-Identifier: BSD-2-Clause */ -/* - * Copyright (C) 2019, Raspberry Pi Ltd - * - * pwl.cpp - piecewise linear functions - */ - -#include -#include -#include - -#include "pwl.h" - -using namespace RPiController; - -int Pwl::read(const libcamera::YamlObject ¶ms) -{ - if (!params.size() || params.size() % 2) - return -EINVAL; - - const auto &list = params.asList(); - - for (auto it = list.begin(); it != list.end(); it++) { - auto x = it->get(); - if (!x) - return -EINVAL; - if (it != list.begin() && *x <= points_.back().x) - return -EINVAL; - - auto y = (++it)->get(); - if (!y) - return -EINVAL; - - points_.push_back(Point(*x, *y)); - } - - return 0; -} - -void Pwl::append(double x, double y, const double eps) -{ - if (points_.empty() || points_.back().x + eps < x) - points_.push_back(Point(x, y)); -} - -void Pwl::prepend(double x, double y, const double eps) -{ - if (points_.empty() || points_.front().x - eps > x) - points_.insert(points_.begin(), Point(x, y)); -} - -Pwl::Interval Pwl::domain() const -{ - return Interval(points_[0].x, points_[points_.size() - 1].x); -} - -Pwl::Interval Pwl::range() const -{ - double lo = points_[0].y, hi = lo; - for (auto &p : points_) - lo = std::min(lo, p.y), hi = std::max(hi, p.y); - return Interval(lo, hi); -} - -bool Pwl::empty() const -{ - return points_.empty(); -} - -double Pwl::eval(double x, int *spanPtr, bool updateSpan) const -{ - int span = findSpan(x, spanPtr && *spanPtr != -1 ? *spanPtr : points_.size() / 2 - 1); - if (spanPtr && updateSpan) - *spanPtr = span; - return points_[span].y + - (x - points_[span].x) * (points_[span + 1].y - points_[span].y) / - (points_[span + 1].x - points_[span].x); -} - -int Pwl::findSpan(double x, int span) const -{ - /* - * Pwls are generally small, so linear search may well be faster than - * binary, though could review this if large PWls start turning up. - */ - int lastSpan = points_.size() - 2; - /* - * some algorithms may call us with span pointing directly at the last - * control point - */ - span = std::max(0, std::min(lastSpan, span)); - while (span < lastSpan && x >= points_[span + 1].x) - span++; - while (span && x < points_[span].x) - span--; - return span; -} - -Pwl::PerpType Pwl::invert(Point const &xy, Point &perp, int &span, - const double eps) const -{ - assert(span >= -1); - bool prevOffEnd = false; - for (span = span + 1; span < (int)points_.size() - 1; span++) { - Point spanVec = points_[span + 1] - points_[span]; - double t = ((xy - points_[span]) % spanVec) / spanVec.len2(); - if (t < -eps) /* off the start of this span */ - { - if (span == 0) { - perp = points_[span]; - return PerpType::Start; - } else if (prevOffEnd) { - perp = points_[span]; - return PerpType::Vertex; - } - } else if (t > 1 + eps) /* off the end of this span */ - { - if (span == (int)points_.size() - 2) { - perp = points_[span + 1]; - return PerpType::End; - } - prevOffEnd = true; - } else /* a true perpendicular */ - { - perp = points_[span] + spanVec * t; - return PerpType::Perpendicular; - } - } - return PerpType::None; -} - -Pwl Pwl::inverse(bool *trueInverse, const double eps) const -{ - bool appended = false, prepended = false, neither = false; - Pwl inverse; - - for (Point const &p : points_) { - if (inverse.empty()) - inverse.append(p.y, p.x, eps); - else if (std::abs(inverse.points_.back().x - p.y) <= eps || - std::abs(inverse.points_.front().x - p.y) <= eps) - /* do nothing */; - else if (p.y > inverse.points_.back().x) { - inverse.append(p.y, p.x, eps); - appended = true; - } else if (p.y < inverse.points_.front().x) { - inverse.prepend(p.y, p.x, eps); - prepended = true; - } else - neither = true; - } - - /* - * This is not a proper inverse if we found ourselves putting points - * onto both ends of the inverse, or if there were points that couldn't - * go on either. - */ - if (trueInverse) - *trueInverse = !(neither || (appended && prepended)); - - return inverse; -} - -Pwl Pwl::compose(Pwl const &other, const double eps) const -{ - double thisX = points_[0].x, thisY = points_[0].y; - int thisSpan = 0, otherSpan = other.findSpan(thisY, 0); - Pwl result({ { thisX, other.eval(thisY, &otherSpan, false) } }); - while (thisSpan != (int)points_.size() - 1) { - double dx = points_[thisSpan + 1].x - points_[thisSpan].x, - dy = points_[thisSpan + 1].y - points_[thisSpan].y; - if (std::abs(dy) > eps && - otherSpan + 1 < (int)other.points_.size() && - points_[thisSpan + 1].y >= - other.points_[otherSpan + 1].x + eps) { - /* - * next control point in result will be where this - * function's y reaches the next span in other - */ - thisX = points_[thisSpan].x + - (other.points_[otherSpan + 1].x - - points_[thisSpan].y) * - dx / dy; - thisY = other.points_[++otherSpan].x; - } else if (std::abs(dy) > eps && otherSpan > 0 && - points_[thisSpan + 1].y <= - other.points_[otherSpan - 1].x - eps) { - /* - * next control point in result will be where this - * function's y reaches the previous span in other - */ - thisX = points_[thisSpan].x + - (other.points_[otherSpan + 1].x - - points_[thisSpan].y) * - dx / dy; - thisY = other.points_[--otherSpan].x; - } else { - /* we stay in the same span in other */ - thisSpan++; - thisX = points_[thisSpan].x, - thisY = points_[thisSpan].y; - } - result.append(thisX, other.eval(thisY, &otherSpan, false), - eps); - } - return result; -} - -void Pwl::map(std::function f) const -{ - for (auto &pt : points_) - f(pt.x, pt.y); -} - -void Pwl::map2(Pwl const &pwl0, Pwl const &pwl1, - std::function f) -{ - int span0 = 0, span1 = 0; - double x = std::min(pwl0.points_[0].x, pwl1.points_[0].x); - f(x, pwl0.eval(x, &span0, false), pwl1.eval(x, &span1, false)); - while (span0 < (int)pwl0.points_.size() - 1 || - span1 < (int)pwl1.points_.size() - 1) { - if (span0 == (int)pwl0.points_.size() - 1) - x = pwl1.points_[++span1].x; - else if (span1 == (int)pwl1.points_.size() - 1) - x = pwl0.points_[++span0].x; - else if (pwl0.points_[span0 + 1].x > pwl1.points_[span1 + 1].x) - x = pwl1.points_[++span1].x; - else - x = pwl0.points_[++span0].x; - f(x, pwl0.eval(x, &span0, false), pwl1.eval(x, &span1, false)); - } -} - -Pwl Pwl::combine(Pwl const &pwl0, Pwl const &pwl1, - std::function f, - const double eps) -{ - Pwl result; - map2(pwl0, pwl1, [&](double x, double y0, double y1) { - result.append(x, f(x, y0, y1), eps); - }); - return result; -} - -void Pwl::matchDomain(Interval const &domain, bool clip, const double eps) -{ - int span = 0; - prepend(domain.start, eval(clip ? points_[0].x : domain.start, &span), - eps); - span = points_.size() - 2; - append(domain.end, eval(clip ? points_.back().x : domain.end, &span), - eps); -} - -Pwl &Pwl::operator*=(double d) -{ - for (auto &pt : points_) - pt.y *= d; - return *this; -} - -void Pwl::debug(FILE *fp) const -{ - fprintf(fp, "Pwl {\n"); - for (auto &p : points_) - fprintf(fp, "\t(%g, %g)\n", p.x, p.y); - fprintf(fp, "}\n"); -} diff --git a/src/ipa/rpi/controller/pwl.h b/src/ipa/rpi/controller/pwl.h deleted file mode 100644 index aacf6039..00000000 --- a/src/ipa/rpi/controller/pwl.h +++ /dev/null @@ -1,127 +0,0 @@ -/* SPDX-License-Identifier: BSD-2-Clause */ -/* - * Copyright (C) 2019, Raspberry Pi Ltd - * - * pwl.h - piecewise linear functions interface - */ -#pragma once - -#include -#include -#include - -#include "libcamera/internal/yaml_parser.h" - -namespace RPiController { - -class Pwl -{ -public: - struct Interval { - Interval(double _start, double _end) - : start(_start), end(_end) - { - } - double start, end; - bool contains(double value) - { - return value >= start && value <= end; - } - double clip(double value) - { - return value < start ? start - : (value > end ? end : value); - } - double len() const { return end - start; } - }; - struct Point { - Point() : x(0), y(0) {} - Point(double _x, double _y) - : x(_x), y(_y) {} - double x, y; - Point operator-(Point const &p) const - { - return Point(x - p.x, y - p.y); - } - Point operator+(Point const &p) const - { - return Point(x + p.x, y + p.y); - } - double operator%(Point const &p) const - { - return x * p.x + y * p.y; - } - Point operator*(double f) const { return Point(x * f, y * f); } - Point operator/(double f) const { return Point(x / f, y / f); } - double len2() const { return x * x + y * y; } - double len() const { return sqrt(len2()); } - }; - Pwl() {} - Pwl(std::vector const &points) : points_(points) {} - int read(const libcamera::YamlObject ¶ms); - void append(double x, double y, const double eps = 1e-6); - void prepend(double x, double y, const double eps = 1e-6); - Interval domain() const; - Interval range() const; - bool empty() const; - /* - * Evaluate Pwl, optionally supplying an initial guess for the - * "span". The "span" may be optionally be updated. If you want to know - * the "span" value but don't have an initial guess you can set it to - * -1. - */ - double eval(double x, int *spanPtr = nullptr, - bool updateSpan = true) const; - /* - * Find perpendicular closest to xy, starting from span+1 so you can - * call it repeatedly to check for multiple closest points (set span to - * -1 on the first call). Also returns "pseudo" perpendiculars; see - * PerpType enum. - */ - enum class PerpType { - None, /* no perpendicular found */ - Start, /* start of Pwl is closest point */ - End, /* end of Pwl is closest point */ - Vertex, /* vertex of Pwl is closest point */ - Perpendicular /* true perpendicular found */ - }; - PerpType invert(Point const &xy, Point &perp, int &span, - const double eps = 1e-6) const; - /* - * Compute the inverse function. Indicate if it is a proper (true) - * inverse, or only a best effort (e.g. input was non-monotonic). - */ - Pwl inverse(bool *trueInverse = nullptr, const double eps = 1e-6) const; - /* Compose two Pwls together, doing "this" first and "other" after. */ - Pwl compose(Pwl const &other, const double eps = 1e-6) const; - /* Apply function to (x,y) values at every control point. */ - void map(std::function f) const; - /* - * Apply function to (x, y0, y1) values wherever either Pwl has a - * control point. - */ - static void map2(Pwl const &pwl0, Pwl const &pwl1, - std::function f); - /* - * Combine two Pwls, meaning we create a new Pwl where the y values are - * given by running f wherever either has a knot. - */ - static Pwl - combine(Pwl const &pwl0, Pwl const &pwl1, - std::function f, - const double eps = 1e-6); - /* - * Make "this" match (at least) the given domain. Any extension my be - * clipped or linear. - */ - void matchDomain(Interval const &domain, bool clip = true, - const double eps = 1e-6); - Pwl &operator*=(double d); - void debug(FILE *fp = stdout) const; - -private: - int findSpan(double x, int span) const; - std::vector points_; -}; - -} /* namespace RPiController */ diff --git a/src/ipa/rpi/controller/rpi/af.cpp b/src/ipa/rpi/controller/rpi/af.cpp index ed0c8a94..d54cee69 100644 --- a/src/ipa/rpi/controller/rpi/af.cpp +++ b/src/ipa/rpi/controller/rpi/af.cpp @@ -139,7 +139,7 @@ int Af::CfgParams::read(const libcamera::YamlObject ¶ms) readNumber(skipFrames, params, "skip_frames"); if (params.contains("map")) - map.read(params["map"]); + map.readYaml(params["map"]); else LOG(RPiAf, Warning) << "No map defined"; @@ -721,7 +721,7 @@ bool Af::setLensPosition(double dioptres, int *hwpos) if (mode_ == AfModeManual) { LOG(RPiAf, Debug) << "setLensPosition: " << dioptres; - ftarget_ = cfg_.map.domain().clip(dioptres); + ftarget_ = cfg_.map.domain().clamp(dioptres); changed = !(initted_ && fsmooth_ == ftarget_); updateLensPosition(); } diff --git a/src/ipa/rpi/controller/rpi/af.h b/src/ipa/rpi/controller/rpi/af.h index 6d2bae67..a6b2ad07 100644 --- a/src/ipa/rpi/controller/rpi/af.h +++ b/src/ipa/rpi/controller/rpi/af.h @@ -9,7 +9,8 @@ #include "../af_algorithm.h" #include "../af_status.h" #include "../pdaf_data.h" -#include "../pwl.h" + +#include "libipa/pwl.h" /* * This algorithm implements a hybrid of CDAF and PDAF, favouring PDAF. @@ -100,7 +101,7 @@ private: uint32_t confThresh; /* PDAF confidence cell min (sensor-specific) */ uint32_t confClip; /* PDAF confidence cell max (sensor-specific) */ uint32_t skipFrames; /* frames to skip at start or modeswitch */ - Pwl map; /* converts dioptres -> lens driver position */ + libcamera::ipa::Pwl map; /* converts dioptres -> lens driver position */ CfgParams(); int read(const libcamera::YamlObject ¶ms); diff --git a/src/ipa/rpi/controller/rpi/agc_channel.cpp b/src/ipa/rpi/controller/rpi/agc_channel.cpp index 8116c6c1..e6d292f9 100644 --- a/src/ipa/rpi/controller/rpi/agc_channel.cpp +++ b/src/ipa/rpi/controller/rpi/agc_channel.cpp @@ -130,7 +130,7 @@ int AgcConstraint::read(const libcamera::YamlObject ¶ms) return -EINVAL; qHi = *value; - return yTarget.read(params["y_target"]); + return yTarget.readYaml(params["y_target"]); } static std::tuple @@ -237,7 +237,7 @@ int AgcConfig::read(const libcamera::YamlObject ¶ms) return ret; } - ret = yTarget.read(params["y_target"]); + ret = yTarget.readYaml(params["y_target"]); if (ret) return ret; @@ -715,7 +715,7 @@ static constexpr double EvGainYTargetLimit = 0.9; static double constraintComputeGain(AgcConstraint &c, const Histogram &h, double lux, double evGain, double &targetY) { - targetY = c.yTarget.eval(c.yTarget.domain().clip(lux)); + targetY = c.yTarget.eval(c.yTarget.domain().clamp(lux)); targetY = std::min(EvGainYTargetLimit, targetY * evGain); double iqm = h.interQuantileMean(c.qLo, c.qHi); return (targetY * h.bins()) / iqm; @@ -734,7 +734,7 @@ void AgcChannel::computeGain(StatisticsPtr &statistics, Metadata *imageMetadata, * The initial gain and target_Y come from some of the regions. After * that we consider the histogram constraints. */ - targetY = config_.yTarget.eval(config_.yTarget.domain().clip(lux.lux)); + targetY = config_.yTarget.eval(config_.yTarget.domain().clamp(lux.lux)); targetY = std::min(EvGainYTargetLimit, targetY * evGain); /* diff --git a/src/ipa/rpi/controller/rpi/agc_channel.h b/src/ipa/rpi/controller/rpi/agc_channel.h index 4cf7233e..157b3baf 100644 --- a/src/ipa/rpi/controller/rpi/agc_channel.h +++ b/src/ipa/rpi/controller/rpi/agc_channel.h @@ -12,10 +12,11 @@ #include +#include + #include "../agc_status.h" #include "../awb_status.h" #include "../controller.h" -#include "../pwl.h" /* This is our implementation of AGC. */ @@ -40,7 +41,7 @@ struct AgcConstraint { Bound bound; double qLo; double qHi; - Pwl yTarget; + libcamera::ipa::Pwl yTarget; int read(const libcamera::YamlObject ¶ms); }; @@ -61,7 +62,7 @@ struct AgcConfig { std::map exposureModes; std::map constraintModes; std::vector channelConstraints; - Pwl yTarget; + libcamera::ipa::Pwl yTarget; double speed; uint16_t startupFrames; unsigned int convergenceFrames; diff --git a/src/ipa/rpi/controller/rpi/awb.cpp b/src/ipa/rpi/controller/rpi/awb.cpp index dde5785a..9474fcef 100644 --- a/src/ipa/rpi/controller/rpi/awb.cpp +++ b/src/ipa/rpi/controller/rpi/awb.cpp @@ -49,10 +49,10 @@ int AwbPrior::read(const libcamera::YamlObject ¶ms) return -EINVAL; lux = *value; - return prior.read(params["prior"]); + return prior.readYaml(params["prior"]); } -static int readCtCurve(Pwl &ctR, Pwl &ctB, const libcamera::YamlObject ¶ms) +static int readCtCurve(ipa::Pwl &ctR, ipa::Pwl &ctB, const libcamera::YamlObject ¶ms) { if (params.size() % 3) { LOG(RPiAwb, Error) << "AwbConfig: incomplete CT curve entry"; @@ -207,7 +207,7 @@ void Awb::initialise() * them. */ if (!config_.ctR.empty() && !config_.ctB.empty()) { - syncResults_.temperatureK = config_.ctR.domain().clip(4000); + syncResults_.temperatureK = config_.ctR.domain().clamp(4000); syncResults_.gainR = 1.0 / config_.ctR.eval(syncResults_.temperatureK); syncResults_.gainG = 1.0; syncResults_.gainB = 1.0 / config_.ctB.eval(syncResults_.temperatureK); @@ -273,8 +273,8 @@ void Awb::setManualGains(double manualR, double manualB) syncResults_.gainB = prevSyncResults_.gainB = manualB_; if (config_.bayes) { /* Also estimate the best corresponding colour temperature from the curves. */ - double ctR = config_.ctRInverse.eval(config_.ctRInverse.domain().clip(1 / manualR_)); - double ctB = config_.ctBInverse.eval(config_.ctBInverse.domain().clip(1 / manualB_)); + double ctR = config_.ctRInverse.eval(config_.ctRInverse.domain().clamp(1 / manualR_)); + double ctB = config_.ctBInverse.eval(config_.ctBInverse.domain().clamp(1 / manualB_)); prevSyncResults_.temperatureK = (ctR + ctB) / 2; syncResults_.temperatureK = prevSyncResults_.temperatureK; } @@ -468,7 +468,7 @@ double Awb::computeDelta2Sum(double gainR, double gainB) return delta2Sum; } -Pwl Awb::interpolatePrior() +ipa::Pwl Awb::interpolatePrior() { /* * Interpolate the prior log likelihood function for our current lux @@ -485,7 +485,7 @@ Pwl Awb::interpolatePrior() idx++; double lux0 = config_.priors[idx].lux, lux1 = config_.priors[idx + 1].lux; - return Pwl::combine(config_.priors[idx].prior, + return ipa::Pwl::combine(config_.priors[idx].prior, config_.priors[idx + 1].prior, [&](double /*x*/, double y0, double y1) { return y0 + (y1 - y0) * @@ -494,15 +494,15 @@ Pwl Awb::interpolatePrior() } } -static double interpolateQuadatric(Pwl::Point const &a, Pwl::Point const &b, - Pwl::Point const &c) +static double interpolateQuadatric(PointF const &a, PointF const &b, + PointF const &c) { /* * Given 3 points on a curve, find the extremum of the function in that * interval by fitting a quadratic. */ const double eps = 1e-3; - Pwl::Point ca = c - a, ba = b - a; + PointF ca = c - a, ba = b - a; double denominator = 2 * (ba.y * ca.x - ca.y * ba.x); if (abs(denominator) > eps) { double numerator = ba.y * ca.x * ca.x - ca.y * ba.x * ba.x; @@ -513,7 +513,7 @@ static double interpolateQuadatric(Pwl::Point const &a, Pwl::Point const &b, return a.y < c.y - eps ? a.x : (c.y < a.y - eps ? c.x : b.x); } -double Awb::coarseSearch(Pwl const &prior) +double Awb::coarseSearch(ipa::Pwl const &prior) { points_.clear(); /* assume doesn't deallocate memory */ size_t bestPoint = 0; @@ -525,14 +525,14 @@ double Awb::coarseSearch(Pwl const &prior) double b = config_.ctB.eval(t, &spanB); double gainR = 1 / r, gainB = 1 / b; double delta2Sum = computeDelta2Sum(gainR, gainB); - double priorLogLikelihood = prior.eval(prior.domain().clip(t)); + double priorLogLikelihood = prior.eval(prior.domain().clamp(t)); double finalLogLikelihood = delta2Sum - priorLogLikelihood; LOG(RPiAwb, Debug) << "t: " << t << " gain R " << gainR << " gain B " << gainB << " delta2_sum " << delta2Sum << " prior " << priorLogLikelihood << " final " << finalLogLikelihood; - points_.push_back(Pwl::Point(t, finalLogLikelihood)); + points_.push_back(PointF(t, finalLogLikelihood)); if (points_.back().y < points_[bestPoint].y) bestPoint = points_.size() - 1; if (t == mode_->ctHi) @@ -559,7 +559,7 @@ double Awb::coarseSearch(Pwl const &prior) return t; } -void Awb::fineSearch(double &t, double &r, double &b, Pwl const &prior) +void Awb::fineSearch(double &t, double &r, double &b, ipa::Pwl const &prior) { int spanR = -1, spanB = -1; config_.ctR.eval(t, &spanR); @@ -570,7 +570,7 @@ void Awb::fineSearch(double &t, double &r, double &b, Pwl const &prior) config_.ctR.eval(t - nsteps * step, &spanR); double bDiff = config_.ctB.eval(t + nsteps * step, &spanB) - config_.ctB.eval(t - nsteps * step, &spanB); - Pwl::Point transverse(bDiff, -rDiff); + PointF transverse(bDiff, -rDiff); if (transverse.len2() < 1e-6) return; /* @@ -592,17 +592,17 @@ void Awb::fineSearch(double &t, double &r, double &b, Pwl const &prior) for (int i = -nsteps; i <= nsteps; i++) { double tTest = t + i * step; double priorLogLikelihood = - prior.eval(prior.domain().clip(tTest)); + prior.eval(prior.domain().clamp(tTest)); double rCurve = config_.ctR.eval(tTest, &spanR); double bCurve = config_.ctB.eval(tTest, &spanB); /* x will be distance off the curve, y the log likelihood there */ - Pwl::Point points[maxNumDeltas]; + PointF points[maxNumDeltas]; int bestPoint = 0; /* Take some measurements transversely *off* the CT curve. */ for (int j = 0; j < numDeltas; j++) { points[j].x = -config_.transverseNeg + (transverseRange * j) / (numDeltas - 1); - Pwl::Point rbTest = Pwl::Point(rCurve, bCurve) + + PointF rbTest = PointF(rCurve, bCurve) + transverse * points[j].x; double rTest = rbTest.x, bTest = rbTest.y; double gainR = 1 / rTest, gainB = 1 / bTest; @@ -619,7 +619,7 @@ void Awb::fineSearch(double &t, double &r, double &b, Pwl const &prior) * now let's do a quadratic interpolation for the best result. */ bestPoint = std::max(1, std::min(bestPoint, numDeltas - 2)); - Pwl::Point rbTest = Pwl::Point(rCurve, bCurve) + + PointF rbTest = PointF(rCurve, bCurve) + transverse * interpolateQuadatric(points[bestPoint - 1], points[bestPoint], points[bestPoint + 1]); @@ -653,7 +653,7 @@ void Awb::awbBayes() * Get the current prior, and scale according to how many zones are * valid... not entirely sure about this. */ - Pwl prior = interpolatePrior(); + ipa::Pwl prior = interpolatePrior(); prior *= zones_.size() / (double)(statistics_->awbRegions.numRegions()); prior.map([](double x, double y) { LOG(RPiAwb, Debug) << "(" << x << "," << y << ")"; diff --git a/src/ipa/rpi/controller/rpi/awb.h b/src/ipa/rpi/controller/rpi/awb.h index cde6a62f..a4445c1b 100644 --- a/src/ipa/rpi/controller/rpi/awb.h +++ b/src/ipa/rpi/controller/rpi/awb.h @@ -10,11 +10,14 @@ #include #include +#include + #include "../awb_algorithm.h" -#include "../pwl.h" #include "../awb_status.h" #include "../statistics.h" +#include "libipa/pwl.h" + namespace RPiController { /* Control algorithm to perform AWB calculations. */ @@ -28,7 +31,7 @@ struct AwbMode { struct AwbPrior { int read(const libcamera::YamlObject ¶ms); double lux; /* lux level */ - Pwl prior; /* maps CT to prior log likelihood for this lux level */ + libcamera::ipa::Pwl prior; /* maps CT to prior log likelihood for this lux level */ }; struct AwbConfig { @@ -41,10 +44,10 @@ struct AwbConfig { unsigned int convergenceFrames; /* approx number of frames to converge */ double speed; /* IIR filter speed applied to algorithm results */ bool fast; /* "fast" mode uses a 16x16 rather than 32x32 grid */ - Pwl ctR; /* function maps CT to r (= R/G) */ - Pwl ctB; /* function maps CT to b (= B/G) */ - Pwl ctRInverse; /* inverse of ctR */ - Pwl ctBInverse; /* inverse of ctB */ + libcamera::ipa::Pwl ctR; /* function maps CT to r (= R/G) */ + libcamera::ipa::Pwl ctB; /* function maps CT to b (= B/G) */ + libcamera::ipa::Pwl ctRInverse; /* inverse of ctR */ + libcamera::ipa::Pwl ctBInverse; /* inverse of ctB */ /* table of illuminant priors at different lux levels */ std::vector priors; /* AWB "modes" (determines the search range) */ @@ -161,11 +164,11 @@ private: void awbGrey(); void prepareStats(); double computeDelta2Sum(double gainR, double gainB); - Pwl interpolatePrior(); - double coarseSearch(Pwl const &prior); - void fineSearch(double &t, double &r, double &b, Pwl const &prior); + libcamera::ipa::Pwl interpolatePrior(); + double coarseSearch(libcamera::ipa::Pwl const &prior); + void fineSearch(double &t, double &r, double &b, libcamera::ipa::Pwl const &prior); std::vector zones_; - std::vector points_; + std::vector points_; /* manual r setting */ double manualR_; /* manual b setting */ diff --git a/src/ipa/rpi/controller/rpi/ccm.cpp b/src/ipa/rpi/controller/rpi/ccm.cpp index 2e2e6664..68f9cff6 100644 --- a/src/ipa/rpi/controller/rpi/ccm.cpp +++ b/src/ipa/rpi/controller/rpi/ccm.cpp @@ -71,7 +71,7 @@ int Ccm::read(const libcamera::YamlObject ¶ms) int ret; if (params.contains("saturation")) { - ret = config_.saturation.read(params["saturation"]); + ret = config_.saturation.readYaml(params["saturation"]); if (ret) return ret; } @@ -172,7 +172,7 @@ void Ccm::prepare(Metadata *imageMetadata) ccmStatus.saturation = saturation; if (!config_.saturation.empty()) saturation *= config_.saturation.eval( - config_.saturation.domain().clip(lux.lux)); + config_.saturation.domain().clamp(lux.lux)); ccm = applySaturation(ccm, saturation); for (int j = 0; j < 3; j++) for (int i = 0; i < 3; i++) diff --git a/src/ipa/rpi/controller/rpi/ccm.h b/src/ipa/rpi/controller/rpi/ccm.h index 286d0b33..e5042176 100644 --- a/src/ipa/rpi/controller/rpi/ccm.h +++ b/src/ipa/rpi/controller/rpi/ccm.h @@ -8,8 +8,9 @@ #include +#include + #include "../ccm_algorithm.h" -#include "../pwl.h" namespace RPiController { @@ -54,7 +55,7 @@ struct CtCcm { struct CcmConfig { std::vector ccms; - Pwl saturation; + libcamera::ipa::Pwl saturation; }; class Ccm : public CcmAlgorithm diff --git a/src/ipa/rpi/controller/rpi/contrast.cpp b/src/ipa/rpi/controller/rpi/contrast.cpp index 4e038a02..7ba6cef0 100644 --- a/src/ipa/rpi/controller/rpi/contrast.cpp +++ b/src/ipa/rpi/controller/rpi/contrast.cpp @@ -53,7 +53,7 @@ int Contrast::read(const libcamera::YamlObject ¶ms) config_.hiHistogram = params["hi_histogram"].get(0.95); config_.hiLevel = params["hi_level"].get(0.95); config_.hiMax = params["hi_max"].get(2000); - return config_.gammaCurve.read(params["gamma_curve"]); + return config_.gammaCurve.readYaml(params["gamma_curve"]); } void Contrast::setBrightness(double brightness) @@ -92,10 +92,10 @@ void Contrast::prepare(Metadata *imageMetadata) imageMetadata->set("contrast.status", status_); } -Pwl computeStretchCurve(Histogram const &histogram, +ipa::Pwl computeStretchCurve(Histogram const &histogram, ContrastConfig const &config) { - Pwl enhance; + ipa::Pwl enhance; enhance.append(0, 0); /* * If the start of the histogram is rather empty, try to pull it down a @@ -136,10 +136,10 @@ Pwl computeStretchCurve(Histogram const &histogram, return enhance; } -Pwl applyManualContrast(Pwl const &gammaCurve, double brightness, - double contrast) +ipa::Pwl applyManualContrast(ipa::Pwl const &gammaCurve, double brightness, + double contrast) { - Pwl newGammaCurve; + ipa::Pwl newGammaCurve; LOG(RPiContrast, Debug) << "Manual brightness " << brightness << " contrast " << contrast; gammaCurve.map([&](double x, double y) { @@ -160,7 +160,7 @@ void Contrast::process(StatisticsPtr &stats, * ways: 1. Adjust the gamma curve so as to pull the start of the * histogram down, and possibly push the end up. */ - Pwl gammaCurve = config_.gammaCurve; + ipa::Pwl gammaCurve = config_.gammaCurve; if (ceEnable_) { if (config_.loMax != 0 || config_.hiMax != 0) gammaCurve = computeStretchCurve(histogram, config_).compose(gammaCurve); diff --git a/src/ipa/rpi/controller/rpi/contrast.h b/src/ipa/rpi/controller/rpi/contrast.h index 59aa70dc..c61bfb4d 100644 --- a/src/ipa/rpi/controller/rpi/contrast.h +++ b/src/ipa/rpi/controller/rpi/contrast.h @@ -8,8 +8,9 @@ #include +#include + #include "../contrast_algorithm.h" -#include "../pwl.h" namespace RPiController { @@ -26,7 +27,7 @@ struct ContrastConfig { double hiHistogram; double hiLevel; double hiMax; - Pwl gammaCurve; + libcamera::ipa::Pwl gammaCurve; }; class Contrast : public ContrastAlgorithm diff --git a/src/ipa/rpi/controller/rpi/geq.cpp b/src/ipa/rpi/controller/rpi/geq.cpp index 510870e9..a0c350ec 100644 --- a/src/ipa/rpi/controller/rpi/geq.cpp +++ b/src/ipa/rpi/controller/rpi/geq.cpp @@ -9,7 +9,6 @@ #include "../device_status.h" #include "../lux_status.h" -#include "../pwl.h" #include "geq.h" @@ -45,7 +44,7 @@ int Geq::read(const libcamera::YamlObject ¶ms) } if (params.contains("strength")) { - int ret = config_.strength.read(params["strength"]); + int ret = config_.strength.readYaml(params["strength"]); if (ret) return ret; } @@ -67,7 +66,7 @@ void Geq::prepare(Metadata *imageMetadata) GeqStatus geqStatus = {}; double strength = config_.strength.empty() ? 1.0 - : config_.strength.eval(config_.strength.domain().clip(luxStatus.lux)); + : config_.strength.eval(config_.strength.domain().clamp(luxStatus.lux)); strength *= deviceStatus.analogueGain; double offset = config_.offset * strength; double slope = config_.slope * strength; diff --git a/src/ipa/rpi/controller/rpi/geq.h b/src/ipa/rpi/controller/rpi/geq.h index ee3a52ff..5ae29edc 100644 --- a/src/ipa/rpi/controller/rpi/geq.h +++ b/src/ipa/rpi/controller/rpi/geq.h @@ -6,6 +6,8 @@ */ #pragma once +#include + #include "../algorithm.h" #include "../geq_status.h" @@ -16,7 +18,7 @@ namespace RPiController { struct GeqConfig { uint16_t offset; double slope; - Pwl strength; /* lux to strength factor */ + libcamera::ipa::Pwl strength; /* lux to strength factor */ }; class Geq : public Algorithm diff --git a/src/ipa/rpi/controller/rpi/hdr.cpp b/src/ipa/rpi/controller/rpi/hdr.cpp index fb580548..b3ff2e25 100644 --- a/src/ipa/rpi/controller/rpi/hdr.cpp +++ b/src/ipa/rpi/controller/rpi/hdr.cpp @@ -40,7 +40,7 @@ void HdrConfig::read(const libcamera::YamlObject ¶ms, const std::string &mod /* Lens shading related parameters. */ if (params.contains("spatial_gain")) { - spatialGain.read(params["spatial_gain"]); + spatialGain.readYaml(params["spatial_gain"]); diffusion = params["diffusion"].get(3); /* Clip to an arbitrary limit just to stop typos from killing the system! */ const unsigned int MAX_DIFFUSION = 15; @@ -57,7 +57,7 @@ void HdrConfig::read(const libcamera::YamlObject ¶ms, const std::string &mod iirStrength = params["iir_strength"].get(8.0); strength = params["strength"].get(1.5); if (tonemapEnable) - tonemap.read(params["tonemap"]); + tonemap.readYaml(params["tonemap"]); /* Read any stitch parameters. */ stitchEnable = params["stitch_enable"].get(0); @@ -183,7 +183,7 @@ bool Hdr::updateTonemap([[maybe_unused]] StatisticsPtr &stats, HdrConfig &config /* When there's a change of HDR mode we start over with a new tonemap curve. */ if (delayedStatus_.mode != previousMode_) { previousMode_ = delayedStatus_.mode; - tonemap_ = Pwl(); + tonemap_ = ipa::Pwl(); } /* No tonemapping. No need to output a tonemap.status. */ diff --git a/src/ipa/rpi/controller/rpi/hdr.h b/src/ipa/rpi/controller/rpi/hdr.h index 980aa3d1..991fa690 100644 --- a/src/ipa/rpi/controller/rpi/hdr.h +++ b/src/ipa/rpi/controller/rpi/hdr.h @@ -12,9 +12,10 @@ #include +#include + #include "../hdr_algorithm.h" #include "../hdr_status.h" -#include "../pwl.h" /* This is our implementation of an HDR algorithm. */ @@ -26,7 +27,7 @@ struct HdrConfig { std::map channelMap; /* Lens shading related parameters. */ - Pwl spatialGain; /* Brightness to gain curve for different image regions. */ + libcamera::ipa::Pwl spatialGain; /* Brightness to gain curve for different image regions. */ unsigned int diffusion; /* How much to diffuse the gain spatially. */ /* Tonemap related parameters. */ @@ -35,7 +36,7 @@ struct HdrConfig { double detailSlope; double iirStrength; double strength; - Pwl tonemap; + libcamera::ipa::Pwl tonemap; /* Stitch related parameters. */ bool stitchEnable; @@ -67,7 +68,7 @@ private: HdrStatus status_; /* track the current HDR mode and channel */ HdrStatus delayedStatus_; /* track the delayed HDR mode and channel */ std::string previousMode_; - Pwl tonemap_; + libcamera::ipa::Pwl tonemap_; libcamera::Size regions_; /* stats regions */ unsigned int numRegions_; /* total number of stats regions */ std::vector gains_[2]; diff --git a/src/ipa/rpi/controller/rpi/tonemap.cpp b/src/ipa/rpi/controller/rpi/tonemap.cpp index 5f8b2bf2..f7a47fe0 100644 --- a/src/ipa/rpi/controller/rpi/tonemap.cpp +++ b/src/ipa/rpi/controller/rpi/tonemap.cpp @@ -33,7 +33,7 @@ int Tonemap::read(const libcamera::YamlObject ¶ms) config_.detailSlope = params["detail_slope"].get(0.1); config_.iirStrength = params["iir_strength"].get(1.0); config_.strength = params["strength"].get(1.0); - config_.tonemap.read(params["tone_curve"]); + config_.tonemap.readYaml(params["tone_curve"]); return 0; } diff --git a/src/ipa/rpi/controller/rpi/tonemap.h b/src/ipa/rpi/controller/rpi/tonemap.h index f25aa47f..ba0cf5c4 100644 --- a/src/ipa/rpi/controller/rpi/tonemap.h +++ b/src/ipa/rpi/controller/rpi/tonemap.h @@ -6,8 +6,9 @@ */ #pragma once +#include + #include "algorithm.h" -#include "pwl.h" namespace RPiController { @@ -16,7 +17,7 @@ struct TonemapConfig { double detailSlope; double iirStrength; double strength; - Pwl tonemap; + libcamera::ipa::Pwl tonemap; }; class Tonemap : public Algorithm diff --git a/src/ipa/rpi/controller/tonemap_status.h b/src/ipa/rpi/controller/tonemap_status.h index 0e639946..e51a2b6c 100644 --- a/src/ipa/rpi/controller/tonemap_status.h +++ b/src/ipa/rpi/controller/tonemap_status.h @@ -6,12 +6,12 @@ */ #pragma once -#include "pwl.h" +#include struct TonemapStatus { uint16_t detailConstant; double detailSlope; double iirStrength; double strength; - RPiController::Pwl tonemap; + libcamera::ipa::Pwl tonemap; };