From patchwork Mon Jul 20 14:59:46 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27420 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 745EBC3301 for ; Mon, 20 Jul 2026 15:00:22 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E166667EBB; Mon, 20 Jul 2026 17:00:21 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="OEkMwFKO"; 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 C7E4D67E54 for ; Mon, 20 Jul 2026 16:59:56 +0200 (CEST) Received: from [192.168.1.106] (mob-5-90-63-152.net.vodafone.it [5.90.63.152]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id E5A5233A1; Mon, 20 Jul 2026 16:58:57 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559538; bh=Ue8WFFWwwNU/78Yj2z4SyG8waebc1pheVFmlqj1Cy+4=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=OEkMwFKOtoSlOOJAfi4F8CAHr8QzS/Wydb1mmeptEpQr/mnp4SSu0MiJaFBMcglcB USHxkd+k2bnfJYbTHLcQltARm/UVxIEXsZfksydX5y/7cq16YjjK1O31jUV7SMg56W v8GjTgGTbEXDM36VO1SmFvdjw3BlbDT06ZcAFUHs= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:46 +0200 Subject: [PATCH v6 28/31] ipa: libipa: lsc: Quantize gains in LscAlgorithm MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-28-ececb73f97cb@ideasonboard.com> References: <20260720-libipa-algorithms-v6-0-ececb73f97cb@ideasonboard.com> In-Reply-To: <20260720-libipa-algorithms-v6-0-ececb73f97cb@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=15810; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=Ue8WFFWwwNU/78Yj2z4SyG8waebc1pheVFmlqj1Cy+4=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfbp52volV3i7QIAFmnEYey79eWMvv17yLQw kqErzrBx8GJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal432wAKCRByNAaPFqFW PBzCD/9i9Y36FRglymkhYNhi8ELqHmaySRRRDryLNdxfKLeu5R8r35CzeaoBYIbjgBLnZrI7gAa TZ0M3P8y5dwySRfHxKQpXB4/iLaFGZNJgyBGUoyBsqpdzPXUUM/OKIeDYE6RxceZPrP52MqYFmE s8vk7sZcXpUi4uefuwVkWuIgRPDkqFckYYHIfv2QtNOXmbRQBudWI7KWTml8GJGMf8gs5ZbQ1t6 szdohvpCuMyOIOMRfVC2Ml+RivAL12xfnAyCzQZuxAra43eijJWgEg44sE/5K9AlaTt6+8sNi4P u54tOdrrgP5LyAfZeu8YRyl1fGNLu4GzXrj5AxnxPIk7JtmJASD7lxq9sEk/1rJCobuya6En9DP FC3Z7JwAwCKxXyuX7bq2QrKuujjVfVpRIqwXUK6saW84Q47N2gntH5I+MvRPYy4/93OfatkrMk6 ZDITbmdx6KYsBNenWVaae0bHMk6kMibhgXI27DlJu9og1DsGbPjfiIQAg0IVxb1H6j2192XM4V9 bsHkdPwcUz1FPBqPolSc1Dh3/mZFYCTLt3n8WA9DZMHz5EPRST3IW7dNqYa0hg/1vnAW7bdmB18 0qyt/igXPkG5jrZm8hU413RsnorZQ3gw1KmNE99nigROQ1w+nxN6OJphsl5SBXvxyk0ryP+PCSb LT82prK7yiY8R6Q== 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" Template the LscAlgorithm class with the LSC engine register format. This allows to quantize gains in the LscAlgorithm class and not in the IPA modules. Split the LscAlgorithm class in two, one LscAlgorithmBase one for non-templated function implementations and one for the templated configure() implementation and for templated data members and accessor. Modify the LscAlgorithm interpolator interface. As now the returned list of gains is in register format, a dedicated Interpolator::interpolate() overload has to be defined for each IPA module. Signed-off-by: Jacopo Mondi --- src/ipa/libipa/lsc.cpp | 88 +++++++++++++++++++++----------------- src/ipa/libipa/lsc.h | 90 +++++++++++++++++++++++++++++++++------ src/ipa/libipa/lsc_base.cpp | 13 ------ src/ipa/libipa/lsc_base.h | 19 --------- src/ipa/rkisp1/algorithms/lsc.cpp | 40 +++++------------ src/ipa/rkisp1/algorithms/lsc.h | 44 +++++++++++++++++-- 6 files changed, 177 insertions(+), 117 deletions(-) diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp index 8c5c28077cce..8f881edef94c 100644 --- a/src/ipa/libipa/lsc.cpp +++ b/src/ipa/libipa/lsc.cpp @@ -48,6 +48,29 @@ namespace lsc { } /* namespace lsc */ +/** + * \class LscAlgorithmBase + * \brief Base class for LscAlgorithm + * + * Base class for LscAlgorithm for non-templated functions implementation + */ + +/** + * \var LscAlgorithmBase::impl_ + * \brief The LSC algorithm implementation + * + * There are two classes derived from LscImplementation, the LscTable and + * LscPolynomial ones. Which one to instantiate is decided by parsing the tuning + * file. + */ + +/** + * \var LscAlgorithmBase::polynomial_ + * \brief Boolean flag for polynomial LSC + * + * Set to true if polynomial LSC is in use. + */ + /** * \class LscAlgorithm * \brief libIPA LSC algorithm implementation @@ -199,6 +222,16 @@ namespace lsc { * can retrieve them using LscAlgorithm::getComponents(). */ +/** + * \typedef LscAlgorithm::Components + * \brief Map a colour component to a list of gains + */ + +/** + * \typedef LscAlgorithm::ComponentsMap + * \brief Map a colour temperature to an LSC componenet + */ + /** * \param[in] tuningData The tuning data * \param[in] controls The IPA list of supported controls @@ -209,8 +242,8 @@ namespace lsc { * * \return 0 on success, a negative error code otherwise */ -int LscAlgorithm::init(const ValueNode &tuningData, ControlInfoMap::Map &controls, - const LscDescriptor &descriptor) +int LscAlgorithmBase::init(const ValueNode &tuningData, ControlInfoMap::Map &controls, + const LscDescriptor &descriptor) { polynomial_ = false; @@ -245,6 +278,7 @@ int LscAlgorithm::init(const ValueNode &tuningData, ControlInfoMap::Map &control } /** + * \fn LscAlgorithm::configure() * \param[in] state The LSC active state * \param[in] analogCrop The current sensor analog crop rectangle * \param[in] xPos List of horizontal positions of the LSC grid nodes @@ -265,28 +299,6 @@ int LscAlgorithm::init(const ValueNode &tuningData, ControlInfoMap::Map &control * * \return 0 on success, a negative error code otherwise */ -int LscAlgorithm::configure(lsc::ActiveState &state, const Rectangle &analogCrop, - const std::vector &xPos, - const std::vector &yPos) -{ - LOG(Lsc, Debug) << "Sample Lsc data for " << analogCrop; - lsc::ComponentsMap lscData = - impl_->sampleForCrop(analogCrop, xPos, yPos); - - /* - * Retain a copy of the components table. - * - * We could avoid a copy here if getComponents() could - * return sets_.data() but I wasn't able to work around the - * compiler refusing it. - */ - lscData_ = lscData; - - sets_.setData(std::move(lscData)); - state.enabled = true; - - return 0; -} /** * \brief Queue a request to the lsc algorithm @@ -297,9 +309,9 @@ int LscAlgorithm::configure(lsc::ActiveState &state, const Rectangle &analogCrop * Queue a new list of \a controls to the lsc algorithm. * The only supported control is controls::LensShadingCorrectionEnable. */ -void LscAlgorithm::queueRequest(lsc::ActiveState &state, - lsc::FrameContext &context, - const ControlList &controls) +void LscAlgorithmBase::queueRequest(lsc::ActiveState &state, + lsc::FrameContext &context, + const ControlList &controls) { const auto &lscEnable = controls.get(controls::LensShadingCorrectionEnable); if (lscEnable && *lscEnable != state.enabled) { @@ -322,25 +334,22 @@ void LscAlgorithm::queueRequest(lsc::ActiveState &state, * Populates the list of \a metadata with controls handled by the LscAlgorithm * class. The only supported metadata is controls::LensShadingCorrectionEnable. */ -void LscAlgorithm::process(lsc::FrameContext &context, ControlList &metadata) +void LscAlgorithmBase::process(lsc::FrameContext &context, ControlList &metadata) { metadata.set(controls::LensShadingCorrectionEnable, context.enabled); } /** - * \fn LscAlgorithm::interpolateComponents - * \brief Interpolate the LSC tables for a given colour temperature - * \param[in] ct The colour temperature + * \fn LscAlgorithm::getInterpolator + * \brief Retrieve the LSC tables interpolator * * LSC tables are generated using different colour temperatures during the - * tuning phase. - * - * This function returns the interpolated LSC data for a given \a ct - * colour temperature. + * tuning phase. This function returns the interpolator for the LSC data. * - * IPA algorithm can use this function to obtain a list of per-colour-component - * gains to program their LSC engines with every time a significant enough - * change in colour temperature is detected. + * IPA algorithm can use the interpolator to obtain a list of + * per-colour-component gains to program their LSC engines with every time a + * significant enough change in colour temperature is detected by calling the + * Interpolator::getInterpolated() function. * * Calling this function is only valid after LscAlgorithm::configure() has been * called. An empty components list is returned otherwise. @@ -350,8 +359,7 @@ void LscAlgorithm::process(lsc::FrameContext &context, ControlList &metadata) /** * \fn LscAlgorithm::getComponents - * - * Return the map of LSC data per-colour-temperature. + * \brief Return the map of LSC data per-colour-temperature * * Calling this function is only valid after LscAlgorithm::configure() has been * called. An empty components list is returned otherwise. diff --git a/src/ipa/libipa/lsc.h b/src/ipa/libipa/lsc.h index 9fe8ad67ea33..15c8a9cb7b9c 100644 --- a/src/ipa/libipa/lsc.h +++ b/src/ipa/libipa/lsc.h @@ -35,35 +35,101 @@ struct FrameContext { } /* namespace lsc */ -class LscAlgorithm +class LscAlgorithmBase { public: int init(const ValueNode &tuningData, ControlInfoMap::Map &controls, const LscDescriptor &descriptor); - int configure(lsc::ActiveState &state, const Rectangle &analogCrop, - const std::vector &xPos, - const std::vector &yPos); - void queueRequest(lsc::ActiveState &state, lsc::FrameContext &context, const ControlList &controls); void process(lsc::FrameContext &context, ControlList &metadata); - const lsc::Components interpolateComponents(unsigned int ct) +protected: + LscAlgorithmBase() = default; + + std::unique_ptr impl_; + bool polynomial_; +}; + +template +class LscAlgorithm : public LscAlgorithmBase +{ +private: + using T = typename U::QuantizedType; + + template + class _Components : public std::map> + { + }; + + template + class _ComponentsMap : public std::map> { - return sets_.getInterpolated(ct); + }; + +public: + using Components = _Components; + using ComponentsMap = _ComponentsMap; + + LscAlgorithm() = default; + + int configure(lsc::ActiveState &state, const Rectangle &analogCrop, + const std::vector &xPos, + const std::vector &yPos) + { + lsc::ComponentsMap data = + impl_->sampleForCrop(analogCrop, xPos, yPos); + + ComponentsMap lscData; + for (const auto &[t, c] : data) { + Components comp; + + for (const auto &[k, gains] : c) { + std::vector quantizedGains; + quantizedGains.reserve(gains.size()); + + for (const float &gain : gains) { + if (polynomial_) + quantizedGains.push_back(U(gain).quantized()); + else + quantizedGains.push_back(gain); + } + + comp[k] = std::move(quantizedGains); + } + + lscData[t] = comp; + } + + /* + * Retain a copy of the components table. + * + * We could avoid a copy here if getComponents() could + * return sets_.data() but I wasn't able to work around the + * compiler refusing it. + */ + lscData_ = lscData; + + sets_.setData(std::move(lscData)); + state.enabled = true; + + return 0; + } + + Interpolator &getInterpolator() + { + return sets_; } - const lsc::ComponentsMap getComponents() + const ComponentsMap &getComponents() const { return lscData_; } private: - std::unique_ptr impl_; - Interpolator sets_; - lsc::ComponentsMap lscData_; - bool polynomial_; + ComponentsMap lscData_; + Interpolator sets_; }; } /* namespace ipa */ diff --git a/src/ipa/libipa/lsc_base.cpp b/src/ipa/libipa/lsc_base.cpp index cf2c3bfc382f..a98358daf4ec 100644 --- a/src/ipa/libipa/lsc_base.cpp +++ b/src/ipa/libipa/lsc_base.cpp @@ -46,19 +46,6 @@ namespace lsc { } /* namespace lsc */ -#ifndef __DOXYGEN__ -template<> -void Interpolator:: - interpolate(const lsc::Components &a, - const lsc::Components &b, - lsc::Components &dest, - double lambda) -{ - for (auto const &[k, v] : a) - interpolateVector(v, b.at(k), dest[k], lambda); -} -#endif - /** * \struct LscDescriptor * \brief Describe the ISP LSC engine diff --git a/src/ipa/libipa/lsc_base.h b/src/ipa/libipa/lsc_base.h index 386cac55be34..8053de7d2edb 100644 --- a/src/ipa/libipa/lsc_base.h +++ b/src/ipa/libipa/lsc_base.h @@ -31,25 +31,6 @@ using ComponentsMap = std::map; } /* namespace lsc */ -#ifndef __DOXYGEN__ -template -void interpolateVector(const std::vector &a, const std::vector &b, - std::vector &dest, double lambda) -{ - ASSERT(a.size() == b.size()); - dest.resize(a.size()); - for (size_t i = 0; i < a.size(); i++) - dest[i] = a[i] * (1.0 - lambda) + b[i] * lambda; -} - -template<> -void Interpolator:: - interpolate(const lsc::Components &a, - const lsc::Components &b, - lsc::Components &dest, - double lambda); -#endif /* __DOXYGEN__ */ - struct LscDescriptor { std::vector keys; unsigned int numHSamples; diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index dba1a01118ee..157403691ffc 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -175,34 +175,16 @@ void LensShadingCorrection::setParameters(rkisp1_cif_isp_lsc_config &config) } void LensShadingCorrection::copyTable(rkisp1_cif_isp_lsc_config &config, - const lsc::Components &set) + const RkISP1Components &set) { - /* - * The hardware uses 2.10 fixed point format and limits the legal values - * to [1..3.999]. Scale and clamp the sampled values accordingly. - */ - std::vector regs; - regs.reserve(RKISP1_CIF_ISP_LSC_SAMPLES_MAX * - RKISP1_CIF_ISP_LSC_SAMPLES_MAX); - - for (const float &f : set.at("r")) - regs.emplace_back(std::clamp(static_cast(f * 1024), 1024, 4095)); - std::copy(regs.begin(), regs.end(), &config.r_data_tbl[0][0]); - - regs = {}; - for (const float &f : set.at("gr")) - regs.emplace_back(std::clamp(static_cast(f * 1024), 1024, 4095)); - std::copy(regs.begin(), regs.end(), &config.gr_data_tbl[0][0]); - - regs = {}; - for (const float &f : set.at("gb")) - regs.emplace_back(std::clamp(static_cast(f * 1024), 1024, 4095)); - std::copy(regs.begin(), regs.end(), &config.gb_data_tbl[0][0]); - - regs = {}; - for (const float &f : set.at("b")) - regs.emplace_back(std::clamp(static_cast(f * 1024), 1024, 4095)); - std::copy(regs.begin(), regs.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]); } /** @@ -252,8 +234,8 @@ void LensShadingCorrection::prepare([[maybe_unused]] IPAContext &context, setParameters(*config); - const lsc::Components &set = lscAlgo_.interpolateComponents(quantizedCt); - copyTable(*config, set); + auto &lscData = lscAlgo_.getInterpolator(); + copyTable(*config, lscData.getInterpolated(quantizedCt)); lastAppliedCt_ = ct; lastAppliedQuantizedCt_ = quantizedCt; diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h index 63f0887f0838..55a6980014f2 100644 --- a/src/ipa/rkisp1/algorithms/lsc.h +++ b/src/ipa/rkisp1/algorithms/lsc.h @@ -12,6 +12,7 @@ #include #include "libcamera/internal/value_node.h" +#include "libipa/fixedpoint.h" #include "libipa/lsc.h" @@ -19,7 +20,13 @@ namespace libcamera { -namespace ipa::rkisp1::algorithms { +namespace ipa { + +namespace rkisp1::algorithms { + +using RkISP1LscAlgorithm = LscAlgorithm>; +using RkISP1Components = RkISP1LscAlgorithm::Components; +using RkISP1ComponentsMap = RkISP1LscAlgorithm::ComponentsMap; class LensShadingCorrection : public Algorithm { @@ -39,9 +46,11 @@ public: IPAFrameContext &frameContext, const rkisp1_stat_buffer *stats, ControlList &metadata) override; + private: void setParameters(rkisp1_cif_isp_lsc_config &config); - void copyTable(rkisp1_cif_isp_lsc_config &config, const lsc::Components &set0); + void copyTable(rkisp1_cif_isp_lsc_config &config, + const RkISP1Components &set); std::vector xSize_; std::vector ySize_; @@ -55,8 +64,35 @@ private: unsigned int lastAppliedCt_; unsigned int lastAppliedQuantizedCt_; - LscAlgorithm lscAlgo_; + RkISP1LscAlgorithm lscAlgo_; }; -} /* namespace ipa::rkisp1::algorithms */ +} /* namespace rkisp1::algorithms */ + +#ifndef __DOXYGEN__ +template +void interpolateVector(const std::vector &a, + const std::vector &b, + std::vector &dest, double lambda) +{ + ASSERT(a.size() == b.size()); + dest.resize(a.size()); + for (size_t i = 0; i < a.size(); i++) + dest[i] = a[i] * (1.0 - lambda) + b[i] * lambda; +} + +template<> +void Interpolator:: + interpolate(const rkisp1::algorithms::RkISP1Components &a, + const rkisp1::algorithms::RkISP1Components &b, + rkisp1::algorithms::RkISP1Components &dest, + double lambda) +{ + for (auto const &[k, v] : a) + interpolateVector(v, b.at(k), dest[k], lambda); +} +#endif /* __DOXYGEN__ */ + +} /* namespace ipa */ + } /* namespace libcamera */