From patchwork Wed Aug 5 16:13:05 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27646 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 776D3C3347 for ; Wed, 5 Aug 2026 16:13:57 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0F7CD68188; Wed, 5 Aug 2026 18:13:57 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="H8wKa6w0"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 787C36815A for ; Wed, 5 Aug 2026 18:13:39 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8A9CB3A1A; Wed, 5 Aug 2026 18:12:28 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946349; bh=v4v4Eg4kSgf7KzA8URy4yQTBrx03pOq6Cpvt3Zh6qPA=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=H8wKa6w09FIlg2QdAe63eCQ+XxreP0RPD3CAQL4KxXZTo0ji27qcnS8LoUnlEr2/Z cUupobcAISbIJVJksw7HKLK7VqYQlMr6vE3jBJ20RzHAuy1MLw/7ceuVbLKqmhxAyV PC67csEuaWJqgAFbU/X1aHsHTbuB+oOwc+sETEUc= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:13:05 +0200 Subject: [PATCH v7 23/32] ipa: libipa: lsc: Make Components a map<> MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-23-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=17891; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=v4v4Eg4kSgf7KzA8URy4yQTBrx03pOq6Cpvt3Zh6qPA=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2Ehs/nvt+Evx5gXcZMbBgRgbgZajHQOs9ly+ spR/rC2S+KJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhIQAKCRByNAaPFqFW PDWiEACuDhhjYo4nADOserLRfA/TGp9Ix+U1zFJqOAXJjRa1MpArlad6F3OeOI/fagMnZG3WuS6 9aeF6dEbzjolEqCE2N9hKgpREHZBFQLv3EWnM/7SUAC1y5ne9Tlqy1XZW92xrtHGmcVysKZr9w+ qq2CaIlAVsYqWhkB2ZSJt2d32sLxATNn5gIIq7XkLwqsWfCY1NHHf0ZNEzJfO4QJStNKbmcZKiR e+Fyzo+uv0ZDSTIwjeJLrlAwZepGXUyXN99478Klfkirlhx/ACtNrFW5Wb7YDLguwiw/OItojUH iPix/KtBfQ7Avw5AFdI4IPfq/yiCVlM3bQSZ6ekB1Z3DZDkUh8b8c+hcyOh/h/+ZKODjdzjPD/1 eEDVZ3Vf5RLDOqpjsjaxN3ieYey6CwGSGkgeI1l5Kqj4qjWdh2V/c6sHmI9teVqP4oL62VXPWqw pNQV/ch86+xv2X1qYewKY1fMnCP3hPfp5zPVSg1P5zE2f7P4G55JdxnCRrjpZZCSGNjMaBzkYyv d09h1F0hpczFk5KGu8GO9NnjOUMy7dlkU1YtjHcCnzXL7lU/OiNLT6yic+YLxCm8o+v8sEl5Qky ftXpchCMUfb7MijahvW8Qmchg8G5oCBVebrDO+npdqFt+2ZW4dIGMJCACqa4X6y7HK3JZwTbtIs 8/F/KLeqC70nYpg== 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 Reviewed-by: Stefan Klug Reviewed-by: Kieran Bingham --- v5->v6: - Rename LscDescriptor::num*Cells to num*Samples --- 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 | 59 ++++++++++++++++----------------- 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, 144 insertions(+), 103 deletions(-) diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp index dd69c7a80b17..831f6b003df1 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 b3671376a12a..289cb3da33f2 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 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 ISPs 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::numHSamples + * \brief Number of horizontal gain samples of the ISP LSC grid. Used for + * validating the list of gains parsed from tuning file + * + * \var LscDescriptor::numVSamples + * \brief Number of vertical gain samples 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 1bdb7d5c8ed9..4ad754b3727a 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, std::less<>>; using ComponentsMap = std::map; } /* namespace lsc */ @@ -56,12 +50,20 @@ void Interpolator:: double lambda); #endif /* __DOXYGEN__ */ +struct LscDescriptor { + std::vector keys; + unsigned int numHSamples; + unsigned int numVSamples; + 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 682e2f14fe09..c3871eb3295c 100644 --- a/src/ipa/libipa/lsc_polynomial.cpp +++ b/src/ipa/libipa/lsc_polynomial.cpp @@ -133,44 +133,42 @@ 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].get(); + if (!polynomial) { + LOG(LscPolynomial, Error) + << "Missing polynomial for component " + << k; + return -EINVAL; + } + + auto [it, inserted] = + components.try_emplace(k, std::move(*polynomial)); + ASSERT(inserted); + + 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_.try_emplace(ct, std::move(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 +208,12 @@ 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 = components[t]; + + for (const auto &[k, p] : c) + comp.try_emplace(k, samplePolynomial(p, xPos, yPos, + cropRectangle)); } return components; diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h index d2276ecc6f6b..4c6af2d4900d 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 47dff156963a..639f4f6aa8b0 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.try_emplace(k, parseTable(yamlSet, + k.c_str(), + descriptor.numHSamples, + descriptor.numVSamples)); + ASSERT(inserted); + + if (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_.try_emplace(ct, std::move(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 numHSamples, + unsigned int numVSamples) { - static constexpr unsigned int kLscNumSamples = - RKISP1_CIF_ISP_LSC_SAMPLES_MAX * RKISP1_CIF_ISP_LSC_SAMPLES_MAX; + unsigned int lscNumSamples = numHSamples * numVSamples; 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..a33208761c15 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 numHSamples, + unsigned int numVSamples); lsc::ComponentsMap lscData_; }; diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index b0d6649be4fd..7a95bfcfb93a 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -123,8 +123,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" }, + .numHSamples = RKISP1_CIF_ISP_LSC_SAMPLES_MAX, + .numVSamples = RKISP1_CIF_ISP_LSC_SAMPLES_MAX, + .sensorSize = context.sensorInfo.activeAreaSize + }); } /** @@ -173,10 +177,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]); } /**