From patchwork Mon Jul 6 08:01:36 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27216 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 8F812C3308 for ; Mon, 6 Jul 2026 08:02:22 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 3617466030; Mon, 6 Jul 2026 10:02:22 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="AEt4VwSL"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 2B1C76601F for ; Mon, 6 Jul 2026 10:02:08 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id D4DD63F8B; Mon, 6 Jul 2026 10:01:19 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324880; bh=G6B9NHf9Mbk6aw4/wLFkZw2N4cVuZsUD7Jc9FKcDTqo=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=AEt4VwSLCIhpU+1NzvWlMcw+da6XnJ+uEJ7hRNcueELJKUMaY/7q8BiHVwCwnbq7b QD0PM/yx8TLqOkmu0LHvITXAMP6j8e+egoBaKhQV+E7uqR8gZYZojRP7VbP7a461LO 7we/zKtga+ea+ZHLjrPmV/l3XYzZS1LamXSbzmeE= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:36 +0200 Subject: [PATCH v3 16/35] ipa: libipa: lsc_polynomial: Move LscPolynomial from RkISP1 MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-16-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=14417; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=G6B9NHf9Mbk6aw4/wLFkZw2N4cVuZsUD7Jc9FKcDTqo=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2DxROsbwisYvoi0ZwCPFeDWnJQJrq9Nq8sHk /N5Xqr2X9SJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg8QAKCRByNAaPFqFW PJaAD/9ODXTU+5orMSCr1R0LnNTUVUqmUCifTP9PNGmPDu6EpZuv3iHicR8F7apFxm5p96zjHi+ PJdq+lEQXK3MOLeLhupaPcrLwsZ1Ech7MgOYUQ/qzwglNoMdffiL0pVPuAxHGS3E2nmVVPDKKar xJl7+m2TOLSaYsVS7r8UQEd9jwkd6nBNZLBvtEq9hS4MSLfZ7TnRWGUXoTTNZkOedFGJhN8lVme QNfwZHrdUctfMdu3rpU5Nzz8wwgGN2aHtHxyfoT92JUyrPWmYfT7pFKNI/HVbDAOMw0Jt+Vx8iM OKspJjDG5tTzWKZYrE9FdUxBeTC8+Zv7icTavfHNvNpAIyWvLvxmLwELz333sJtOWpBaV0QMAE9 9QU7G8WPzHjeFQBesbGOAqTtkst//May3YnVXdly4S7ufvAJUT5HBTFKKwpitPILQzl37jTrZoV H457lOvFfahfY+RCDqGcUab+3p8C7mP6mBDYqjSJGdMZDRVORmLF/oGCWv0N1QNUarnjLMgaaIL +RxcJy1yjdyAEdP05SCBh84LF/KmwxXTHfUPJNj/xMXZeSIa993xOYrNUuq1Tuh2j8WcEcmSAL6 xUHkqO7GnbRK0rXOpf0Wcstal397CAtQ09R8sdvGCimhifpzkbDh4zlELKJKUwZ7WWW8sFJtB+n +tw4hZvLwqigtXw== X-Developer-Key: i=jacopo.mondi@ideasonboard.com; a=openpgp; fpr=72392EDC88144A65C701EA9BA5826A2587AD026B 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" Move the LscPolynomial class to libipa from RkISP1. Rename the class from LscPolynomialImpl to a simpler LscPolynomial. The class is copied verbatim, only documntation has been added. Signed-off-by: Jacopo Mondi --- src/ipa/libipa/lsc_polynomial.cpp | 179 +++++++++++++++++++++++++++++++++++++- src/ipa/libipa/lsc_polynomial.h | 46 ++++++++-- src/ipa/rkisp1/algorithms/lsc.cpp | 162 +--------------------------------- src/ipa/rkisp1/algorithms/lsc.h | 1 - 4 files changed, 218 insertions(+), 170 deletions(-) diff --git a/src/ipa/libipa/lsc_polynomial.cpp b/src/ipa/libipa/lsc_polynomial.cpp index a845d8b1783b..5c4ffc98797e 100644 --- a/src/ipa/libipa/lsc_polynomial.cpp +++ b/src/ipa/libipa/lsc_polynomial.cpp @@ -2,11 +2,14 @@ /* * Copyright (C) 2024, Ideas On Board * - * Polynomial class to represent lens shading correction + * Polynomial based lens shading correction */ #include "lsc_polynomial.h" +#include +#include + #include /** @@ -109,6 +112,180 @@ void Polynomial::setReferenceImageSize(const Size &size) } /* namespace lsc */ +/** + * \class LscPolynomial + * \brief Radial Polynomial lsc algorithm implementation + * + * Polynomial-based lsc algorithm implementation. The LscPolynomial class + * implements lsc support using Polynomial to represent the shading artifacts + * map. + * + * \sa LscImplementation + */ + +/** + * \fn LscPolynomial::LscPolynomial + * \param[in] sensorSize The physical sensor size + * + * Construct an LscPolynomial + */ + +/** + * \brief Parse polynomial lsc data + * \param[in] sets The tuning file content + * + * Parse the lsc data in polyomial form from the \a sets tuning data. + * + * \return 0 on success or a negative error number otherwise + */ +int LscPolynomial::parseLscData(const ValueNode &sets) +{ + for (const auto &set : sets.asList()) { + std::optional pr, pgr, pgb, pb; + uint32_t ct = set["ct"].get(0); + + if (lscData_.count(ct)) { + LOG(LscPolynomial, Error) + << "Multiple sets found for " + << "color temperature " << ct; + return -EINVAL; + } + + pr = set["r"].get(); + pgr = set["gr"].get(); + pgb = set["gb"].get(); + pb = set["b"].get(); + + if (!(pr || pgr || pgb || pb)) { + LOG(LscPolynomial, Error) + << "Failed to parse polynomial for " + << "colour temperature " << ct; + return -EINVAL; + } + + pr->setReferenceImageSize(sensorSize_); + pgr->setReferenceImageSize(sensorSize_); + pgb->setReferenceImageSize(sensorSize_); + pb->setReferenceImageSize(sensorSize_); + + lscData_.emplace(std::piecewise_construct, + std::forward_as_tuple(ct), + std::forward_as_tuple(PolynomialComponents{ *pr, *pgr, *pgb, *pb })); + } + + if (lscData_.empty()) { + LOG(LscPolynomial, Error) << "Failed to load any sets"; + return -EINVAL; + } + + return 0; +} + +/** + * \brief Re-sample the lsc components for \a cropRectangle + * \param[in] cropRectangle The sensor analogue crop rectangle + * \param[in] xSizes List of horizontal positions of the lsc grid nodes + * \param[in] ySizes List of vertical positions of the lsc grid nodes + * + * Lsc tables have to be re-sampled every time a new sensor configuration is + * used, as each streaming session might use a different sensor crop rectangle. + * + * Polynomial Lsc tables can be re-sampled for a given sensor frame resolution + * using a list of horizontal and vertical nodes that define the lsc grid on + * which the polynomial is re-sampled on. + * + * \a cropRectangle represents the size of the frame on which the Lsc tables + * have to be re-sampled on. + * + * \a xSizes and \a ySizes represent the position of the grid nodes vertexes in + * the [0, 1] interval. In example an equally spaced grid of 16 nodes will have + * each segment of size 0.0625 and the list of nodes position will be + * [0, 0.0625, 0.125, 0.1875, ... , 1]. It is expected that the first position + * is 0 and the last position is 1. + */ +lsc::ComponentsMap +LscPolynomial::sampleForCrop(const Rectangle &cropRectangle, + Span xSizes, + Span ySizes) +{ + std::vector xPos = sizesListToPositions(xSizes); + std::vector yPos = sizesListToPositions(ySizes); + + lsc::ComponentsMap components; + + for (const auto &[k, p] : lscData_) { + components[k] = { + samplePolynomial(p.pr, xPos, yPos, cropRectangle), + samplePolynomial(p.pgr, xPos, yPos, cropRectangle), + samplePolynomial(p.pgb, xPos, yPos, cropRectangle), + samplePolynomial(p.pb, xPos, yPos, cropRectangle) + }; + } + + return components; +} + +std::vector +LscPolynomial::samplePolynomial(const lsc::Polynomial &poly, + Span xPositions, + Span yPositions, + const Rectangle &cropRectangle) +{ + double m = poly.getM(); + double x0 = cropRectangle.x / m; + double y0 = cropRectangle.y / m; + double w = cropRectangle.width / m; + double h = cropRectangle.height / m; + std::vector samples; + + samples.reserve(xPositions.size() * yPositions.size()); + + for (double y : yPositions) { + for (double x : xPositions) { + double xp = x0 + x * w; + double yp = y0 + y * h; + /* + * The hardware uses 2.10 fixed point format and limits + * the legal values to [1..3.999]. Scale and clamp the + * sampled value accordingly. + */ + int v = static_cast( + poly.sampleAtNormalizedPixelPos(xp, yp) * + 1024); + v = std::clamp(v, 1024, 4095); + samples.push_back(v); + } + } + return samples; +} + +/* + * The rkisp1 LSC grid spacing is defined by the cell sizes on the top-left + * quadrant of the grid. This is then mirrored in hardware to the other + * quadrants. See parseSizes() for further details. For easier handling, this + * function converts the cell sizes of half the grid to a list of position of + * the whole grid (on one axis). Example: + * + * input: | 0.2 | 0.3 | + * output: 0.0 0.2 0.5 0.8 1.0 + */ +std::vector +LscPolynomial::sizesListToPositions(Span sizes) +{ + const int half = sizes.size(); + std::vector positions(half * 2 + 1); + double x = 0.0; + + positions[half] = 0.5; + for (int i = 1; i <= half; i++) { + x += sizes[half - i]; + positions[half - i] = 0.5 - x; + positions[half + i] = 0.5 + x; + } + + return positions; +} + } /* namespace ipa */ #ifndef __DOXYGEN__ diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h index 2caf46d2d759..17c2c34499f0 100644 --- a/src/ipa/libipa/lsc_polynomial.h +++ b/src/ipa/libipa/lsc_polynomial.h @@ -2,25 +2,23 @@ /* * Copyright (C) 2024, Ideas On Board * - * Helper for radial polynomial used in lens shading correction. + * Polynomial based lens shading correction */ #pragma once -#include #include -#include -#include +#include +#include -#include #include #include #include "libcamera/internal/value_node.h" -namespace libcamera { +#include "lsc_base.h" -LOG_DECLARE_CATEGORY(LscPolynomial) +namespace libcamera { namespace ipa { @@ -52,6 +50,40 @@ private: } /* namespace lsc */ +class LscPolynomial : public LscImplementation +{ +private: + struct PolynomialComponents { + lsc::Polynomial pr; + lsc::Polynomial pgr; + lsc::Polynomial pgb; + lsc::Polynomial pb; + }; + using PolynomialComponentsMap = std::map; + +public: + LscPolynomial(const Size &sensorSize) + : sensorSize_(sensorSize) + { + } + + int parseLscData(const ValueNode &sets) override; + + lsc::ComponentsMap + sampleForCrop(const Rectangle &cropRectangle, + Span xSizes, + Span ySizes) override; + +private: + std::vector sizesListToPositions(Span sizes); + std::vector samplePolynomial(const lsc::Polynomial &poly, + Span xPositions, + Span yPositions, + const Rectangle &cropRectangle); + PolynomialComponentsMap lscData_; + Size sensorSize_; +}; + } /* namespace ipa */ } /* namespace libcamera */ diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index 910b2ced292e..a4ea879aaa83 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -33,166 +33,6 @@ namespace { constexpr int kColourTemperatureQuantization = 10; -class LscPolynomialImpl : public LscImplementation -{ -private: - struct PolynomialComponents { - lsc::Polynomial pr; - lsc::Polynomial pgr; - lsc::Polynomial pgb; - lsc::Polynomial pb; - }; - using PolynomialComponentsMap = std::map; - -public: - LscPolynomialImpl(const Size &sensorSize) - : sensorSize_(sensorSize) - { - } - - int parseLscData(const ValueNode &sets) override; - - lsc::ComponentsMap - sampleForCrop(const Rectangle &cropRectangle, - Span xSizes, - Span ySizes) override; - -private: - std::vector sizesListToPositions(Span sizes); - std::vector samplePolynomial(const lsc::Polynomial &poly, - Span xPositions, - Span yPositions, - const Rectangle &cropRectangle); - PolynomialComponentsMap lscData_; - Size sensorSize_; -}; - -int LscPolynomialImpl::parseLscData(const ValueNode &sets) -{ - for (const auto &set : sets.asList()) { - std::optional pr, pgr, pgb, pb; - uint32_t ct = set["ct"].get(0); - - if (lscData_.count(ct)) { - LOG(RkISP1Lsc, Error) - << "Multiple sets found for " - << "color temperature " << ct; - return -EINVAL; - } - - pr = set["r"].get(); - pgr = set["gr"].get(); - pgb = set["gb"].get(); - pb = set["b"].get(); - - if (!(pr || pgr || pgb || pb)) { - LOG(RkISP1Lsc, Error) - << "Failed to parse polynomial for " - << "colour temperature " << ct; - return -EINVAL; - } - - pr->setReferenceImageSize(sensorSize_); - pgr->setReferenceImageSize(sensorSize_); - pgb->setReferenceImageSize(sensorSize_); - pb->setReferenceImageSize(sensorSize_); - - lscData_.emplace(std::piecewise_construct, - std::forward_as_tuple(ct), - std::forward_as_tuple(PolynomialComponents{ *pr, *pgr, *pgb, *pb })); - } - - if (lscData_.empty()) { - LOG(RkISP1Lsc, Error) << "Failed to load any sets"; - return -EINVAL; - } - - return 0; -} - -lsc::ComponentsMap -LscPolynomialImpl::sampleForCrop(const Rectangle &cropRectangle, - Span xSizes, - Span ySizes) -{ - std::vector xPos = sizesListToPositions(xSizes); - std::vector yPos = sizesListToPositions(ySizes); - - lsc::ComponentsMap components; - - for (const auto &[k, p] : lscData_) { - components[k] = { - samplePolynomial(p.pr, xPos, yPos, cropRectangle), - samplePolynomial(p.pgr, xPos, yPos, cropRectangle), - samplePolynomial(p.pgb, xPos, yPos, cropRectangle), - samplePolynomial(p.pb, xPos, yPos, cropRectangle) - }; - } - - return components; -} - -std::vector -LscPolynomialImpl::samplePolynomial(const lsc::Polynomial &poly, - Span xPositions, - Span yPositions, - const Rectangle &cropRectangle) -{ - double m = poly.getM(); - double x0 = cropRectangle.x / m; - double y0 = cropRectangle.y / m; - double w = cropRectangle.width / m; - double h = cropRectangle.height / m; - std::vector samples; - - samples.reserve(xPositions.size() * yPositions.size()); - - for (double y : yPositions) { - for (double x : xPositions) { - double xp = x0 + x * w; - double yp = y0 + y * h; - /* - * The hardware uses 2.10 fixed point format and limits - * the legal values to [1..3.999]. Scale and clamp the - * sampled value accordingly. - */ - int v = static_cast( - poly.sampleAtNormalizedPixelPos(xp, yp) * - 1024); - v = std::clamp(v, 1024, 4095); - samples.push_back(v); - } - } - return samples; -} - -/* - * The rkisp1 LSC grid spacing is defined by the cell sizes on the top-left - * quadrant of the grid. This is then mirrored in hardware to the other - * quadrants. See parseSizes() for further details. For easier handling, this - * function converts the cell sizes of half the grid to a list of position of - * the whole grid (on one axis). Example: - * - * input: | 0.2 | 0.3 | - * output: 0.0 0.2 0.5 0.8 1.0 - */ -std::vector -LscPolynomialImpl::sizesListToPositions(Span sizes) -{ - const int half = sizes.size(); - std::vector positions(half * 2 + 1); - double x = 0.0; - - positions[half] = 0.5; - for (int i = 1; i <= half; i++) { - x += sizes[half - i]; - positions[half - i] = 0.5 - x; - positions[half + i] = 0.5 + x; - } - - return positions; -} - class LscTableImpl : public LscImplementation { public: @@ -358,7 +198,7 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, * \todo: Most likely the reference frame should be native_size. * Let's wait how the internal discussions progress. */ - algo_ = std::make_unique(context.sensorInfo.activeAreaSize); + algo_ = std::make_unique(context.sensorInfo.activeAreaSize); ret = algo_->parseLscData(sets); } else { LOG(RkISP1Lsc, Error) << "Unsupported LSC data type '" diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h index b21c91c0d81f..2a428293cb2e 100644 --- a/src/ipa/rkisp1/algorithms/lsc.h +++ b/src/ipa/rkisp1/algorithms/lsc.h @@ -7,7 +7,6 @@ #pragma once -#include #include #include "libipa/interpolator.h"