From patchwork Wed May 29 19:26:08 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 20127 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 CA2A7BDE6B for ; Wed, 29 May 2024 19:26:30 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 75B07634BB; Wed, 29 May 2024 21:26:30 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="pAXLj4s/"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 3F4D36347E for ; Wed, 29 May 2024 21:26:28 +0200 (CEST) Received: from neptunite.hamster-moth.ts.net (h175-177-049-156.catv02.itscom.jp [175.177.49.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id CDD0A2D31; Wed, 29 May 2024 21:26:22 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1717010784; bh=IuTxl67ABbyA4wUlOjmbwphef6+ZzuUjvdZZlGKiwW4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=pAXLj4s/F5LNVN4EDP7o3gTFnJUmw5NfIiGeMPzD2TcmaZnOSmxJkqT3covM6cepO 7N4VZIH0HYBlPGtxUIYg+yymvDHm6FyrzaWBQWvuqwmVBwcOaCaAX/6ZnchYb2QdLs zUIXwFS8lqzA+G7mSDHl5R+HUbkGsysI/1vujHns= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder , Naushir Patuck , David Plowman , Stefan Klug Subject: [PATCH v3 1/4] libcamera: geometry: Add floating-point version of Point class Date: Thu, 30 May 2024 04:26:08 +0900 Message-Id: <20240529192612.814515-2-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240529192612.814515-1-paul.elder@ideasonboard.com> References: <20240529192612.814515-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 integers, 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 PointF 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 v3: - fix typoes in commit message 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 3e6f0f5d7..81a697398 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 000151364..2ab4c60f4 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 64169206a..54f1ce4ab 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 Wed May 29 19:26: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: 20128 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 8155EC32C8 for ; Wed, 29 May 2024 19:26:32 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 267BD634BE; Wed, 29 May 2024 21:26:32 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="asaVoOju"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id C41BB634B6 for ; Wed, 29 May 2024 21:26:30 +0200 (CEST) Received: from neptunite.hamster-moth.ts.net (h175-177-049-156.catv02.itscom.jp [175.177.49.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 405392D5D; Wed, 29 May 2024 21:26:25 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1717010787; bh=cvxKzFo+yQpTdZfzrEKpYKn57bkaabbdHVF/dwqX7vE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=asaVoOjuASC6pCSuZTExDM/vM7mUzSVpvT0CRvtSqoeZv4sJ9riBqRFbS41hW0Re8 FwKMteHjN5IrpoW2d/wEKJfzTkhfVYPg+PZ/7malMOC97crP+Nym55tWXGyTGHtTHB 1CI+8jcv/u6wQIf3xX1pWMdPjyIN7yOm64ZM+2oU= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder , Naushir Patuck , David Plowman , Stefan Klug Subject: [PATCH v3 2/4] ipa: libipa: Copy pwl from rpi Date: Thu, 30 May 2024 04:26:09 +0900 Message-Id: <20240529192612.814515-3-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240529192612.814515-1-paul.elder@ideasonboard.com> References: <20240529192612.814515-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 v3 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 7ce885da7..1b3faf8d5 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 000000000..09f5d65c0 --- /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 000000000..7a6a64523 --- /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 Wed May 29 19:26: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: 20129 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 76A93BDE6B for ; Wed, 29 May 2024 19:26:36 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E37C2634C2; Wed, 29 May 2024 21:26:35 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="OiCF80yE"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0EDC5634BB for ; Wed, 29 May 2024 21:26:33 +0200 (CEST) Received: from neptunite.hamster-moth.ts.net (h175-177-049-156.catv02.itscom.jp [175.177.49.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id A87D7149B; Wed, 29 May 2024 21:26:27 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1717010789; bh=c6ud6JBRzUAaJCsL31hrnx1QPS9jcz8JzE+ZS/yw0/M=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=OiCF80yED0FTnZJVnJ888M1Aj6ca54qp0rS9e2yHECPFD+IIIN0sLdu/Mwr2OQzrD tG4MOdKLMiensG8vPGD2NPA5q9m7j2kaPcUDYYFCB0+Cw0F9IFWAs/SoH1D7lpQ2ab YVZssFS7NDMlFS8wj8n2O+275UgWWZzQgBtO93xc= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder , Naushir Patuck , David Plowman , Stefan Klug Subject: [PATCH v3 3/4] ipa: libipa: pwl: Clean up Pwl class to match libcamera Date: Thu, 30 May 2024 04:26:10 +0900 Message-Id: <20240529192612.814515-4-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240529192612.814515-1-paul.elder@ideasonboard.com> References: <20240529192612.814515-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 --- No change in v3 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 09f5d65c0..4faf9c316 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 7a6a64523..8c8abb51c 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 Wed May 29 19:26: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: 20130 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 8A12BC32C8 for ; Wed, 29 May 2024 19:26:38 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 35EFF634C1; Wed, 29 May 2024 21:26:38 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="quT2p5Ew"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id BB1AA634BE for ; Wed, 29 May 2024 21:26:35 +0200 (CEST) Received: from neptunite.hamster-moth.ts.net (h175-177-049-156.catv02.itscom.jp [175.177.49.156]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 149B62D5D; Wed, 29 May 2024 21:26:29 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1717010792; bh=F679p5jOhcmpaksyJSfkgPFmFgvDrNOdbODcRd+DbBU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=quT2p5Ew79qVs6Q4QaNTn2GY+x2qXKRxcgDE1VHwmCGJsgE/km+hL/Q+EJoiz0pvH a05l/x+eK92lHrbille6RQ5a9goiRgmQlBRanRojCR2D4wGl4hIgUCdKE1wEDn8gLT SQyUl5WAXWdsdL+7js8JWAyV6z7nqsOnJG8qkQEY= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder , Naushir Patuck , David Plowman , Stefan Klug Subject: [PATCH v3 4/4] ipa: rpi: controller: Use libipa's Pwl class Date: Thu, 30 May 2024 04:26:11 +0900 Message-Id: <20240529192612.814515-5-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240529192612.814515-1-paul.elder@ideasonboard.com> References: <20240529192612.814515-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 --- No change in v3 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/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 | 8 ++--- 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 +-- 20 files changed, 84 insertions(+), 76 deletions(-) diff --git a/src/ipa/rpi/controller/cac_status.h b/src/ipa/rpi/controller/cac_status.h index 475d4c5cc..adffce411 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 7c67f0547..1f175872a 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 32a4d31cf..74b74888b 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/rpi/af.cpp b/src/ipa/rpi/controller/rpi/af.cpp index c5fd84826..304629d6d 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 2617e2ace..317a51f3e 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 a77ccec36..a381dd972 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 99033e23e..58368889e 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 abe5906e9..db9937cd4 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 499b4519c..7869d630a 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 c55880296..3272a1416 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 b3abeddf6..4e5b33fef 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 9eef792d3..66871a61e 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 a9d9bbc99..c0f7db981 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 fb539d1f2..c9c38ebff 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 2c8400c2f..e8b9f4270 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 34cf360e9..d533a4ea4 100644 --- a/src/ipa/rpi/controller/rpi/hdr.cpp +++ b/src/ipa/rpi/controller/rpi/hdr.cpp @@ -42,7 +42,7 @@ void HdrConfig::read(const libcamera::YamlObject ¶ms, const std::string &mod /* Lens shading related parameters. */ if (params.contains("spatial_gain_curve")) { - spatialGainCurve.read(params["spatial_gain_curve"]); + spatialGainCurve.readYaml(params["spatial_gain_curve"]); } else if (params.contains("spatial_gain")) { double spatialGain = params["spatial_gain"].get(2.0); spatialGainCurve.append(0.0, spatialGain); @@ -66,7 +66,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"]); speed = params["speed"].get(1.0); if (params.contains("hi_quantile_targets")) { hiQuantileTargets = params["hi_quantile_targets"].getList().value(); @@ -212,7 +212,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. */ @@ -275,7 +275,7 @@ bool Hdr::updateTonemap([[maybe_unused]] StatisticsPtr &stats, HdrConfig &config double power = std::clamp(min_power, config.powerMin, config.powerMax); /* Generate the tonemap, including the contrast adjustment factors. */ - Pwl tonemap; + libcamera::ipa::Pwl tonemap; tonemap.append(0, 0); for (unsigned int i = 0; i <= 6; i++) { double x = 1 << (i + 9); /* x loops from 512 to 32768 inclusive */ diff --git a/src/ipa/rpi/controller/rpi/hdr.h b/src/ipa/rpi/controller/rpi/hdr.h index 9b7327f8b..5c2f3988d 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 spatialGainCurve; /* Brightness to gain curve for different image regions. */ + libcamera::ipa::Pwl spatialGainCurve; /* 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; /* These relate to adaptive tonemap calculation. */ double speed; std::vector hiQuantileTargets; /* quantiles to check for unsaturated images */ @@ -75,7 +76,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 0426e9722..2dc50dfc8 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 f25aa47f8..ba0cf5c40 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 41a7bf2ff..0364ff66f 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; };