From patchwork Mon Jul 6 08:01:45 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27226 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 1C027C330A for ; Mon, 6 Jul 2026 08:02:35 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id CA32466060; Mon, 6 Jul 2026 10:02:34 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Z1ahdnU3"; 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 B2E426601E for ; Mon, 6 Jul 2026 10:02:13 +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 5969316F9; Mon, 6 Jul 2026 10:01:25 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324885; bh=rQNQ1vsGDE7PAsPQfl1To2b5CQlmApEmLmHW5x+0GTU=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=Z1ahdnU3hitVqu/CEY/1nscxzA7c8pd1zjPFb5RtpBUSduR4dqqiCIQXceujY2PY3 5s+LTQnAT24pxlMJ0K/3qK7wD3jHqIjQPux/BhjKNaHPedY5kMCtLlLm6qD607nFj3 2IuWB4gW8e4u96mBTKXd9Jnn1cXXdF6ujSrGRguE= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:45 +0200 Subject: [PATCH v3 25/35] ipa: libipa: lsc: Make Components a map<> MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-25-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=17939; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=rQNQ1vsGDE7PAsPQfl1To2b5CQlmApEmLmHW5x+0GTU=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2Dy7K1H+WgwuhzQUjeNTPbV5Fh9PoDGRF6HC TJL5f8LK+6JAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg8gAKCRByNAaPFqFW POFhEACtBGeOYpvyukDW5/CEStoCuIsthleahNu2Y+TtmfYj/Obg6OH5BAidErYJq8YRjmwzL8Q lS4biqbKVptDKTVj6DdFJTBkq0ZHZVn8iYxpnJhySOv9hCiWCyVQR+6TNASstDybBuK+P2XfHaV EI70cPGyHfFzjnc4yEs1TwGJq7LNApLNuDhZ2akZtWPUJmtiLBdvPHrBsTXVuTfBF042gxmEPQw q9mK2WDbRss/8ZBcmUWFfL404gJ1p/yxDUWWobPb2RNw583MTmcMoWpbdJlkXTXjvLZ+migjisU FAYuL+8fK3643KNCePb0vIs85IVBYKDe3lXPhNs8WIvpD9QALGjDs05Jd5xnnCn2YePIEiS6BzW 1pNYd1gF33cKzTYby/MQqzeI5W70loKDbk/wAUJ12Sok0WUAdd2FTrjlsOwNbeiSudTlgHUDIu/ ET9SGFXhsjBEe1+w3rEV0Vaqm2oJcfCFT9xeA4CsSNodGlJw87fLQ98KWA7IQeHoUw+MLAnAxsM mEFsxkKrx09WwS+oWSKmMD4LL4L9ZIc5xnjCaLalX+z0AZ/MaR6hsJke7E14XJbnqPjuwV1SbrF CjV06OtUfoPv9xHwugyKvKuKMES+2rR3mLr5xQ4DX6W5mIlvJdK/aCV5kfoGJvCYwv78RuzsVvz 6/qKjWOCnUjCXVQ== 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" The current definition of lsc::Components assumes 4 gain vectors named after the colour components supported by the RkISP1 ISP (r, gr, gb and b). As different ISP support different colour channels for their Lsc tables make lsc::Components a map that associates the name of the colour channel to the vector of gains. The use of strings as index for the map comes from the requirement of parsing the tuning file using string identifiers. Pass the strings to use for tuning file parsing to the LscAlgorithm::init() function as part of a newly introduced LscDescriptor type and use the same strings for indexing the gain arrays. The newly introduced LscDescriptor type also provides to the LscAlgorithm class the expected size of the Lsc grid, allowing to remove RkISP1 specific values from the libipa generic implementation. Signed-off-by: Jacopo Mondi --- src/ipa/libipa/lsc.cpp | 10 +++--- src/ipa/libipa/lsc.h | 4 +-- src/ipa/libipa/lsc_base.cpp | 49 ++++++++++++++++++++-------- src/ipa/libipa/lsc_base.h | 18 ++++++----- src/ipa/libipa/lsc_polynomial.cpp | 65 +++++++++++++++++++------------------ src/ipa/libipa/lsc_polynomial.h | 10 ++---- src/ipa/libipa/lsc_table.cpp | 68 +++++++++++++++++++++++---------------- src/ipa/libipa/lsc_table.h | 9 ++++-- src/ipa/rkisp1/algorithms/lsc.cpp | 20 ++++++++---- 9 files changed, 150 insertions(+), 103 deletions(-) diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp index 66d33a2dd796..9bc71e3c73ae 100644 --- a/src/ipa/libipa/lsc.cpp +++ b/src/ipa/libipa/lsc.cpp @@ -86,16 +86,16 @@ namespace lsc { /** * \param[in] tuningData The tuning data - * \param[in] sensorSize The physical sensor size * \param[in] controls The IPA list of supported controls + * \param[in] descriptor The lsc engine descriptor * * Parse \a tuningData according to the settings specified in \a descriptor to * populate the lsc data and registers lsc controls in \a controls. * * \return 0 on success, a negative error code otherwise */ -int LscAlgorithm::init(const ValueNode &tuningData, const Size &sensorSize, - ControlInfoMap::Map &controls) +int LscAlgorithm::init(const ValueNode &tuningData, ControlInfoMap::Map &controls, + const LscDescriptor &descriptor) { polynomial_ = false; @@ -108,7 +108,7 @@ int LscAlgorithm::init(const ValueNode &tuningData, const Size &sensorSize, * \todo: Most likely the reference frame should be native_size. * Let's wait how the internal discussions progress. */ - impl_ = std::make_unique(sensorSize); + impl_ = std::make_unique(descriptor.sensorSize); polynomial_ = true; LOG(Lsc, Debug) << "Using polynomial Lsc"; } else { @@ -123,7 +123,7 @@ int LscAlgorithm::init(const ValueNode &tuningData, const Size &sensorSize, return -EINVAL; } - int ret = impl_->parseLscData(yamlSets); + int ret = impl_->parseLscData(yamlSets, descriptor); if (ret) return ret; diff --git a/src/ipa/libipa/lsc.h b/src/ipa/libipa/lsc.h index 9a51d32c3464..9fe8ad67ea33 100644 --- a/src/ipa/libipa/lsc.h +++ b/src/ipa/libipa/lsc.h @@ -38,8 +38,8 @@ struct FrameContext { class LscAlgorithm { public: - int init(const ValueNode &tuningData, const Size &sensorSize, - ControlInfoMap::Map &controls); + int init(const ValueNode &tuningData, ControlInfoMap::Map &controls, + const LscDescriptor &descriptor); int configure(lsc::ActiveState &state, const Rectangle &analogCrop, const std::vector &xPos, diff --git a/src/ipa/libipa/lsc_base.cpp b/src/ipa/libipa/lsc_base.cpp index 7b0216b689c4..30af8d970a12 100644 --- a/src/ipa/libipa/lsc_base.cpp +++ b/src/ipa/libipa/lsc_base.cpp @@ -19,23 +19,20 @@ namespace ipa { namespace lsc { /** - * \struct Components + * \typedef Components * \brief Associate a colour components with a list of gains * * Lsc tables are defined as a list of gain values associated to a colour * component. * - * \var Components::r - * \brief The list of gains for the Red colour component + * As different ISP support different colour components (usually 'r', 'gr', + * 'gb', 'b' or just 'r', 'g', 'b') this class associates a string + * identifier for the colour component to a list of gains. * - * \var Components::gr - * \brief The list of gains for the Green/Red colour component + * Each key name shall match an entry in the tuning file. * - * \var Components::gb - * \brief The list of gains for the Green/Blue colour component - * - * \var Components::b - * \brief The list of gains for the Blue colour component + * The list of keys is provided to the LscAlgorithm class using \a + * LscDescriptor::keys. */ /** @@ -57,13 +54,32 @@ void Interpolator:: lsc::Components &dest, double lambda) { - interpolateVector(a.r, b.r, dest.r, lambda); - interpolateVector(a.gr, b.gr, dest.gr, lambda); - interpolateVector(a.gb, b.gb, dest.gb, lambda); - interpolateVector(a.b, b.b, dest.b, lambda); + for (auto const &[k, v] : a) + interpolateVector(v, b.at(k), dest[k], lambda); } #endif +/** + * \struct LscDescriptor + * \brief Describe the ISP lsc engine + * + * \var LscDescriptor::keys + * \brief The list of colour components to which a list of gains is associated + * with in the tuning file. Used for parsing the tuning file + * + * \var LscDescriptor::numHCells + * \brief Number of horizontal cells of the ISP lsc grid. Used for validating + * the list of gains parsed from tuning file + * + * \var LscDescriptor::numVCells + * \brief Number of vertical cells of the ISP lsc grid. Used for validating + * the list of gains parsed from tuning file + * + * \var LscDescriptor::sensorSize + * \brief The physical sensor size. This is the largest frame size used to + * generate the lsc table. Only used by the polynomial lsc algorithm + */ + /** * \class LscImplementation * \brief Pure virtual base class for lsc algorithm implementations @@ -80,6 +96,11 @@ void Interpolator:: * \fn LscImplementation::parseLscData * \brief Parse \a tuningData * \param[in] tuningData The tuning data + * \param[in] descriptor The lsc engine descriptor + * + * Parse the tuning file using the \a descriptor to identify the colour + * components in the tuning data and validate the size of the loaded gains + * tables. * * \return 0 on success, a negative error number otherwise */ diff --git a/src/ipa/libipa/lsc_base.h b/src/ipa/libipa/lsc_base.h index 8d1843a5d4fd..48307455362a 100644 --- a/src/ipa/libipa/lsc_base.h +++ b/src/ipa/libipa/lsc_base.h @@ -26,13 +26,7 @@ namespace ipa { namespace lsc { -struct Components { - std::vector r; - std::vector gr; - std::vector gb; - std::vector b; -}; - +using Components = std::map>; using ComponentsMap = std::map; } /* namespace lsc */ @@ -56,12 +50,20 @@ void Interpolator:: double lambda); #endif /* __DOXYGEN__ */ +struct LscDescriptor { + std::vector keys; + unsigned int numHCells; + unsigned int numVCells; + Size sensorSize; +}; + class LscImplementation { public: virtual ~LscImplementation() {} - virtual int parseLscData(const ValueNode &tuningData) = 0; + virtual int parseLscData(const ValueNode &tuningData, + const LscDescriptor &descriptor) = 0; virtual lsc::ComponentsMap sampleForCrop(const Rectangle &cropRectangle, diff --git a/src/ipa/libipa/lsc_polynomial.cpp b/src/ipa/libipa/lsc_polynomial.cpp index f39122963019..0550229b5152 100644 --- a/src/ipa/libipa/lsc_polynomial.cpp +++ b/src/ipa/libipa/lsc_polynomial.cpp @@ -133,44 +133,43 @@ void Polynomial::setReferenceImageSize(const Size &size) /** * \brief Parse polynomial lsc data * \param[in] sets The tuning file content + * \param[in] descriptor The lsc engine descriptor * * 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) +int LscPolynomial::parseLscData(const ValueNode &sets, + const LscDescriptor &descriptor) { 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; + PolynomialComponents components; + for (auto &k : descriptor.keys) { + auto polynomial = set[k.c_str()].get(); + if (!polynomial) { + LOG(LscPolynomial, Error) + << "Missing polynomial for component " + << k; + return -EINVAL; + } + + auto [it, inserted] = + components.emplace(std::piecewise_construct, + std::forward_as_tuple(k.c_str()), + std::forward_as_tuple(*polynomial)); + + it->second.setReferenceImageSize(descriptor.sensorSize); } - pr = set["r"].get(); - pgr = set["gr"].get(); - pgb = set["gb"].get(); - pb = set["b"].get(); - - if (!(pr || pgr || pgb || pb)) { + auto [it, inserted] = lscData_.emplace(ct, components); + if (!inserted) { LOG(LscPolynomial, Error) - << "Failed to parse polynomial for " - << "colour temperature " << ct; + << "Multiple sets found for " + << "color 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()) { @@ -210,13 +209,17 @@ LscPolynomial::sampleForCrop(const Rectangle &cropRectangle, 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) - }; + for (const auto &[t, c] : lscData_) { + lsc::Components comp; + + for (const auto &[k, p] : c) { + comp.emplace(std::piecewise_construct, + std::forward_as_tuple(k), + std::forward_as_tuple(samplePolynomial(p, xPos, yPos, + cropRectangle))); + } + + components[t] = comp; } return components; diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h index d2276ecc6f6b..2a26db499d68 100644 --- a/src/ipa/libipa/lsc_polynomial.h +++ b/src/ipa/libipa/lsc_polynomial.h @@ -53,12 +53,7 @@ private: class LscPolynomial : public LscImplementation { private: - struct PolynomialComponents { - lsc::Polynomial pr; - lsc::Polynomial pgr; - lsc::Polynomial pgb; - lsc::Polynomial pb; - }; + using PolynomialComponents = std::map; using PolynomialComponentsMap = std::map; public: @@ -67,7 +62,8 @@ public: { } - int parseLscData(const ValueNode &sets) override; + int parseLscData(const ValueNode &sets, + const LscDescriptor &descriptor) override; lsc::ComponentsMap sampleForCrop(const Rectangle &cropRectangle, diff --git a/src/ipa/libipa/lsc_table.cpp b/src/ipa/libipa/lsc_table.cpp index 41a862b92e88..415c1cc0ccd4 100644 --- a/src/ipa/libipa/lsc_table.cpp +++ b/src/ipa/libipa/lsc_table.cpp @@ -7,9 +7,6 @@ #include "lsc_table.h" -/* \todo Remove RkISP1 from libipa. */ -#include "linux/rkisp1-config.h" - namespace libcamera { LOG_DEFINE_CATEGORY(LscTable) @@ -29,42 +26,56 @@ namespace ipa { /** * \brief Parse tabular lsc data * \param[in] sets The tuning file content + * \param[in] descriptor The lsc engine descriptor * * Parse the lsc data in tabular form from the \a sets tuning data. * * \return 0 on success or a negative error number otherwise */ -int LscTable::parseLscData(const ValueNode &sets) +int LscTable::parseLscData(const ValueNode &sets, + const LscDescriptor &descriptor) { for (const auto &set : sets.asList()) { uint32_t ct = set["ct"].get(0); - if (lscData_.count(ct)) { - LOG(LscTable, Error) - << "Multiple sets found for color temperature " - << ct; - return -EINVAL; - } + int ret = parseLscComponent(set, ct, descriptor); + if (ret) + return ret; + } - lsc::Components components; - components.r = parseTable(set, "r"); - components.gr = parseTable(set, "gr"); - components.gb = parseTable(set, "gb"); - components.b = parseTable(set, "b"); + if (lscData_.empty()) { + LOG(LscTable, Error) << "Failed to load any sets"; + return -EINVAL; + } - if (components.r.empty() || components.gr.empty() || - components.gb.empty() || components.b.empty()) { + return 0; +} + +int LscTable::parseLscComponent(const ValueNode &yamlSet, + unsigned int ct, const LscDescriptor &descriptor) +{ + lsc::Components component; + for (auto &k : descriptor.keys) { + auto [it, inserted] = component.emplace( + std::piecewise_construct, + std::forward_as_tuple(k.c_str()), + std::forward_as_tuple(parseTable(yamlSet, + k.c_str(), + descriptor.numHCells, + descriptor.numVCells))); + if (!inserted || it->second.empty()) { LOG(LscTable, Error) - << "Set for color temperature " << ct - << " is missing tables"; + << "Set " << k << " for color temperature " + << ct << " is missing"; return -EINVAL; } - - lscData_.emplace(ct, std::move(components)); } - if (lscData_.empty()) { - LOG(LscTable, Error) << "Failed to load any sets"; + auto [it, inserted] = lscData_.emplace(ct, component); + if (!inserted) { + LOG(LscTable, Error) + << "Multiple sets found for color temperature " + << ct; return -EINVAL; } @@ -72,17 +83,18 @@ int LscTable::parseLscData(const ValueNode &sets) } std::vector LscTable::parseTable(const ValueNode &tuningData, - const char *prop) + const char *prop, + unsigned int numHCells, + unsigned int numVCells) { - static constexpr unsigned int kLscNumSamples = - RKISP1_CIF_ISP_LSC_SAMPLES_MAX * RKISP1_CIF_ISP_LSC_SAMPLES_MAX; + unsigned int lscNumSamples = numHCells * numVCells; std::vector table = tuningData[prop].get>().value_or(utils::defopt); - if (table.size() != kLscNumSamples) { + if (table.size() != lscNumSamples) { LOG(LscTable, Error) << "Invalid '" << prop << "' values: expected " - << kLscNumSamples + << lscNumSamples << " elements, got " << table.size(); return {}; } diff --git a/src/ipa/libipa/lsc_table.h b/src/ipa/libipa/lsc_table.h index 67c7464ea5b6..3fe52afbad7b 100644 --- a/src/ipa/libipa/lsc_table.h +++ b/src/ipa/libipa/lsc_table.h @@ -26,7 +26,8 @@ namespace ipa { class LscTable : public LscImplementation { public: - int parseLscData(const ValueNode &sets) override; + int parseLscData(const ValueNode &sets, + const LscDescriptor &descriptor) override; lsc::ComponentsMap sampleForCrop([[maybe_unused]] const Rectangle &cropRectangle, @@ -39,8 +40,12 @@ public: } private: + int parseLscComponent(const ValueNode &yamlSet, + unsigned int ct, const LscDescriptor &descriptor); std::vector parseTable(const ValueNode &tuningData, - const char *prop); + const char *prop, + unsigned int numHCells, + unsigned int numVCells); lsc::ComponentsMap lscData_; }; diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index 828ab2f32fcc..79d68ac7d6dd 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -67,8 +67,12 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, xPos_ = sizesListToPositions(xSize_); yPos_ = sizesListToPositions(ySize_); - return lscAlgo_.init(tuningData, context.sensorInfo.activeAreaSize, - context.ctrlMap); + return lscAlgo_.init(tuningData, context.ctrlMap, { + .keys = { "r", "gr", "gb", "b" }, + .numHCells = RKISP1_CIF_ISP_LSC_SAMPLES_MAX, + .numVCells = RKISP1_CIF_ISP_LSC_SAMPLES_MAX, + .sensorSize = context.sensorInfo.activeAreaSize + }); } std::vector LensShadingCorrection::parseSizes(const ValueNode &tuningData, @@ -174,10 +178,14 @@ void LensShadingCorrection::setParameters(rkisp1_cif_isp_lsc_config &config) void LensShadingCorrection::copyTable(rkisp1_cif_isp_lsc_config &config, const lsc::Components &set) { - std::copy(set.r.begin(), set.r.end(), &config.r_data_tbl[0][0]); - std::copy(set.gr.begin(), set.gr.end(), &config.gr_data_tbl[0][0]); - std::copy(set.gb.begin(), set.gb.end(), &config.gb_data_tbl[0][0]); - std::copy(set.b.begin(), set.b.end(), &config.b_data_tbl[0][0]); + const auto &r = set.at("r"); + std::copy(r.begin(), r.end(), &config.r_data_tbl[0][0]); + const auto &gr = set.at("gr"); + std::copy(gr.begin(), gr.end(), &config.gr_data_tbl[0][0]); + const auto &gb = set.at("gb"); + std::copy(gb.begin(), gb.end(), &config.gb_data_tbl[0][0]); + const auto &b = set.at("b"); + std::copy(b.begin(), b.end(), &config.b_data_tbl[0][0]); } /**