From patchwork Mon Jul 20 14:59:19 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27393 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 C51E5C328C for ; Mon, 20 Jul 2026 14:59:45 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 96DE467E53; Mon, 20 Jul 2026 16:59:42 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="rb80k6PL"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 61A5066177 for ; Mon, 20 Jul 2026 16:59:41 +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 49C2B6DF; Mon, 20 Jul 2026 16:58:42 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559522; bh=RpUDcWqrtYtmnystDS0mFOGu34COlhfbnzDYcV9x7j0=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=rb80k6PL4hQPOsi6EraQQr2t9IG/yipO0YMsPUg00dSlUv6oA7fTfaJhtu3G4n1iU iOnlIPFsTz0PDcpoxFjTfgCj2msyJwef07PR3z37pR2iFVjnapfxo2J+9kfSI/NkYO OwmLQGCZlZnnBYSR2RYRQHdenCOp5KRmK4yQO0A4= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:19 +0200 Subject: [PATCH v6 01/31] ipa: libipa: awb: Reimplement AwbAlgorithm MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-1-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 , Kieran Bingham , Daniel Scally , Robert Mader X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=45702; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=RpUDcWqrtYtmnystDS0mFOGu34COlhfbnzDYcV9x7j0=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfR+vIEucNZIwCK5L0pdWyzynB62uKF9QHSo nm1KQSCp2+JAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal430QAKCRByNAaPFqFW PKP9D/0WHLuFxAL4i/nMJVaPQs0vx4Uh9n5f3vZS/o5oAijhVUBdYlhpXAQciNfEf4EgLKxMifA 8BIbo+yHbBQEvwQnIWnFnh8i+5M6D0sWVqP2lKFKKsFWhEh7WKsX/JZ9YGFe+99vO/toEofd70L txpirY7yvXrWhqB6bC5ejbCUXRbEbSfubaWTSzChv2V9Ikjwf7u8/SxuNO+cfWYRgtXCfafmyk1 YxLRk2m5z5nzzlYHT8S+WN58z+viPpFERUSEAO5B5QyWKCfzRpfEJgPQjn+Zo0kin0LF5Ko+Y16 dymWjZRDTEf67WLODEHDjt/g6giXWeKKLl8y8zP1aR7zshHdmxx8m/YnW0T/hbIybhDvLXVApXD wMhvCcRT9g2Y4V71YbruXDW1/lqsWLT5LVbNLFQR8My1pove1/Ajq1mjnk4jGrJFA5wky6ZLJ7r zPAMwQ24DpNjDMbgYvDYncX3WEo3TXSPp79h8In6cSKe4cxjTlztskXAtJMXcTg/Vee+fnrRe3K UzxUPHq8/yJ+TtZQT5HnIFWg41LnLlEElwPy2vYnK/YL0gxzZTbtA72uGf6IMY972sL0moU4j0G JCqwlom1xJo1r/IbTyel3R2AEJCTlSz+09/3v03CyjMIN3pRkv+Lpl9bl0HvCD3/5SVNWNlA8Zy epnIoxPHZtK6mBw== 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 implementation of AwbAlgorithm is the following one: AwbAlgorithm o---- RkISP1Awb ^ | |-----| | | Bayes Grey The platform-specific Awb implementation instantiate one of the Bayes or Grey Awb helper classes and both inherit from the AwbAlgorithm base class. The handling of libcamera controls is split between the base and the derived (mostly Bayes) class. We want instead the AwbAlgorithm to handle all-things-libcamera and let the Bayes and Grey classes implement the logic. In order to achieve that use composition also in the AwbAlgorithm class and introduce an AwbImplementation base class from which Bayes and Grey derive from. AwbImplementation o---- AwbAlgorithmBase ^ ^ | | |-----| AwbAlgorithm> o---- RkISP1Awb | | Bayes Grey The AwbAlgorithm<> class is instantiated with a Q template argument that represents the platform-specific register format which is used to initialize controls and clamp gains to the hardware limits. Signed-off-by: Jacopo Mondi Signed-off-by: Kieran Bingham Reviewed-by: Daniel Scally Tested-by: Daniel Scally Tested-by: Robert Mader --- v3->v4: - s/Awb|awb/AWB wherever appropriate - Make AwbStats pure virtual - Reduce comment changes to the bare minimum to avoid churn --- src/ipa/libipa/awb.cpp | 418 ++++++++++++++++++++++++++++++++++---- src/ipa/libipa/awb.h | 100 +++++++-- src/ipa/libipa/awb_bayes.cpp | 52 ++--- src/ipa/libipa/awb_bayes.h | 12 +- src/ipa/libipa/awb_grey.cpp | 16 +- src/ipa/libipa/awb_grey.h | 6 +- src/ipa/rkisp1/algorithms/awb.cpp | 191 +++-------------- src/ipa/rkisp1/algorithms/awb.h | 18 +- src/ipa/rkisp1/algorithms/ccm.cpp | 4 +- src/ipa/rkisp1/algorithms/lsc.cpp | 2 +- src/ipa/rkisp1/ipa_context.h | 29 +-- 11 files changed, 549 insertions(+), 299 deletions(-) diff --git a/src/ipa/libipa/awb.cpp b/src/ipa/libipa/awb.cpp index 29a78069c52c..da835bec018d 100644 --- a/src/ipa/libipa/awb.cpp +++ b/src/ipa/libipa/awb.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2024 Ideas on Board Oy * - * Generic AWB algorithms + * libIPA AWB algorithms */ #include "awb.h" @@ -11,9 +11,16 @@ #include +#include "awb_bayes.h" +#include "awb_grey.h" + +constexpr int32_t kMinColourTemperature = 2500; +constexpr int32_t kMaxColourTemperature = 10000; +constexpr int32_t kDefaultColourTemperature = 5000; + /** * \file awb.h - * \brief Base classes for AWB algorithms + * \brief libipa AWB implementation */ namespace libcamera { @@ -22,30 +29,55 @@ LOG_DEFINE_CATEGORY(Awb) namespace ipa { +namespace awb { + /** - * \class AwbResult - * \brief The result of an AWB calculation + * \struct Context + * \brief AWB gains and colour temperature + * + * \var Context::gains + * \brief The white balance gains * - * This class holds the result of an auto white balance calculation. + * \var Context::colourTemperature + * \brief The colour temperature, in Kelvin */ /** - * \var AwbResult::gains - * \brief The calculated white balance gains + * \struct ActiveState + * \brief Active AWB state shared across frames + * + * \var ActiveState::manual + * \brief The most recent manually requested AWB state + * + * \var ActiveState::automatic + * \brief The most recent automatically calculated AWB state + * + * \var ActiveState::autoEnabled + * \brief True when automatic AWB is selected */ /** - * \var AwbResult::colourTemperature - * \brief The calculated colour temperature in Kelvin + * \struct FrameContext + * \brief Per-frame AWB state + * + * \var FrameContext::autoEnabled + * \brief True when automatic AWB is in use */ +} /* namespace awb */ + /** * \class AwbStats * \brief An abstraction class wrapping hardware-specific AWB statistics * - * IPA modules using an AWB algorithm based on the AwbAlgorithm class need to - * implement this class to give the algorithm access to the hardware-specific - * statistics data. + * IPA modules using libIPA AwbAlgorithm class need to provide a derived + * implementation of this class to give the algorithm access to the + * hardware-specific statistics data. + */ + +/** + * AwbStat::~AwbStat + * \brief Virtual class destructor */ /** @@ -64,6 +96,17 @@ namespace ipa { * \return The computed error value */ +/** + * \fn AwbStats::valid() + * \brief Retrieve if the AWB statistics are valid + * + * If the colour mean values are too small, the AwbAlgorithm class doesn't have + * enough information to meaningfully calculate white-balance gains. Freeze the + * algorithm in that case. + * + * \return True if the AWB statistics are valid, false otherwise + */ + /** * \fn AwbStats::rgbMeans() * \brief Get RGB means of the statistics @@ -72,31 +115,63 @@ namespace ipa { * dimensionless and only the ratios are used for further calculations. This is * used by the simple grey world model to calculate the gains to apply. * + * \todo Make a requirement to return statistics in the [0, 1] range. + * * \return The RGB means */ /** - * \class AwbAlgorithm - * \brief A base class for auto white balance algorithms + * \class AwbImplementation + * \brief Pure virtual base class for AWB algorithms implementations + * + * The AwbImplementation class defines the interface for the AWB algorithm + * implementations. + * + * It is currently implemented by the AwbGrey and AwbBayes classes. + * + * The interface defines an init() function to initialize the algorithm with the + * content of the tuning file and two functions to compute colour gains + * according to the algorithm operating mode (auto or manual) in use. * - * This class is a base class for auto white balance algorithms. It defines an - * interface for the algorithms to implement, and is used by the IPAs to - * interact with the concrete implementation. + * The calculateAwb() function calculates colour gains given a set of statistics + * provided by the IPA module. It is used when the algorithm operates in auto + * mode and gains are dynamically computed given a new set of statistics from + * the AWB engine. + * + * The gainsFromColourTemperature() function instead computes the white balance + * gains from a colour temperature. This function does not take any statistics + * into account. It is used to compute the colour gains when the user manually + * specifies a colour temperature. */ /** - * \fn AwbAlgorithm::init() - * \brief Initialize the algorithm with the given tuning data - * \param[in] tuningData The tuning data to use for the algorithm + * \struct AwbImplementation::Result + * \brief The result of an AWB computation * - * \return 0 on success, a negative error code otherwise + * \var AwbImplementation::Result::gains + * \brief The computed white balance gains + * + * \var AwbImplementation::Result::colourTemperature + * \brief The computed colour temperature, in Kelvin + */ + +/** + * \fn AwbImplementation::~AwbImplementation + * \brief Virtual class destructor + */ + +/** + * \fn AwbImplementation::init() + * \param[in] tuningData + * \brief Initialize the algorithm by parsing \a tuningData */ /** - * \fn AwbAlgorithm::calculateAwb() + * \fn AwbImplementation::calculateAwb() * \brief Calculate AWB data from the given statistics * \param[in] stats The statistics to use for the calculation * \param[in] lux The lux value of the scene + * \param[in] range The colour temperature search limits * * Calculate an AwbResult object from the given statistics and lux value. A \a * lux value of 0 means it is unknown or invalid and the algorithm shall ignore @@ -106,7 +181,7 @@ namespace ipa { */ /** - * \fn AwbAlgorithm::gainsFromColourTemperature() + * \fn AwbImplementation::gainsFromColourTemperature() * \brief Compute white balance gains from a colour temperature * \param[in] colourTemperature The colour temperature in Kelvin * @@ -118,26 +193,225 @@ namespace ipa { */ /** - * \fn AwbAlgorithm::controls() - * \brief Get the controls info map for this algorithm + * \class AwbAlgorithmBase + * \brief Base class for AwbAlgorithm for non-templated functions implementation + * + * Base class for AwbAlgorithm where non-templated functions are implemented. + * IPA implementations shall use AwbAlgorithm and not this class. + */ + +/** + * \brief Initialize the algorithm with the given tuning data + * \param[in] tuningData The tuning data to use for the algorithm + * + * Parse \a tuningData to initialize the AWB algorithm and register controls. + * IPA modules are expected to call this function as part of their + * implementation of Algorithm::init(). + * + * \return 0 on success, a negative error code otherwise + */ +int AwbAlgorithmBase::init(const ValueNode &tuningData) +{ + bayes_ = false; + + if (!tuningData.contains("algorithm")) + LOG(Awb, Info) << "No AWB algorithm specified, using grey world"; + + auto mode = tuningData["algorithm"].get("grey"); + if (mode == "grey") { + impl_ = std::make_unique(); + } else if (mode == "bayes") { + impl_ = std::make_unique(); + bayes_ = true; + } else { + LOG(Awb, Error) << "Unknown AWB algorithm: " << mode; + return -EINVAL; + } + + LOG(Awb, Debug) << "Using AWB algorithm: " << mode; + + int ret = impl_->init(tuningData); + if (ret) + return ret; + + controls_[&controls::ColourTemperature] = + ControlInfo(kMinColourTemperature, kMaxColourTemperature, + kDefaultColourTemperature); + controls_[&controls::AwbEnable] = ControlInfo(false, true); + + return parseModeConfigs(tuningData, controls::AwbAuto); +} + +/** + * \brief Configure the AWB algorithm + * \param[in] state The AWB active state + * \return 0 if successful, an error code otherwise + */ +int AwbAlgorithmBase::configure(awb::ActiveState &state) +{ + state.manual.gains = RGB{ 1.0 }; + auto gains = impl_->gainsFromColourTemperature(kDefaultColourTemperature); + if (gains) + state.automatic.gains = *gains; + else + state.automatic.gains = RGB{ 1.0 }; + + state.autoEnabled = true; + state.manual.colourTemperature = kDefaultColourTemperature; + state.automatic.colourTemperature = kDefaultColourTemperature; + + return 0; +} + +/** + * \brief Queue a Request to the AWB algorithm + * \param[in] state The AWB active state + * \param[in] frame The frame number + * \param[in] frameContext The AWB frame context + * \param[in] controls The list of controls part of the Request * - * \return The controls info map + * Queue a new Request to the AWB algorithm and modify its behaviour according + * to the provided controls. + * + * The currently handled controls are: + * - controls::AwbEnable + * - controls::AwbMode + * - controls::ColourGains + * - controls::ColourTemperature */ +void AwbAlgorithmBase::queueRequest(awb::ActiveState &state, + [[maybe_unused]] const uint32_t frame, + awb::FrameContext &frameContext, + const ControlList &controls) +{ + const auto &awbEnable = controls.get(controls::AwbEnable); + if (awbEnable && *awbEnable != state.autoEnabled) { + state.autoEnabled = *awbEnable; + + LOG(Awb, Debug) + << (*awbEnable ? "Enabling" : "Disabling") << " Awb"; + } + + auto mode = controls.get(controls::AwbMode); + if (mode) { + auto it = modes_.find(static_cast(*mode)); + if (it == modes_.end()) { + LOG(Awb, Error) << "Unsupported AWB mode " << *mode; + return; + } + + currentMode_ = &it->second; + } + + frameContext.autoEnabled = state.autoEnabled; + + if (frameContext.autoEnabled) + return; + + const auto &colourGains = controls.get(controls::ColourGains); + const auto &colourTemperature = controls.get(controls::ColourTemperature); + bool update = false; + if (colourGains) { + state.manual.gains.r() = (*colourGains)[0]; + state.manual.gains.b() = (*colourGains)[1]; + /* + * \todo Colour temperature reported in metadata is now + * incorrect, as we can't deduce the temperature from the gains. + * This will be fixed with the bayes AWB algorithm. + */ + update = true; + } else if (colourTemperature) { + state.manual.colourTemperature = *colourTemperature; + const auto &gains = impl_->gainsFromColourTemperature(*colourTemperature); + if (gains) { + state.manual.gains.r() = gains->r(); + state.manual.gains.b() = gains->b(); + update = true; + } + } + + if (update) + LOG(Awb, Debug) + << "Set colour gains to " << state.manual.gains; + + frameContext.gains = state.manual.gains; + frameContext.colourTemperature = state.manual.colourTemperature; +} /** - * \fn AwbAlgorithm::handleControls() - * \param[in] controls The controls to handle - * \brief Handle the controls supplied in a request + * \brief Set the gains and colour temperature values in \a frameContext + * \param[in] state The AWB active state + * \param[in] frameContext The AWB frame context + * + * If auto mode is enabled, take the most recently computed gains and use them + * for the current frame. Otherwise, if in manual mode, gains and colour + * temperature for a frame are set at queueRequest() time. */ +void AwbAlgorithmBase::prepare(awb::ActiveState &state, + awb::FrameContext &frameContext) +{ + if (frameContext.autoEnabled) { + frameContext.gains = state.automatic.gains; + frameContext.colourTemperature = state.automatic.colourTemperature; + } +} /** + * \brief Process AWB statistics to calculate gains and populate metadata + * \param[in] state The AWB active state + * \param[in] frameContext The AWB frame context + * \param[in] stats The AWB statistics + * \param[in] lux The lux value as estimated by the IPA module + * \param[out] metadata The metadata list + * + * Process \a stats to calculate new gains and colour temperature and populate + * \a metadata with the results. + */ +void AwbAlgorithmBase::process(awb::ActiveState &state, + awb::FrameContext &frameContext, + const AwbStats &stats, unsigned int lux, + ControlList &metadata) +{ + if (!stats.valid()) + return; + + auto awbResult = impl_->calculateAwb(stats, lux, { currentMode_->ctLo, + currentMode_->ctHi }); + + /* + * Clamp the gain values to the hardware, according to the gainMin_ + * and gainMax_ values. + */ + awbResult.gains = awbResult.gains.clamp(gainMin_, gainMax_); + + /* Smooth color gains adjustments. */ + double speed = 0.2; + double ct = awbResult.colourTemperature; + ct = ct * speed + state.automatic.colourTemperature * (1 - speed); + + state.automatic.colourTemperature = awbResult.colourTemperature; + state.automatic.gains = awbResult.gains * speed + + state.automatic.gains * (1 - speed); + + /* Populate metadata. */ + metadata.set(controls::AwbEnable, frameContext.autoEnabled); + metadata.set(controls::ColourGains, { static_cast(frameContext.gains.r()), + static_cast(frameContext.gains.b()) }); + metadata.set(controls::ColourTemperature, frameContext.colourTemperature); + + LOG(Awb, Debug) << std::showpoint << "Means " << stats.rgbMeans() + << ", gains " << state.automatic.gains + << ", temp " << state.automatic.colourTemperature << "K"; +} + +/* * \brief Parse the mode configurations from the tuning data * \param[in] tuningData the ValueNode representing the tuning data * \param[in] def The default value for the AwbMode control * * Utility function to parse the tuning data for an AwbMode entry and read all - * provided modes. It adds controls::AwbMode to AwbAlgorithm::controls_ and - * populates AwbAlgorithm::modes_. For a list of possible modes see \ref + * provided modes. It adds controls::AwbMode to AwbAlgorithmBase::controls_ and + * populates AwbAlgorithmBase::modes_. For a list of possible modes see \ref * controls::AwbModeEnum. * * Each mode entry must contain a "lo" and "hi" key to specify the lower and @@ -156,15 +430,23 @@ namespace ipa { * ... * \endcode * - * If \a def is supplied but not contained in the the \a tuningData, -EINVAL is + * If \a def is supplied but not contained in the \a tuningData, -EINVAL is * returned. * + * AwbModes are only used by the AwbBayes implementation. + * * \sa controls::AwbModeEnum * \return Zero on success, negative error code otherwise */ -int AwbAlgorithm::parseModeConfigs(const ValueNode &tuningData, - const ControlValue &def) +int AwbAlgorithmBase::parseModeConfigs(const ValueNode &tuningData, + const ControlValue &def) { + if (!bayes_) { + /* AwbGrey does not support and does not use modes. */ + currentMode_ = &AwbGreyMode; + return 0; + } + std::vector availableModes; const ValueNode &modes = tuningData[controls::AwbMode.name()]; @@ -227,37 +509,83 @@ int AwbAlgorithm::parseModeConfigs(const ValueNode &tuningData, } controls_[&controls::AwbMode] = ControlInfo(availableModes, def); + currentMode_ = &modes_[controls::AwbAuto]; return 0; } /** - * \class AwbAlgorithm::ModeConfig + * \var AwbAlgorithmBase::controls_ + * \brief Controls info map for the controls registered by the AWB algorithm + */ + +/** + * \var AwbAlgorithmBase::gainMin_ + * \brief The minimum supported gain value + * + * Minimum gain value used to clamp the AWB algorithm calculation results in the + * range supported by the platform AWB engine. + * + * The min and max gain values are initialized by AwbAlgorithm::init(). + */ + +/** + * \var AwbAlgorithmBase::gainMax_ + * \brief The maximum supported gain value + * + * Maximum gain value used to clamp the AWB algorithm calculation results in the + * range supported by the platform AWB engine. + * + * The min and max gain values are initialized by AwbAlgorithm::init(). + */ + +/* + * \class AwbAlgorithmBase::ModeConfig * \brief Holds the configuration of a single AWB mode * * AWB modes limit the regulation of the AWB algorithm to a specific range of - * colour temperatures. + * colour temperatures. Use by AwbBayes only. */ -/** - * \var AwbAlgorithm::ModeConfig::ctLo +/* + * \var AwbAlgorithmBase::ModeConfig::ctLo * \brief The lowest valid colour temperature of that mode */ -/** - * \var AwbAlgorithm::ModeConfig::ctHi +/* + * \var AwbAlgorithmBase::ModeConfig::ctHi * \brief The highest valid colour temperature of that mode */ +/* + * \var AwbAlgorithmBase::modes_ + * \brief Map of all configured modes + * \sa AwbAlgorithmBase::parseModeConfigs + */ + /** - * \var AwbAlgorithm::controls_ - * \brief Controls info map for the controls provided by the algorithm + * \class AwbAlgorithm + * \brief The libipa AWB algorithm + * \tparam Q The fixedpoint register representation of gain values + * + * Implement the AWB algorithm for libipa. + * + * The AwbAlgorithm class implements an interface similar in spirit to the one + * of the Algorithm class. IPA modules are expected to store an instance of + * AwbAlgorithm as class member, template it with the AWB engine gain register + * representation and call its function in their implementations of the + * Algorithm interface. + * + * The AwbAlgorithm instantiates an AwbImplementation implementation (AwbGrey or + * AwbBayes) at init() time by parsing the tuning data and uses it to compute + * the RGB gains and estimate a colour temperature given a set of statistics + * in the form of a AwbStats derived implementation. */ /** - * \var AwbAlgorithm::modes_ - * \brief Map of all configured modes - * \sa AwbAlgorithm::parseModeConfigs + * \fn AwbAlgorithm::init() + * \param[in] controls The info map of the IPA controls + * \copydoc AwbAlgorithmBase::init() */ } /* namespace ipa */ diff --git a/src/ipa/libipa/awb.h b/src/ipa/libipa/awb.h index 09c00e47d604..d35a85167c20 100644 --- a/src/ipa/libipa/awb.h +++ b/src/ipa/libipa/awb.h @@ -2,11 +2,12 @@ /* * Copyright (C) 2024 Ideas on Board Oy * - * Generic AWB algorithms + * libIPA AWB algorithms */ #pragma once +#include #include #include @@ -20,46 +21,113 @@ namespace libcamera { namespace ipa { -struct AwbResult { +namespace awb { + +struct Context { RGB gains; - double colourTemperature; + unsigned int colourTemperature; }; +struct ActiveState { + Context manual; + Context automatic; + + bool autoEnabled; +}; + +struct FrameContext : public Context { + bool autoEnabled; +}; + +} /* namespace awb */ + struct AwbStats { virtual double computeColourError(const RGB &gains) const = 0; + virtual bool valid() const = 0; virtual RGB rgbMeans() const = 0; protected: ~AwbStats() = default; }; -class AwbAlgorithm +class AwbImplementation { public: - virtual ~AwbAlgorithm() = default; + struct Result { + RGB gains; + unsigned int colourTemperature; + }; + virtual ~AwbImplementation() = default; virtual int init(const ValueNode &tuningData) = 0; - virtual AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) = 0; - virtual std::optional> gainsFromColourTemperature(double colourTemperature) = 0; + virtual Result calculateAwb(const AwbStats &stats, unsigned int lux, + std::array range) = 0; + virtual std::optional> + gainsFromColourTemperature(double colourTemperature) = 0; +}; - const ControlInfoMap::Map &controls() const - { - return controls_; - } +class AwbAlgorithmBase +{ +public: + int init(const ValueNode &tuningData); + + int configure(awb::ActiveState &state); - virtual void handleControls([[maybe_unused]] const ControlList &controls) {} + void queueRequest(awb::ActiveState &state, + const uint32_t frame, + awb::FrameContext &frameContext, + const ControlList &controls); + + void prepare(awb::ActiveState &state, awb::FrameContext &frameContext); + + void process(awb::ActiveState &state, awb::FrameContext &frameContext, + const AwbStats &stats, unsigned int lux, + ControlList &metadata); protected: - int parseModeConfigs(const ValueNode &tuningData, - const ControlValue &def = {}); + AwbAlgorithmBase() = default; + ControlInfoMap::Map controls_; + float gainMin_; + float gainMax_; + +private: struct ModeConfig { double ctHi; double ctLo; }; - ControlInfoMap::Map controls_; - std::map modes_; + /* AwbGrey does not support modes; */ + static constexpr ModeConfig AwbGreyMode = { 0.0, 0.0 }; + + int parseModeConfigs(const ValueNode &tuningData, + const ControlValue &def = {}); + + std::map modes_; + const ModeConfig *currentMode_ = nullptr; + std::unique_ptr impl_; + bool bayes_ = false; +}; + +template +class AwbAlgorithm : public AwbAlgorithmBase +{ +public: + int init(const ValueNode &tuningData, ControlInfoMap::Map &controls) + { + AwbAlgorithmBase::init(tuningData); + + gainMin_ = std::max(Q::TraitsType::min, 1.0f); + gainMax_ = Q::TraitsType::max; + + controls_[&controls::ColourGains] = + ControlInfo(gainMin_, gainMax_, + Span{ { 1.0f, 1.0f } }); + + controls.insert(controls_.begin(), controls_.end()); + + return 0; + } }; } /* namespace ipa */ diff --git a/src/ipa/libipa/awb_bayes.cpp b/src/ipa/libipa/awb_bayes.cpp index 9fd85e5a4505..2c10f9c4c82d 100644 --- a/src/ipa/libipa/awb_bayes.cpp +++ b/src/ipa/libipa/awb_bayes.cpp @@ -140,11 +140,6 @@ void Interpolator::interpolate(const Pwl &a, const Pwl &b, Pwl &dest, doubl * \brief How far to wander off CT curve towards "more green" */ -/** - * \var AwbBayes::currentMode_ - * \brief The currently selected mode - */ - int AwbBayes::init(const ValueNode &tuningData) { int ret = colourGainCurve_.readYaml(tuningData["colourGains"], "ct", "gains"); @@ -172,14 +167,6 @@ int AwbBayes::init(const ValueNode &tuningData) return ret; } - ret = parseModeConfigs(tuningData, controls::AwbAuto); - if (ret) { - LOG(Awb, Error) - << "Failed to parse mode parameter from tuning file"; - return ret; - } - currentMode_ = &modes_[controls::AwbAuto]; - transversePos_ = tuningData["transversePos"].get(0.01); transverseNeg_ = tuningData["transverseNeg"].get(0.01); if (transversePos_ <= 0 || transverseNeg_ <= 0) { @@ -260,18 +247,16 @@ int AwbBayes::readPriors(const ValueNode &tuningData) return 0; } -void AwbBayes::handleControls(const ControlList &controls) -{ - auto mode = controls.get(controls::AwbMode); - if (mode) { - auto it = modes_.find(static_cast(*mode)); - if (it != modes_.end()) - currentMode_ = &it->second; - else - LOG(Awb, Error) << "Unsupported AWB mode " << *mode; - } -} - +/** + * \brief Compute white balance gains from a colour temperature + * \param[in] colourTemperature The colour temperature in Kelvin + * + * Compute the white balance gains from a \a colourTemperature. This function + * does not take any statistics into account. It simply interpolates the colour + * gains curve on a new colour temperature. + * + * \return The RGB gain values adjusted to \a temperatureK + */ std::optional> AwbBayes::gainsFromColourTemperature(double colourTemperature) { /* @@ -283,7 +268,9 @@ std::optional> AwbBayes::gainsFromColourTemperature(double colourTem return RGB{ { gains[0], 1.0, gains[1] } }; } -AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux) +AwbImplementation::Result +AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux, + std::array range) { ipa::Pwl prior; if (lux > 0) { @@ -295,7 +282,7 @@ AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux) prior.append(0, 1.0); } - double t = coarseSearch(prior, stats); + double t = coarseSearch(prior, stats, range); double r = ctR_.eval(t); double b = ctB_.eval(t); LOG(Awb, Debug) @@ -316,14 +303,15 @@ AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux) << "After fine search: r " << r << " b " << b << " (gains r " << 1 / r << " b " << 1 / b << ")"; - return { { { 1.0 / r, 1.0, 1.0 / b } }, t }; + return { { { 1.0 / r, 1.0, 1.0 / b } }, static_cast(t) }; } -double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) const +double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats, + Span range) const { std::vector points; size_t bestPoint = 0; - double t = currentMode_->ctLo; + double t = range[0]; int spanR = -1; int spanB = -1; LimitsRecorder errorLimits; @@ -345,14 +333,14 @@ double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) cons if (points.back().y() < points[bestPoint].y()) bestPoint = points.size() - 1; - if (t == currentMode_->ctHi) + if (t == range[1]) break; /* * Ensure even steps along the r/b curve by scaling them by the * current t. */ - t = std::min(t + t / 10 * kSearchStep, currentMode_->ctHi); + t = std::min(t + t / 10 * kSearchStep, range[1]); } t = points[bestPoint].x(); diff --git a/src/ipa/libipa/awb_bayes.h b/src/ipa/libipa/awb_bayes.h index 1e3373676bc0..8d4cff076d4a 100644 --- a/src/ipa/libipa/awb_bayes.h +++ b/src/ipa/libipa/awb_bayes.h @@ -20,22 +20,24 @@ namespace libcamera { namespace ipa { -class AwbBayes : public AwbAlgorithm +class AwbBayes : public AwbImplementation { public: AwbBayes() = default; int init(const ValueNode &tuningData) override; - AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) override; + AwbImplementation::Result + calculateAwb(const AwbStats &stats, unsigned int lux, + std::array range) override; std::optional> gainsFromColourTemperature(double temperatureK) override; - void handleControls(const ControlList &controls) override; private: int readPriors(const ValueNode &tuningData); void fineSearch(double &t, double &r, double &b, ipa::Pwl const &prior, const AwbStats &stats) const; - double coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) const; + double coarseSearch(const ipa::Pwl &prior, const AwbStats &stats, + Span range) const; double interpolateQuadratic(ipa::Pwl::Point const &a, ipa::Pwl::Point const &b, ipa::Pwl::Point const &c) const; @@ -50,8 +52,6 @@ private: double transversePos_; double transverseNeg_; - - ModeConfig *currentMode_ = nullptr; }; } /* namespace ipa */ diff --git a/src/ipa/libipa/awb_grey.cpp b/src/ipa/libipa/awb_grey.cpp index b14b096810ae..b12229023883 100644 --- a/src/ipa/libipa/awb_grey.cpp +++ b/src/ipa/libipa/awb_grey.cpp @@ -60,6 +60,7 @@ int AwbGrey::init(const ValueNode &tuningData) * \brief Calculate AWB data from the given statistics * \param[in] stats The statistics to use for the calculation * \param[in] lux The lux value of the scene + * \param[in] range The colour temperature search limits * * The colour temperature is estimated based on the colours::estimateCCT() * function. The gains are calculated purely based on the RGB means provided by @@ -70,9 +71,11 @@ int AwbGrey::init(const ValueNode &tuningData) * * \return The AWB result */ -AwbResult AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned int lux) +AwbImplementation::Result +AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned int lux, + [[maybe_unused]] std::array range) { - AwbResult result; + AwbImplementation::Result result; auto means = stats.rgbMeans(); result.colourTemperature = estimateCCT(means); @@ -93,15 +96,16 @@ AwbResult AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned * * Compute the white balance gains from a \a colourTemperature. This function * does not take any statistics into account. It simply interpolates the colour - * gains configured in the colour temperature curve. + * gains curve (if provided in the tuning file) on a new colour temperature. * - * \return The colour gains if a colour temperature curve is available, - * [1, 1, 1] otherwise. + * \return The RGB gain values adjusted to \a colourTemperature or std::nullopt if + * no gain curve is specified in the tuning data */ std::optional> AwbGrey::gainsFromColourTemperature(double colourTemperature) { if (!colourGainCurve_) { - LOG(Awb, Error) << "No gains defined"; + LOG(Awb, Info) << "No gains curve defined, " + << "unable to interpolate gains to colour temperature"; return std::nullopt; } diff --git a/src/ipa/libipa/awb_grey.h b/src/ipa/libipa/awb_grey.h index 154a2af97f15..d1b0088f0470 100644 --- a/src/ipa/libipa/awb_grey.h +++ b/src/ipa/libipa/awb_grey.h @@ -18,13 +18,15 @@ namespace libcamera { namespace ipa { -class AwbGrey : public AwbAlgorithm +class AwbGrey : public AwbImplementation { public: AwbGrey() = default; int init(const ValueNode &tuningData) override; - AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) override; + AwbImplementation::Result + calculateAwb(const AwbStats &stats, unsigned int lux, + std::array range) override; std::optional> gainsFromColourTemperature(double colourTemperature) override; private: diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp index 0eb05c2b6e1a..cf8dc14308e2 100644 --- a/src/ipa/rkisp1/algorithms/awb.cpp +++ b/src/ipa/rkisp1/algorithms/awb.cpp @@ -8,17 +8,12 @@ #include "awb.h" #include -#include #include -#include - #include -#include "libipa/awb_bayes.h" -#include "libipa/awb_grey.h" -#include "libipa/colours.h" +#include "libcamera/internal/vector.h" /** * \file awb.h @@ -28,20 +23,8 @@ namespace libcamera { namespace ipa::rkisp1::algorithms { -/** - * \class Awb - * \brief Manage the white balance with automatic and manual controls - */ - LOG_DEFINE_CATEGORY(RkISP1Awb) -constexpr int32_t kMinColourTemperature = 2500; -constexpr int32_t kMaxColourTemperature = 10000; -constexpr int32_t kDefaultColourTemperature = 5000; - -/* Minimum mean value below which AWB can't operate. */ -constexpr double kMeanMinThreshold = 2.0; - class RkISP1AwbStats final : public AwbStats { public: @@ -70,12 +53,26 @@ public: return rgbMeans_; } + bool valid() const override + { + /* Minimum mean value below which AWB can't operate. */ + constexpr double minValue = 2.0; + + return rgbMeans_.r() > minValue || rgbMeans_.g() > minValue || + rgbMeans_.b() > minValue; + } + private: RGB rgbMeans_; double rg_; double bg_; }; +/** + * \class Awb + * \brief Manage the white balance with automatic and manual controls + */ + Awb::Awb() : rgbMode_(false) { @@ -86,40 +83,7 @@ Awb::Awb() */ int Awb::init(IPAContext &context, const ValueNode &tuningData) { - auto &cmap = context.ctrlMap; - cmap[&controls::ColourTemperature] = ControlInfo(kMinColourTemperature, - kMaxColourTemperature, - kDefaultColourTemperature); - cmap[&controls::AwbEnable] = ControlInfo(false, true); - - cmap[&controls::ColourGains] = ControlInfo(0.0f, 3.996f, - Span{ { 1.0f, 1.0f } }); - - if (!tuningData.contains("algorithm")) - LOG(RkISP1Awb, Info) << "No AWB algorithm specified." - << " Default to grey world"; - - auto mode = tuningData["algorithm"].get("grey"); - if (mode == "grey") { - awbAlgo_ = std::make_unique(); - } else if (mode == "bayes") { - awbAlgo_ = std::make_unique(); - } else { - LOG(RkISP1Awb, Error) << "Unknown AWB algorithm: " << mode; - return -EINVAL; - } - LOG(RkISP1Awb, Debug) << "Using AWB algorithm: " << mode; - - int ret = awbAlgo_->init(tuningData); - if (ret) { - LOG(RkISP1Awb, Error) << "Failed to init AWB algorithm"; - return ret; - } - - const auto &src = awbAlgo_->controls(); - cmap.insert(src.begin(), src.end()); - - return 0; + return awbAlgo_.init(tuningData, context.ctrlMap); } /** @@ -128,16 +92,7 @@ int Awb::init(IPAContext &context, const ValueNode &tuningData) int Awb::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) { - context.activeState.awb.manual.gains = RGB{ 1.0 }; - auto gains = awbAlgo_->gainsFromColourTemperature(kDefaultColourTemperature); - if (gains) - context.activeState.awb.automatic.gains = *gains; - else - context.activeState.awb.automatic.gains = RGB{ 1.0 }; - - context.activeState.awb.autoEnabled = true; - context.activeState.awb.manual.temperatureK = kDefaultColourTemperature; - context.activeState.awb.automatic.temperatureK = kDefaultColourTemperature; + awbAlgo_.configure(context.activeState.awb); /* * Define the measurement window for AWB as a centered rectangle @@ -156,56 +111,12 @@ int Awb::configure(IPAContext &context, /** * \copydoc libcamera::ipa::Algorithm::queueRequest */ -void Awb::queueRequest(IPAContext &context, - [[maybe_unused]] const uint32_t frame, +void Awb::queueRequest(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, const ControlList &controls) { - auto &awb = context.activeState.awb; - - const auto &awbEnable = controls.get(controls::AwbEnable); - if (awbEnable && *awbEnable != awb.autoEnabled) { - awb.autoEnabled = *awbEnable; - - LOG(RkISP1Awb, Debug) - << (*awbEnable ? "Enabling" : "Disabling") << " AWB"; - } - - awbAlgo_->handleControls(controls); - - frameContext.awb.autoEnabled = awb.autoEnabled; - - if (awb.autoEnabled) - return; - - const auto &colourGains = controls.get(controls::ColourGains); - const auto &colourTemperature = controls.get(controls::ColourTemperature); - bool update = false; - if (colourGains) { - awb.manual.gains.r() = (*colourGains)[0]; - awb.manual.gains.b() = (*colourGains)[1]; - /* - * \todo Colour temperature reported in metadata is now - * incorrect, as we can't deduce the temperature from the gains. - * This will be fixed with the bayes AWB algorithm. - */ - update = true; - } else if (colourTemperature) { - awb.manual.temperatureK = *colourTemperature; - const auto &gains = awbAlgo_->gainsFromColourTemperature(*colourTemperature); - if (gains) { - awb.manual.gains.r() = gains->r(); - awb.manual.gains.b() = gains->b(); - update = true; - } - } - - if (update) - LOG(RkISP1Awb, Debug) - << "Set colour gains to " << awb.manual.gains; - - frameContext.awb.gains = awb.manual.gains; - frameContext.awb.temperatureK = awb.manual.temperatureK; + awbAlgo_.queueRequest(context.activeState.awb, frame, frameContext.awb, + controls); } /** @@ -214,15 +125,7 @@ void Awb::queueRequest(IPAContext &context, void Awb::prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, RkISP1Params *params) { - /* - * This is the latest time we can read the active state. This is the - * most up-to-date automatic values we can read. - */ - if (frameContext.awb.autoEnabled) { - const auto &awb = context.activeState.awb; - frameContext.awb.gains = awb.automatic.gains; - frameContext.awb.temperatureK = awb.automatic.temperatureK; - } + awbAlgo_.prepare(context.activeState.awb, frameContext.awb); auto gainConfig = params->block(); gainConfig.setEnabled(true); @@ -291,15 +194,6 @@ void Awb::process(IPAContext &context, const rkisp1_stat_buffer *stats, ControlList &metadata) { - IPAActiveState &activeState = context.activeState; - - metadata.set(controls::AwbEnable, frameContext.awb.autoEnabled); - metadata.set(controls::ColourGains, { - static_cast(frameContext.awb.gains.r()), - static_cast(frameContext.awb.gains.b()) - }); - metadata.set(controls::ColourTemperature, frameContext.awb.temperatureK); - if (!stats || !(stats->meas_type & RKISP1_CIF_ISP_STAT_AWB)) { LOG(RkISP1Awb, Error) << "AWB data is missing in statistics"; return; @@ -313,45 +207,14 @@ void Awb::process(IPAContext &context, return; } - RGB rgbMeans = calculateRgbMeans(frameContext, awb); - - /* - * If the means are too small we don't have enough information to - * meaningfully calculate gains. Freeze the algorithm in that case. - */ - if (rgbMeans.r() < kMeanMinThreshold && rgbMeans.g() < kMeanMinThreshold && - rgbMeans.b() < kMeanMinThreshold) - return; + RkISP1AwbStats awbStats = calculateRgbMeans(frameContext, awb); - RkISP1AwbStats awbStats{ rgbMeans }; - AwbResult awbResult = awbAlgo_->calculateAwb(awbStats, frameContext.lux.lux); - - /* - * Clamp the gain values to the hardware, which expresses gains as Q2.8 - * unsigned integer values. Set the minimum just above zero to avoid - * divisions by zero when computing the raw means in subsequent - * iterations. - */ - awbResult.gains = awbResult.gains.clamp(1.0 / 256, 1023.0 / 256); - - /* Filter the values to avoid oscillations. */ - double speed = 0.2; - double ct = awbResult.colourTemperature; - ct = ct * speed + activeState.awb.automatic.temperatureK * (1 - speed); - awbResult.gains = awbResult.gains * speed + - activeState.awb.automatic.gains * (1 - speed); - - activeState.awb.automatic.temperatureK = static_cast(ct); - activeState.awb.automatic.gains = awbResult.gains; - - LOG(RkISP1Awb, Debug) - << std::showpoint - << "Means " << rgbMeans << ", gains " - << activeState.awb.automatic.gains << ", temp " - << activeState.awb.automatic.temperatureK << "K"; + awbAlgo_.process(context.activeState.awb, frameContext.awb, awbStats, + frameContext.lux.lux, metadata); } -RGB Awb::calculateRgbMeans(const IPAFrameContext &frameContext, const rkisp1_cif_isp_awb_stat *awb) const +RkISP1AwbStats Awb::calculateRgbMeans(const IPAFrameContext &frameContext, + const rkisp1_cif_isp_awb_stat *awb) const { Vector rgbMeans; @@ -419,7 +282,7 @@ RGB Awb::calculateRgbMeans(const IPAFrameContext &frameContext, const rk */ rgbMeans /= frameContext.awb.gains.max(0.01); - return rgbMeans; + return RkISP1AwbStats(rgbMeans); } REGISTER_IPA_ALGORITHM(Awb, "Awb") diff --git a/src/ipa/rkisp1/algorithms/awb.h b/src/ipa/rkisp1/algorithms/awb.h index 60d9ef111495..89a3e37b20f9 100644 --- a/src/ipa/rkisp1/algorithms/awb.h +++ b/src/ipa/rkisp1/algorithms/awb.h @@ -7,17 +7,25 @@ #pragma once -#include "libcamera/internal/vector.h" +#include + +#include + +#include "libcamera/internal/value_node.h" #include "libipa/awb.h" -#include "libipa/interpolator.h" +#include "libipa/fixedpoint.h" #include "algorithm.h" +#include "ipa_context.h" +#include "params.h" namespace libcamera { namespace ipa::rkisp1::algorithms { +class RkISP1AwbStats; + class Awb : public Algorithm { public: @@ -38,10 +46,10 @@ public: ControlList &metadata) override; private: - RGB calculateRgbMeans(const IPAFrameContext &frameContext, - const rkisp1_cif_isp_awb_stat *awb) const; + RkISP1AwbStats calculateRgbMeans(const IPAFrameContext &frameContext, + const rkisp1_cif_isp_awb_stat *awb) const; - std::unique_ptr awbAlgo_; + AwbAlgorithm> awbAlgo_; bool rgbMode_; }; diff --git a/src/ipa/rkisp1/algorithms/ccm.cpp b/src/ipa/rkisp1/algorithms/ccm.cpp index 6bb9c7bcfcb3..633dc2ddb834 100644 --- a/src/ipa/rkisp1/algorithms/ccm.cpp +++ b/src/ipa/rkisp1/algorithms/ccm.cpp @@ -77,7 +77,7 @@ int Ccm::configure(IPAContext &context, { auto &as = context.activeState; as.ccm.manual = kIdentity3x3; - as.ccm.automatic = ccm_.getInterpolated(as.awb.automatic.temperatureK); + as.ccm.automatic = ccm_.getInterpolated(as.awb.automatic.colourTemperature); return 0; } @@ -140,7 +140,7 @@ void Ccm::prepare(IPAContext &context, const uint32_t frame, return; } - uint32_t ct = frameContext.awb.temperatureK; + uint32_t ct = frameContext.awb.colourTemperature; if (frame > 0 && ct == ct_) { frameContext.ccm.ccm = context.activeState.ccm.automatic; return; diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index faf88effde01..06f5913c5750 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -512,7 +512,7 @@ void LensShadingCorrection::prepare([[maybe_unused]] IPAContext &context, IPAFrameContext &frameContext, RkISP1Params *params) { - uint32_t ct = frameContext.awb.temperatureK; + uint32_t ct = frameContext.awb.colourTemperature; unsigned int quantizedCt = quantize(ct, kColourTemperatureQuantization); /* Check if we can skip the update. */ diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h index e5f70d934d67..c36c1f7e0084 100644 --- a/src/ipa/rkisp1/ipa_context.h +++ b/src/ipa/rkisp1/ipa_context.h @@ -25,6 +25,7 @@ #include "libcamera/internal/vector.h" #include "libipa/agc_mean_luminance.h" +#include "libipa/awb.h" #include "libipa/camera_sensor_helper.h" #include "libipa/fc_queue.h" #include "libipa/fixedpoint.h" @@ -48,15 +49,17 @@ struct IPAHwSettings { bool compand; }; +struct RKISP1AwbSession { + struct rkisp1_cif_isp_window measureWindow; + bool enabled; +}; + struct IPASessionConfiguration { struct { struct rkisp1_cif_isp_window measureWindow; } agc; - struct { - struct rkisp1_cif_isp_window measureWindow; - bool enabled; - } awb; + struct RKISP1AwbSession awb; struct { bool supported; @@ -100,17 +103,7 @@ struct IPAActiveState { utils::Duration maxFrameDuration; } agc; - struct { - struct AwbState { - RGB gains; - unsigned int temperatureK; - }; - - AwbState manual; - AwbState automatic; - - bool autoEnabled; - } awb; + ipa::awb::ActiveState awb; struct { Matrix manual; @@ -176,11 +169,7 @@ struct IPAFrameContext : public FrameContext { bool autoGainModeChange; } agc; - struct { - RGB gains; - bool autoEnabled; - unsigned int temperatureK; - } awb; + ipa::awb::FrameContext awb; struct { float actualBrightness; From patchwork Mon Jul 20 14:59:20 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27394 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 11771C328C for ; Mon, 20 Jul 2026 14:59:48 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B6FA467E62; Mon, 20 Jul 2026 16:59:46 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="u0n3pyYC"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 24B6667E50 for ; Mon, 20 Jul 2026 16:59:42 +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 0D1A4ABF; Mon, 20 Jul 2026 16:58:43 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559523; bh=dhl5gblzCqtHc/mQBW5GOxetdk2axICofVb774mO1ss=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=u0n3pyYCaN/goxE+ujtZ7nrUXlJB76Jg+sIEBfseRU3WaC+VBRrZYhTJGdYQPsOQV 8Akdm7ysyFVx8ThkkEcn0YIgEBJI0V/Jt5LMhMUo806VTkw1/N8xZgCzbP5ruYR7kC Ik0Uh+M6aeW39YQxfzLsuhDJey60hVbIKfl9IZ9w= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:20 +0200 Subject: [PATCH v6 02/31] ipa: mali-c55: awb: Port to use libipa AwbAlgorithm MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-2-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 , Kieran Bingham , Daniel Scally X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=13303; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=dhl5gblzCqtHc/mQBW5GOxetdk2axICofVb774mO1ss=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfR0Dbb1WHAVgtFr3e0tg9jiCQ2WMUDB1Gf5 Fc5WqFbb2mJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal430QAKCRByNAaPFqFW PHbGD/4nBnQlaPSSUmKtN72+MD7XfL+Z17vhpjYXD/MuJK/RsvKNeSP1ACRMmnJl9iZtfwcyP9S r/U47BzcwxA6Uw/oo5rRO9V2NoYcEVlhse4AVZlJGRojgAA3Lm28dG4KJ/iqdaFieo8kd3TF9Wh 3E2RJ9I8tlUqOcl+Tpu74hlzk/80osW5o9O9LRfTgyVIEUjDBJUl161K2hud3d3EwiRs1Hd1LqN OrbzFPxnT3cVvBziuBUqdvd7/x2q3nW1haWKNCaxkhDmypcavP/ckYYAJ0PtIoZ+bB2FsiI5lk6 SFhsCwqs62S18VrxZw37UQ3NTFxxgfdWPffQ9d7xROABmwOdR1yuxez10hH22ILZguSSvTFTKhv fTAt6ILw6Gyd53ZpfqbgGAZ7sQFfed//2ej4hEEEV3f77KxaW7Al2ygShq1BcessK0syF8AXJ4J GXYNH4SG0OB2JHswgaU74/ZJD04g50pvz1tVxtNw53R7x/0VwOq+QWbBkbDWraouN4rsYOG5YmQ 2rM2aqOQFJP141Sv0TQbPGmVZFNf3cobCfLLUG62/uhis+3BimoZ7ulyaA+dGiGcgbj/p4f321q 9SrgY5JCEpaPhw+BRdk/JN/XKpe6fFCdfrHI7K4ED8hszJboqT04bS0dywdroj9nDJgWboiSNFq DV8g+oUhKEE6fXQ== 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" Port the Mali-C55 Awb algorithm to use the new libipa implementation of AwbAlgorithm. The awbAlgo_ class member is initialized with the Q<4, 8> type that represents the register format and the MaliC55AwbStats type handles the WB statistics format as produced by the Mali-C55 which already calculates the mean values as R/G and B/G values. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Reviewed-by: Daniel Scally Tested-by: Daniel Scally --- src/ipa/mali-c55/algorithms/awb.cpp | 223 +++++++++++++++++++++--------------- src/ipa/mali-c55/algorithms/awb.h | 28 ++++- src/ipa/mali-c55/ipa_context.cpp | 3 + src/ipa/mali-c55/ipa_context.h | 11 +- 4 files changed, 157 insertions(+), 108 deletions(-) diff --git a/src/ipa/mali-c55/algorithms/awb.cpp b/src/ipa/mali-c55/algorithms/awb.cpp index 8a671b52be59..353a9f0d3f97 100644 --- a/src/ipa/mali-c55/algorithms/awb.cpp +++ b/src/ipa/mali-c55/algorithms/awb.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2024, Ideas On Board Oy * - * Mali C55 grey world auto white balance algorithm + * Mali C55 auto white balance algorithm */ #include "awb.h" @@ -14,60 +14,91 @@ #include -#include "libipa/fixedpoint.h" - namespace libcamera { namespace ipa::mali_c55::algorithms { LOG_DEFINE_CATEGORY(MaliC55Awb) -/* Number of frames at which we should run AWB at full speed */ -static constexpr uint32_t kNumStartupFrames = 4; +/* \todo Mali-C55 doesn't support the Lux algorithm. */ +static constexpr unsigned int kDefaultLux = 500; -Awb::Awb() +class MaliC55AwbStats final : public AwbStats { +public: + MaliC55AwbStats() = default; + MaliC55AwbStats(const RGB &rgbMeans) + { + /* The Mali-C55 ISP already provides stats as R/G and B/G ratios. */ + + rgbMeans_[0] = rgbMeans.r() * rgbMeans.g(); + rgbMeans_[1] = 1.0; + rgbMeans_[2] = rgbMeans.b() * rgbMeans.g(); + + rg_ = rgbMeans_.r(); + bg_ = rgbMeans_.b(); + } + + double computeColourError(const RGB &gains) const override + { + /* + * Compute the sum of the squared colour error (non-greyness) as + * it appears in the log likelihood equation. + */ + double deltaR = gains.r() * rg_ - 1.0; + double deltaB = gains.b() * bg_ - 1.0; + double delta2 = deltaR * deltaR + deltaB * deltaB; + + return delta2; + } + + RGB rgbMeans() const override + { + return rgbMeans_; + } + + bool valid() const override + { + /* Minimum mean value below which AWB can't operate. */ + constexpr double minValue = 0.2; + + return rgbMeans_.r() > minValue || rgbMeans_.g() > minValue || + rgbMeans_.b() > minValue; + } + +private: + RGB rgbMeans_; + double rg_; + double bg_; +}; + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int Awb::init(IPAContext &context, const ValueNode &tuningData) +{ + return awbAlgo_.init(tuningData, context.ctrlMap); } -int Awb::configure([[maybe_unused]] IPAContext &context, +/** + * \copydoc libcamera::ipa::Algorithm::configure + */ +int Awb::configure(IPAContext &context, [[maybe_unused]] const IPACameraSensorInfo &configInfo) { - /* - * Initially we have no idea what the colour balance will be like, so - * for the first frame we will make no assumptions and leave the R/B - * channels unmodified. - */ - context.activeState.awb.rGain = 1.0f; - context.activeState.awb.bGain = 1.0f; - - return 0; + return awbAlgo_.configure(context.activeState.awb); } -void Awb::fillGainsParamBlock(MaliC55Params *params, IPAContext &context, - IPAFrameContext &frameContext) +/** + * \copydoc libcamera::ipa::Algorithm::queueRequest + */ +void Awb::queueRequest(IPAContext &context, + const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) { - UQ<4, 8> rGain = context.activeState.awb.rGain; - UQ<4, 8> bGain = context.activeState.awb.bGain; - - /* - * The gains here map as follows: - * gain00 = R - * gain01 = Gr - * gain10 = Gb - * gain11 = B - * - * This holds true regardless of the bayer order of the input data, as - * the mapping is done internally in the ISP. - */ - auto block = params->block(); - - block->gain00 = rGain.quantized(); - block->gain01 = UQ<4, 8>(1.0f).quantized(); - block->gain10 = UQ<4, 8>(1.0f).quantized(); - block->gain11 = bGain.quantized(); - - frameContext.awb.rGain = rGain; - frameContext.awb.bGain = bGain; + awbAlgo_.queueRequest(context.activeState.awb, frame, frameContext.awb, + controls); } void Awb::fillConfigParamBlock(MaliC55Params *params) @@ -117,10 +148,33 @@ void Awb::fillConfigParamBlock(MaliC55Params *params) block->cb_low = 64; } +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ void Awb::prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, MaliC55Params *params) { - fillGainsParamBlock(params, context, frameContext); + awbAlgo_.prepare(context.activeState.awb, frameContext.awb); + + /* + * The gains here map as follows: + * gain00 = R + * gain01 = Gr + * gain10 = Gb + * gain11 = B + * + * This holds true regardless of the bayer order of the input data, as + * the mapping is done internally in the ISP. + */ + auto block = params->block(); + block.setEnabled(true); + + block->gain00 = UQ<4, 8>(static_cast(frameContext.awb.gains.r())) + .quantized(); + block->gain01 = UQ<4, 8>(1.0f).quantized(); + block->gain10 = UQ<4, 8>(1.0f).quantized(); + block->gain11 = UQ<4, 8>(static_cast(frameContext.awb.gains.b())) + .quantized(); if (frame > 0) return; @@ -128,45 +182,43 @@ void Awb::prepare(IPAContext &context, const uint32_t frame, fillConfigParamBlock(params); } -void Awb::process(IPAContext &context, const uint32_t frame, - IPAFrameContext &frameContext, const mali_c55_stats_buffer *stats, - [[maybe_unused]] ControlList &metadata) +MaliC55AwbStats Awb::calculateRgbMeans(const IPAFrameContext &frameContext, + const mali_c55_stats_buffer *stats) const { - const struct mali_c55_awb_average_ratios *awb_ratios = stats->awb_ratios; + const struct mali_c55_awb_average_ratios *awb = stats->awb_ratios; /* * The ISP produces average R:G and B:G ratios for zones. We take the - * average of all the zones with data and simply invert them to provide - * gain figures that we can apply to approximate a grey world. + * average of all the zones with data and calculate the mean values. */ - unsigned int counted_zones = 0; - float rgSum = 0, bgSum = 0; + unsigned int active_zones = 0; + double rgSum = 0, bgSum = 0; for (unsigned int i = 0; i < 225; i++) { - if (!awb_ratios[i].num_pixels) + if (!awb[i].num_pixels) continue; /* - * The statistics are in Q4.8 format, so we convert to float + * The statistics are in Q4.8 format, so we convert to double * here. */ - rgSum += UQ<4, 8>(awb_ratios[i].avg_rg_gr).value(); - bgSum += UQ<4, 8>(awb_ratios[i].avg_bg_br).value(); - counted_zones++; + rgSum += UQ<4, 8>(awb[i].avg_rg_gr).value(); + bgSum += UQ<4, 8>(awb[i].avg_bg_br).value(); + active_zones++; } /* * Sometimes the first frame's statistics have no valid pixels, in which * case we'll just assume a grey world until they say otherwise. */ - float rgAvg, bgAvg; - if (!counted_zones) { - rgAvg = 1.0; - bgAvg = 1.0; - } else { - rgAvg = rgSum / counted_zones; - bgAvg = bgSum / counted_zones; - } + if (!active_zones) + return {}; + + RGB rgbMeans = { { + rgSum / active_zones, + 1.0, + bgSum / active_zones, + } }; /* * The statistics are generated _after_ white balancing is performed in @@ -174,41 +226,22 @@ void Awb::process(IPAContext &context, const uint32_t frame, * figure by the gains that were applied when the statistics for this * frame were generated. */ - float rRatio = rgAvg / frameContext.awb.rGain.value(); - float bRatio = bgAvg / frameContext.awb.bGain.value(); + rgbMeans /= frameContext.awb.gains.max(0.01); - /* - * And then we can simply invert the ratio to find the gain we should - * apply. - */ - float rGain = 1 / rRatio; - float bGain = 1 / bRatio; + return MaliC55AwbStats(rgbMeans); +} - /* - * Running at full speed, this algorithm results in oscillations in the - * colour balance. To remove those we dampen the speed at which it makes - * changes in gain, unless we're in the startup phase in which case we - * want to fix the miscolouring as quickly as possible. - */ - float speed = frame < kNumStartupFrames ? 1.0f : 0.2f; - rGain = speed * rGain + context.activeState.awb.rGain.value() * (1.0f - speed); - bGain = speed * bGain + context.activeState.awb.bGain.value() * (1.0f - speed); - - context.activeState.awb.rGain = rGain; - context.activeState.awb.bGain = bGain; - - metadata.set(controls::ColourGains, { - frameContext.awb.rGain.value(), - frameContext.awb.bGain.value(), - }); - - LOG(MaliC55Awb, Debug) << "For frame number " << frame << ": " - << "Average R/G Ratio: " << rgAvg - << ", Average B/G Ratio: " << bgAvg - << "\nrGain applied to this frame: " << frameContext.awb.rGain - << ", bGain applied to this frame: " << frameContext.awb.bGain - << "\nrGain to apply: " << context.activeState.awb.rGain - << ", bGain to apply: " << context.activeState.awb.bGain; +/** + * \copydoc libcamera::ipa::Algorithm::process + */ +void Awb::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, const mali_c55_stats_buffer *stats, + ControlList &metadata) +{ + MaliC55AwbStats awbStats = calculateRgbMeans(frameContext, stats); + + awbAlgo_.process(context.activeState.awb, frameContext.awb, awbStats, + kDefaultLux, metadata); } REGISTER_IPA_ALGORITHM(Awb, "Awb") diff --git a/src/ipa/mali-c55/algorithms/awb.h b/src/ipa/mali-c55/algorithms/awb.h index 683a62af263a..4be7a5917ac5 100644 --- a/src/ipa/mali-c55/algorithms/awb.h +++ b/src/ipa/mali-c55/algorithms/awb.h @@ -2,24 +2,41 @@ /* * Copyright (C) 2024, Ideas on Board Oy * - * Mali C55 grey world auto white balance algorithm + * Mali C55 auto white balance algorithm */ +#pragma once + +#include + +#include + +#include "libcamera/internal/value_node.h" + +#include "libipa/awb.h" +#include "libipa/fixedpoint.h" + #include "algorithm.h" #include "ipa_context.h" +#include "params.h" namespace libcamera { namespace ipa::mali_c55::algorithms { +class MaliC55AwbStats; + class Awb : public Algorithm { public: - Awb(); ~Awb() = default; + int init(IPAContext &context, const ValueNode &tuningData) override; int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override; + void queueRequest(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, MaliC55Params *params) override; @@ -29,10 +46,11 @@ public: ControlList &metadata) override; private: - void fillGainsParamBlock(MaliC55Params *params, - IPAContext &context, - IPAFrameContext &frameContext); void fillConfigParamBlock(MaliC55Params *params); + MaliC55AwbStats calculateRgbMeans(const IPAFrameContext &frameContext, + const mali_c55_stats_buffer *stats) const; + + AwbAlgorithm> awbAlgo_; }; } /* namespace ipa::mali_c55::algorithms */ diff --git a/src/ipa/mali-c55/ipa_context.cpp b/src/ipa/mali-c55/ipa_context.cpp index 1b203e2b2605..74f1bd5323de 100644 --- a/src/ipa/mali-c55/ipa_context.cpp +++ b/src/ipa/mali-c55/ipa_context.cpp @@ -96,6 +96,9 @@ namespace libcamera::ipa::mali_c55 { * * \var IPAContext::frameContexts * \brief Ring buffer of per-frame contexts + * + * \var IPAContext::ctrlMap + * \brief A ControlInfoMap::Map of controls populated by the algorithms */ } /* namespace libcamera::ipa::mali_c55 */ diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h index ac4b83773803..7dc765a3542b 100644 --- a/src/ipa/mali-c55/ipa_context.h +++ b/src/ipa/mali-c55/ipa_context.h @@ -14,6 +14,7 @@ #include +#include "libipa/awb.h" #include "libipa/fixedpoint.h" namespace libcamera { @@ -54,10 +55,7 @@ struct IPAActiveState { uint32_t temperatureK; } agc; - struct { - UQ<4, 8> rGain; - UQ<4, 8> bGain; - } awb; + ipa::awb::ActiveState awb; }; struct IPAFrameContext : public FrameContext { @@ -67,10 +65,7 @@ struct IPAFrameContext : public FrameContext { UQ<5, 8> ispGain; } agc; - struct { - UQ<4, 8> rGain; - UQ<4, 8> bGain; - } awb; + ipa::awb::FrameContext awb; }; struct IPAContext { From patchwork Mon Jul 20 14:59:21 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27395 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 E0EBBC328C for ; Mon, 20 Jul 2026 14:59:49 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A535267E65; Mon, 20 Jul 2026 16:59:48 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="NuipyECX"; 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 A3A8A67E54 for ; Mon, 20 Jul 2026 16:59:42 +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 AE40BD77; Mon, 20 Jul 2026 16:58:43 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559524; bh=zXUtBigMiw6eO+vt+/Fv+syxd0sdkfiYn9N4A5x3bFU=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=NuipyECXCd+u7lN0WUFC2Wla4hLnboKCWJdTTYRF0i0Spgy5poWq3AjoHKYZw2klY oa0ItaZMs24MxBsGgIqnU+H3TtncAHv5YRqWRdjT5X46oJFoGty0ShU5Vj2cmo+ooW FZMkyrhuJhptJ/tPg6BJ0H+ThlPMoGqGUxg/W43c= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:21 +0200 Subject: [PATCH v6 03/31] ipa: simple: awb: Port to use libipa AwbAlgorithm MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-3-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 , Kieran Bingham , Robert Mader X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=13654; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=y7LE77rh0+hi/oHub7+mkgErDdvlHu3tWSXsZtrgSRQ=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfS4sPjsps7BC5PGXyc3Cmk5BtK3y1K1Z5r0 Uch80gvbCqJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal430gAKCRByNAaPFqFW PDVKEACACyMHfEwD9uDkevC90fb747lX9Xz4iNpe4n/pVc8WxbS4BEj0CvitBxvAvgktAe9bvjB 0g7DAh/DTQBRzwOrDPTlJOSINZGYyzUwU+W1scFyPF0CRriKRQKHbzOVxU6jE2gJF9hOpn/PIS+ 9P9g21xjFmk8vO+lOA73XZj7XW5XrI8EHDYZH6bg6KWBCr8sqnOHzmKKTolK1Xj7OgyPD0e6qdN i9+bLpdU6/+Oc/ANE+UqvHcm6IR+/66pNP975PR00nt2XTdmAzCml7YW/b1jJVCg/2k8TZvxTP5 yYgcLB2FN392gaV4ZNYg+NWxBOym2dl7kOsJ659uKQUwjuSr4WMAg9KS7y1hp/JYsiJ57+OKBUQ XDUOJziHK5qOxv9bZhoX5oKI2cjVl831t3bjSHTzq+Qf08YoGWhqel7+wXMBUHRqbm5LJiHL9cP V19kDkE3/Zmedltn5rv6xXl7tL9us44Oz+Lj0ol3op8qI9c+PtVjKTWKEO70y81BKlU6Fui0mNb tXHct60uPQE+/60mNzgu01Nv2egEJOalyhTmvGQVdImXcMbxmolB8N4aRDyBbeO4duBZaALTFDo v4nsjv23aZltuyuEMm1hjGEvhVfy5KUcShT6aX42BH6dCZSD8Imt84wALq7v5q18Wzg/m0bqR/M /EQxTtqSS4ngbAg== 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" From: Kieran Bingham Port the SoftISP Awb algorithm to use the new libipa implementation of AwbAlgorithm. The awbAlgo_ class member is initialized with the Q<2, 8> type even if there is no physical register representation for SoftISP. The usage of libipa::awb::Context, which defines the gains vector as RGB, in the SimpleIPA ActiveState and FrameContext requires changes to debayer_cpu.cpp and blc.cpp in order not to mix RGB and RGB. Signed-off-by: Kieran Bingham Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Tested-by: Robert Mader Tested-by: Milan Zamazal --- v5->v6: - Remove [[maybe_unused]] from header - cast sum.r()/b()/g() to double before averaging - remove unused include for colors.h - remove \todo about means --- .../internal/software_isp/debayer_params.h | 4 +- src/ipa/simple/algorithms/awb.cpp | 140 +++++++++++++++------ src/ipa/simple/algorithms/awb.h | 28 +++++ src/ipa/simple/algorithms/blc.cpp | 2 +- src/ipa/simple/algorithms/ccm.cpp | 2 +- src/ipa/simple/ipa_context.h | 12 +- src/libcamera/software_isp/debayer_cpu.cpp | 12 +- 7 files changed, 145 insertions(+), 55 deletions(-) diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h index 6772b43bced4..1074720d73c2 100644 --- a/include/libcamera/internal/software_isp/debayer_params.h +++ b/include/libcamera/internal/software_isp/debayer_params.h @@ -21,10 +21,10 @@ struct DebayerParams { Matrix combinedMatrix = { { 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 } }; - RGB blackLevel = RGB({ 0.0, 0.0, 0.0 }); + RGB blackLevel = RGB({ 0.0, 0.0, 0.0 }); float gamma = 1.0; float contrastExp = 1.0; - RGB gains = RGB({ 1.0, 1.0, 1.0 }); + RGB gains = RGB({ 1.0, 1.0, 1.0 }); }; } /* namespace libcamera */ diff --git a/src/ipa/simple/algorithms/awb.cpp b/src/ipa/simple/algorithms/awb.cpp index 05155c83d172..0bd6b66ce859 100644 --- a/src/ipa/simple/algorithms/awb.cpp +++ b/src/ipa/simple/algorithms/awb.cpp @@ -14,50 +14,113 @@ #include -#include "libipa/colours.h" -#include "simple/ipa_context.h" - namespace libcamera { LOG_DEFINE_CATEGORY(IPASoftAwb) namespace ipa::soft::algorithms { +/* + * \todo Replace it with a proper Lux algorithm + */ +static constexpr unsigned int kDefaultLux = 500; + +class SimpleAwbStats final : public AwbStats +{ +public: + SimpleAwbStats() = default; + + SimpleAwbStats(const RGB &rgbMeans) + { + rgbMeans_ = rgbMeans; + + rg_ = rgbMeans_.r() / rgbMeans_.g(); + bg_ = rgbMeans_.b() / rgbMeans_.g(); + } + + double computeColourError(const RGB &gains) const override + { + /* + * Compute the sum of the squared colour error (non-greyness) as + * it appears in the log likelihood equation. + */ + double deltaR = gains.r() * rg_ - 1.0; + double deltaB = gains.b() * bg_ - 1.0; + double delta2 = deltaR * deltaR + deltaB * deltaB; + + return delta2; + } + + RGB rgbMeans() const override + { + return rgbMeans_; + } + + bool valid() const override + { + /* Minimum mean value below which AWB can't operate. */ + constexpr double minValue = 0.2; + + return rgbMeans_.r() > minValue || rgbMeans_.g() > minValue || + rgbMeans_.b() > minValue; + } + +private: + RGB rgbMeans_; + double rg_; + double bg_; +}; + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int Awb::init(IPAContext &context, const ValueNode &tuningData) +{ + return awbAlgo_.init(tuningData, context.ctrlMap); +} + +/** + * \copydoc libcamera::ipa::Algorithm::configure + */ int Awb::configure(IPAContext &context, [[maybe_unused]] const IPAConfigInfo &configInfo) { - auto &gains = context.activeState.awb.gains; - gains = { { 1.0, 1.0, 1.0 } }; + return awbAlgo_.configure(context.activeState.awb); +} - return 0; +/** + * \copydoc libcamera::ipa::Algorithm::queueRequest + */ +void Awb::queueRequest(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) +{ + awbAlgo_.queueRequest(context.activeState.awb, frame, frameContext.awb, + controls); } +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ void Awb::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, IPAFrameContext &frameContext, DebayerParams *params) { - auto &gains = context.activeState.awb.gains; + awbAlgo_.prepare(context.activeState.awb, frameContext.awb); - frameContext.gains = gains; - params->gains = gains; + params->gains = frameContext.awb.gains; } -void Awb::process(IPAContext &context, - [[maybe_unused]] const uint32_t frame, - IPAFrameContext &frameContext, - const SwIspStats *stats, - ControlList &metadata) +SimpleAwbStats Awb::calculateRgbMeans(IPAContext &context, + const SwIspStats *stats) const { + if (!stats->valid) + return {}; + const SwIspStats::Histogram &histogram = stats->yHistogram; const uint8_t blackLevel = context.activeState.blc.level; - metadata.set(controls::ColourGains, { frameContext.gains.r(), - frameContext.gains.b() }); - - if (!stats->valid) - return; - /* * Black level must be subtracted to get the correct AWB ratios, they * would be off if they were computed from the whole brightness range @@ -67,30 +130,31 @@ void Awb::process(IPAContext &context, histogram.begin(), histogram.end(), uint64_t(0)); const uint64_t offset = blackLevel * nPixels; const uint64_t minValid = 1; + /* * Make sure the sums are at least minValid, while preventing unsigned * integer underflow. */ const RGB sum = stats->sum_.max(offset + minValid) - offset; - /* - * Calculate red and blue gains for AWB. - * Clamp max gain at 4.0, this also avoids 0 division. - */ - auto &gains = context.activeState.awb.gains; - gains = { { - sum.r() <= sum.g() / 4 ? 4.0f : static_cast(sum.g()) / sum.r(), - 1.0, - sum.b() <= sum.g() / 4 ? 4.0f : static_cast(sum.g()) / sum.b(), - } }; - - RGB rgbGains{ { 1 / gains.r(), 1 / gains.g(), 1 / gains.b() } }; - context.activeState.awb.temperatureK = estimateCCT(rgbGains); - metadata.set(controls::ColourTemperature, context.activeState.awb.temperatureK); - - LOG(IPASoftAwb, Debug) - << "gain R/B: " << gains << "; temperature: " - << context.activeState.awb.temperatureK; + RGB rgbMeans = { { static_cast(sum.r()) / nPixels, + static_cast(sum.g()) / nPixels, + static_cast(sum.b()) / nPixels } }; + + return SimpleAwbStats(rgbMeans); +} + +/** + * \copydoc libcamera::ipa::Algorithm::process + */ +void Awb::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, const SwIspStats *stats, + ControlList &metadata) +{ + SimpleAwbStats awbStats = calculateRgbMeans(context, stats); + + awbAlgo_.process(context.activeState.awb, frameContext.awb, awbStats, + kDefaultLux, metadata); } REGISTER_IPA_ALGORITHM(Awb, "Awb") diff --git a/src/ipa/simple/algorithms/awb.h b/src/ipa/simple/algorithms/awb.h index ad993f39c180..ff0a7a28e8f2 100644 --- a/src/ipa/simple/algorithms/awb.h +++ b/src/ipa/simple/algorithms/awb.h @@ -7,19 +7,36 @@ #pragma once +#include + +#include "libcamera/internal/software_isp/debayer_params.h" +#include "libcamera/internal/value_node.h" + +#include "libipa/awb.h" +#include "libipa/fixedpoint.h" +#include "simple/ipa_context.h" + #include "algorithm.h" namespace libcamera { namespace ipa::soft::algorithms { +class SimpleAwbStats; + class Awb : public Algorithm { public: Awb() = default; ~Awb() = default; + int init(IPAContext &context, + const ValueNode &tuningData) override; int configure(IPAContext &context, const IPAConfigInfo &configInfo) override; + + void queueRequest(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, @@ -29,6 +46,17 @@ public: IPAFrameContext &frameContext, const SwIspStats *stats, ControlList &metadata) override; + +private: + SimpleAwbStats calculateRgbMeans(IPAContext &context, + const SwIspStats *stats) const; + + /* + * There actually is no Q register format for SoftISP, but allow the + * colour gains to range in the [0.0f, 3.999f] interval, which seems + * reasonable. + */ + AwbAlgorithm> awbAlgo_; }; } /* namespace ipa::soft::algorithms */ diff --git a/src/ipa/simple/algorithms/blc.cpp b/src/ipa/simple/algorithms/blc.cpp index 677be56ed669..e45a913cd970 100644 --- a/src/ipa/simple/algorithms/blc.cpp +++ b/src/ipa/simple/algorithms/blc.cpp @@ -53,7 +53,7 @@ void BlackLevel::prepare(IPAContext &context, DebayerParams *params) { /* Latch the blacklevel gain so GPUISP can apply. */ - params->blackLevel = RGB(context.activeState.blc.level / 255.0f); + params->blackLevel = RGB(context.activeState.blc.level / 255.0f); } void BlackLevel::process(IPAContext &context, diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp index ace9c35dc462..1174784edc7e 100644 --- a/src/ipa/simple/algorithms/ccm.cpp +++ b/src/ipa/simple/algorithms/ccm.cpp @@ -44,7 +44,7 @@ int Ccm::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData) void Ccm::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, IPAFrameContext &frameContext, [[maybe_unused]] DebayerParams *params) { - const unsigned int ct = context.activeState.awb.temperatureK; + const unsigned int ct = frameContext.awb.colourTemperature; /* Change CCM only on bigger temperature changes. */ if (!currentCcm_ || diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h index 8ccfacb46a59..29643a655ce1 100644 --- a/src/ipa/simple/ipa_context.h +++ b/src/ipa/simple/ipa_context.h @@ -16,6 +16,7 @@ #include "libcamera/internal/matrix.h" #include "libcamera/internal/vector.h" +#include #include #include "core_ipa_interface.h" @@ -36,6 +37,8 @@ struct IPASessionConfiguration { }; struct IPAActiveState { + ipa::awb::ActiveState awb; + struct { int32_t exposure; double again; @@ -48,11 +51,6 @@ struct IPAActiveState { double lastGain; } blc; - struct { - RGB gains; - unsigned int temperatureK; - } awb; - Matrix combinedMatrix; struct { @@ -64,6 +62,8 @@ struct IPAActiveState { }; struct IPAFrameContext : public FrameContext { + ipa::awb::FrameContext awb; + Matrix ccm; struct { @@ -71,8 +71,6 @@ struct IPAFrameContext : public FrameContext { double gain; } sensor; - RGB gains; - float gamma; std::optional contrast; std::optional saturation; diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp index 49382b4c2719..ab22635fdfaf 100644 --- a/src/libcamera/software_isp/debayer_cpu.cpp +++ b/src/libcamera/software_isp/debayer_cpu.cpp @@ -1010,9 +1010,9 @@ void DebayerCpu::updateLookupTables(const DebayerParams ¶ms) }; const unsigned int gammaTableSize = gammaTable_.size(); - const RGB blackIndex = params.blackLevel * kRGBLookupSize; - const RGB gains = params.gains; - const RGB div = (RGB(kRGBLookupSize) - blackIndex).max(1.0); + const RGB blackIndex = params.blackLevel * kRGBLookupSize; + const RGB gains = params.gains; + const RGB div = (RGB(kRGBLookupSize) - blackIndex).max(1.0); if (ccmEnabled_) { if (gammaUpdateNeeded || @@ -1025,7 +1025,7 @@ void DebayerCpu::updateLookupTables(const DebayerParams ¶ms) const unsigned int greenIndex = 1; const unsigned int blueIndex = swapRedBlueGains_ ? 0 : 2; for (unsigned int i = 0; i < kRGBLookupSize; i++) { - const RGB rgb = (gains * (RGB(i) - blackIndex) * kRGBLookupSize / div) + const RGB rgb = (gains * (RGB(i) - blackIndex) * kRGBLookupSize / div) .clamp(0.0, kRGBLookupSize - 1); red[i].r = std::round(rgb.r() * params.combinedMatrix[redIndex][0]); red[i].g = std::round(rgb.r() * params.combinedMatrix[greenIndex][0]); @@ -1045,8 +1045,8 @@ void DebayerCpu::updateLookupTables(const DebayerParams ¶ms) auto &green = green_; auto &blue = swapRedBlueGains_ ? red_ : blue_; for (unsigned int i = 0; i < kRGBLookupSize; i++) { - const RGB lutGains = - (gains * (RGB(i) - blackIndex) * gammaTableSize / div) + const RGB lutGains = + (gains * (RGB(i) - blackIndex) * gammaTableSize / div) .clamp(0.0, gammaTableSize - 1); red[i] = gammaTable_[lutGains.r()]; green[i] = gammaTable_[lutGains.g()]; From patchwork Mon Jul 20 14:59:22 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27396 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 C65A8C32FE for ; Mon, 20 Jul 2026 14:59:51 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D7D9C67E6F; Mon, 20 Jul 2026 16:59:49 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="AHKckXQ7"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 5352367E44 for ; Mon, 20 Jul 2026 16:59:43 +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 5191C13D9; Mon, 20 Jul 2026 16:58:44 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559524; bh=f9+XfBVeEJ4VSD1bfCxQGr3LUyOFZa7nG5xmDQhXjKc=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=AHKckXQ7uOpsBm2hjNMEDQiNAKMpDjS4LjglfuvmZUJ5f9oogfHp37kshi4Og5M/h qEP6pdD+RTAtELVpklBZX+e7mol0fFK6zBWMivT1wk2mxSNBTa5yREgVvyPSlpkePT hWhpc1ACGF3320DTdUuItJXrl+Bq0T7fjzdToKqE= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:22 +0200 Subject: [PATCH v6 04/31] ipa: libipa: Add CcmAlgorithm to libipa MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-4-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 , Kieran Bingham , Daniel Scally , Robert Mader X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=12018; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=f9+XfBVeEJ4VSD1bfCxQGr3LUyOFZa7nG5xmDQhXjKc=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfSHSGC5BBEAs/v5bQg2e4+wU7sg33hvZ21k Wc8/U0IhwyJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal430gAKCRByNAaPFqFW PM1RD/9ymb8UmaPL8EE8EHr3h/Sj+UHr2lVavcOZZj4whEjN88r9Eu6J3jYGY+HMiiUACMTxEvw otwXM+pj8LpyBAm7u59PR7fR/1GkJzB58cCPlbHU27r8eaY18h7Kg3LlL2WnFzqQPpg2BnM7qv0 LN221tBdHMSkPilZe9Sf8Il8pdPfHWfKzMSNuFOOEmNLZ7ZOz5aCEvsRliah/Ai3v50n60n223l k4JXKYrFuHrZi7FA68YD2EjzmbdZNGxhJu3TjYBU6P9JlQmyzTARpw8XvRaWznCYUbrgzJDzPDp uWTVemQEb/qnLYs1hpd1mlJb0DlJ9npETok+eMiz+MmAMG+i37tOTM513+WuKntI3XMWCxE1sUh wNhM6/iX369dnVl3FxWVJZZvpP1YHtjPAYTazSF5Pz15aTcuwGp09TYX6f2N4VNphdRdKI0egHU w+kjsdR3NgJFDaps6oN40L38jZ/Jkf/fJS4MkyZii011jH3wZvATWwUWEBvgxbS6elTMIjARSNo aNXawmnWiCtPC/bjE1BVuxM7QLeIYRsPvcOaqDB/Cy8fyeNEdLnmgNPeZSiK8hwWn1YO8S5FJQM b4B/Yz5eKcEpe5UcYHXb6smGt1RTR3myNBYOw2h6DI7n4FIVfp6y/O6xtIalA3aNnenBWRZtZHO BsAt/v6Wb53sRgQ== 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" Add a CcmAlgorithm to libipa. The CcmAlgorithm performs interpolation of the colour correction matrices as loaded from tuning file on a colour temperature. The implementation is based on the existing RkISP1 CCM algorihtm. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Reviewed-by: Daniel Scally Tested-by: Robert Mader --- src/ipa/libipa/ccm.cpp | 245 +++++++++++++++++++++++++++++++++++++++++++++ src/ipa/libipa/ccm.h | 77 ++++++++++++++ src/ipa/libipa/meson.build | 2 + 3 files changed, 324 insertions(+) diff --git a/src/ipa/libipa/ccm.cpp b/src/ipa/libipa/ccm.cpp new file mode 100644 index 000000000000..8f3652f7e32b --- /dev/null +++ b/src/ipa/libipa/ccm.cpp @@ -0,0 +1,245 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + * + * libIPA CCM algorithm + */ + +#include "ccm.h" + +/** + * \file ccm.h + * \brief libipa CCM (Colour Correction Matrix) algorithm + */ + +namespace libcamera { + +namespace ipa { + +LOG_DEFINE_CATEGORY(Ccm) + +namespace ccm { + +/** + * \struct ActiveState + * \brief Active CCM state + * + * \var ActiveState::manual + * \brief The most recent manually requested CCM state + * + * \var ActiveState::automatic + * \brief The most recent automatically calculated CCM state + */ + +/** + * \struct CcmContext + * \brief CCM coefficients and offsets + * + * \var CcmContext::ccm + * \brief Matrix of 3x3 CCM coefficients + * + * \var CcmContext::offsets + * \brief Vector of RGB CCM offsets + */ + +/** + * \typedef FrameContext + * \brief Per-frame CCM state + */ + +} /* namespace ccm */ + +/** + * \class CcmAlgorithmBase + * \brief Base class for CcmAlgorithm for non-templated functions implementation + * + * Base class for CcmAlgorithm where non-templated functions are implemented. + * IPA implementations shall use CcmAlgorithm and not this class. + */ + +/** + * \brief Initialize the algorithm with the given tuning data + * \param[in] tuningData The tuning data to use for the algorithm + * + * Parse \a tuningData to initialize the CCM algorithm and register controls. + * IPA modules are expected to call this function as part of their + * implementation of Algorithm::init(). + * + * \return 0 on success, a negative error code otherwise + */ +int CcmAlgorithmBase::init(const ValueNode &tuningData) +{ + int ret = ccm_.readYaml(tuningData["ccms"], "ct", "ccm"); + if (ret < 0) { + LOG(Ccm, Warning) + << "Failed to parse 'ccm' " + << "parameter from tuning file; falling back to unit matrix"; + ccm_.setData({ { 0, Matrix::identity() } }); + } + + ret = offsets_.readYaml(tuningData["ccms"], "ct", "offsets"); + if (ret < 0) { + LOG(Ccm, Warning) + << "Failed to parse 'offsets' " + << "parameter from tuning file; falling back to zero offsets"; + + offsets_.setData({ { 0, Matrix({ 0, 0, 0 }) } }); + } + + return 0; +} + +/** + * \brief Configure the CCM algorithm + * \param[in] state The CCM active state + * \param[in] temperatureK The colour temperature in Kelvin + * + * Configure the CCM algorithm by initializing the manual and automatic + * states in \a state by interpolating the default colour correction matrix + * with the given colour temperature \a temperatureK. + * + * \return 0 if successful, an error code otherwise + */ +int CcmAlgorithmBase::configure(ccm::ActiveState &state, unsigned int temperatureK) +{ + state.manual.ccm = ccm_.getInterpolated(temperatureK); + state.manual.offsets = offsets_.getInterpolated(temperatureK); + state.automatic.ccm = ccm_.getInterpolated(temperatureK); + state.automatic.offsets = offsets_.getInterpolated(temperatureK); + + return 0; +} + +/** + * \brief Queue a Request to the CCM algorithm + * \param[in] state The CCM active state + * \param[in] context The CCM frame context + * \param[in] controls The list of controls associated with the Request + * + * Queue a new Request to the CCM algorithm and store the manual colour + * correction matrix and temperature in \a frameContext. + * + * The currently handled controls are: + * - controls::ColourTemperature + * - controls::ColourCorrectionMatrix + * + * When controls::ColourCorrectionMatrix is passed in the supplied matrix is + * stored in \a state and \a context. + * + * When controls::ColourTemperature is passed in, the matrices loaded from + * configuration file are interpolated with the given temperature and the result + * is stored in \a state and \a context. + * + * If the IPA is running in manual mode, the IPA CCM algorithm implementations + * can use the matrix coefficients and offsets directly from \a context after + * calling this function to program the HW CCM engine, without calling prepare(). + */ +void CcmAlgorithmBase::queueRequest(ccm::ActiveState &state, + ccm::FrameContext &context, + const ControlList &controls) +{ + const auto &colourTemperature = controls.get(controls::ColourTemperature); + const auto &ccmMatrix = controls.get(controls::ColourCorrectionMatrix); + if (ccmMatrix) { + state.manual.ccm = Matrix(*ccmMatrix); + LOG(Ccm, Debug) << "Setting manual CCM from CCM control to " + << state.manual.ccm; + } else if (colourTemperature) { + state.manual.ccm = ccm_.getInterpolated(*colourTemperature); + LOG(Ccm, Debug) << "Setting manual CCM from CT control to " + << state.manual.ccm; + } + + context = state.manual; +} + +/** + * \brief Calculate the matrix coefficients for a colour temperature + * \param[in] state The CCM active state + * \param[in] context The CCM frame context + * \param[in] frame The frame number + * \param[in] temperatureK The colour temperature in Kelvin + * + * Interpolate the colour correction matrices as loaded from configuration file + * for colour temperature \a temperatureK. + * + * The function shall only be called if the IPA algorithm is running in auto + * mode. If running in manual mode the application supplied correction matrix is + * stored in \a frameContext at queueRequest() time. + */ +void CcmAlgorithmBase::prepare(ccm::ActiveState &state, + ccm::FrameContext &context, + unsigned int frame, unsigned int temperatureK) +{ + if (frame > 0 && temperatureK == ct_) { + context = state.automatic; + return; + } + + ct_ = temperatureK; + context.ccm = ccm_.getInterpolated(ct_); + context.offsets = offsets_.getInterpolated(ct_); + + state.automatic = context; +} + +/** + * \brief Populate metadata with the latest correction matrix coefficients + * \param[in] context The CCM frame context + * \param[out] metadata The metadata list + */ +void CcmAlgorithmBase::process(ccm::FrameContext &context, ControlList &metadata) +{ + metadata.set(controls::ColourCorrectionMatrix, context.ccm.data()); +} + +/** + * \class CcmAlgorithm + * \brief The libipa CCM algorithm + * \tparam Q The fixedpoint register representation of the colour correction + * coefficients + * + * Implement the CCM algorithm for libipa. + * + * The CcmAlgorithm class implements an interface similar in spirit to the one + * of the Algorithm class. IPA modules are expected to store an instance of + * CcmAlgorithm as class member, template it with the CCM coefficients register + * representation and call its functions in their implementations of the + * Algorithm interface. + * + * The CcmAlgorithm class provides an init() function where tuning data is + * parsed and the per-colour temperature correction matrices are loaded from + * the tuning file. + * + * CcmAlgorithm supports both automatic and manual colour correction operations, + * but doesn't offer a way to select one of them. Enabling or disabling + * automatic CCM operations usually goes through the Awb algorithm + * enable/disable as the two algorithms should work with the same mode. + * + * When the IPA module runs in manual mode a custom colour correction matrix + * or a custom colour temperature can be supplied to the CCM algorithm at + * queueRequest() time. If the Request contains a color correction matrix + * (controls::ColourCorrectionMatrix) then the matrix coefficients gets saved in + * the FrameContext and the IPA module can immediately use them and doesn't need + * to call prepare(). If a custom colour temperature is provided + * (controls::ColourTemperature) then the matrices loaded from configuration are + * interpolated with it and the result is saved in the FrameContext. In this + * case as well IPA modules can use the result immediately and should avoid + * calling prepare(). + * + * When the IPA module runs in automatic mode instead, it estimates the scene + * colour temperature. The estimated colour temperature shall be passed to + * prepare(), where it is used to interpolate the matrices loaded from the + * tuning file. The resulting coefficients are stored in the FrameContext for + * the IPA algorithm to use them to program their CCM engine registers. + */ + +/** + * \fn CcmAlgorithm::init() + * \param[in] controls The info map of the IPA controls + * \copydoc CcmAlgorithmBase::init() + */ + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/ccm.h b/src/ipa/libipa/ccm.h new file mode 100644 index 000000000000..b72b25e2a3b3 --- /dev/null +++ b/src/ipa/libipa/ccm.h @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + * + * libIPA CCM algorithm + */ + +#pragma once + +#include +#include + +#include "libcamera/internal/matrix.h" + +#include "fixedpoint.h" +#include "interpolator.h" + +namespace libcamera { + +namespace ipa { + +namespace ccm { + +struct CcmContext { + Matrix ccm; + Matrix offsets; +}; + +struct ActiveState { + struct CcmContext manual; + struct CcmContext automatic; +}; + +using FrameContext = CcmContext; + +} /* namespace ccm */ + +class CcmAlgorithmBase +{ +public: + int init(const ValueNode &tuningData); + int configure(ccm::ActiveState &state, unsigned int temperatureK); + void queueRequest(ccm::ActiveState &state, ccm::FrameContext &context, + const ControlList &controls); + + void prepare(ccm::ActiveState &state, ccm::FrameContext &context, + unsigned int frame, unsigned int temperatureK); + void process(ccm::FrameContext &context, ControlList &metadata); + +private: + unsigned int ct_; + Interpolator> ccm_; + Interpolator> offsets_; +}; + +template +class CcmAlgorithm : public CcmAlgorithmBase +{ +public: + int init(const ValueNode &tuningData, ControlInfoMap::Map &controls) + { + int ret = CcmAlgorithmBase::init(tuningData); + if (ret) + return ret; + + controls[&controls::ColourCorrectionMatrix] = + ControlInfo(ControlValue(Q::TraitsType::min), + ControlValue(Q::TraitsType::max), + ControlValue(Matrix::identity().data())); + + return 0; + } +}; + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build index 963c5ee73063..edf8eabd8b78 100644 --- a/src/ipa/libipa/meson.build +++ b/src/ipa/libipa/meson.build @@ -7,6 +7,7 @@ libipa_headers = files([ 'awb_grey.h', 'awb.h', 'camera_sensor_helper.h', + 'ccm.h', 'colours.h', 'exposure_mode_helper.h', 'fc_queue.h', @@ -28,6 +29,7 @@ libipa_sources = files([ 'awb_grey.cpp', 'awb.cpp', 'camera_sensor_helper.cpp', + 'ccm.cpp', 'colours.cpp', 'exposure_mode_helper.cpp', 'fc_queue.cpp', From patchwork Mon Jul 20 14:59:23 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27397 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 21228C328C for ; Mon, 20 Jul 2026 14:59:53 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9915067E56; Mon, 20 Jul 2026 16:59:51 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="l9LYNOod"; 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 E66E467E4C for ; Mon, 20 Jul 2026 16:59:43 +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 F0344270; Mon, 20 Jul 2026 16:58:44 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559525; bh=ltIYhnnM1bUd815aMZ307m3Gl3PwVM2zbZHeIKk9o7s=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=l9LYNOodbFyL/mN2f6rnIy5+R3SBBMSRoYbNk23KN/nu6v1FJxu2Gf94We6IdLJj6 q4Yz5JRThHtDMn9KiJ4gU6ezJCET782UcD8LZLjTQ0U9KvjdrAeeYX/ybarGH18twF 4Li2/r7+Y/4snRAucZG8rF/KDmkzFshnmjBgSdaY= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:23 +0200 Subject: [PATCH v6 05/31] ipa: rkisp1: ccm: Port to use CcmAlgorithm MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-5-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 , Kieran Bingham , Daniel Scally X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=8073; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=ltIYhnnM1bUd815aMZ307m3Gl3PwVM2zbZHeIKk9o7s=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfSVPaEtwEVPcSeLkr0ppaTfZt+nmbEse/fr fprt/ZxAF6JAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal430gAKCRByNAaPFqFW POXaD/0XrGyeXw28yPnQdtdAIxdqbqyn9+huwMUDaIAx5j37k3rUBzpJ3IQi5joUoOE0I2GeJNr XCk8UzGRNeaQ9jkC0jziRIc2m/LrVt03nWT4Y1Ee3faA50MpsSlrO2h/In3ukefbMk8xJHk9aAt xPvV8d4g3jgmF9v6LpW1QSr84xug1TtJJnHWNGaYZwlcmR6syjX3SqCFs2GgTAzs/RosyBL7IIF l55TEVwl09KjeV/AlpaP/u+pFDjmRUV7ppjHFZvYFkMkQSARqM79G5zAJSGkCtayl9LKBCSkwz8 9yt/W4LAubaTjBMuTTLq+8EQu5FBcZ3saf+3S1pXEu/S7PUMHMUOX1SAWfY8v2BBpZSUMVKoQvc kyvTk5zDfagt6wdNIH2y4jQrykKOc6dg1lCVQlaFtpJB6v9KgRqfci6DQMnLNU1A0LfJxb/9dge 2pbM+kc6/rSL7EVMDyL3iSf9xaZzAckhoZgQnmTxt1N25EiyvLCRQt9YrEOzkrv3EqNjrxXmFgL lava5FKTB0d8vZchfDcsNtn+ug9NiGleUFhjisGvs0fw+RmPa5yKUf7kWQYxQEhNkQxyyLf8FNb oiy0FmLrEDJOfLk304CYgp+qsU1Auwb62BC9HvvOgMp0lzw++ngL6c8Z5PdFftu4JSA3N1BR5sW ZOIIueV1JyYBXrA== 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" Port the Ccm RkISP1 implementation to use the libipa CcmAlgorithm class. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Reviewed-by: Daniel Scally --- src/ipa/rkisp1/algorithms/ccm.cpp | 97 ++++++++------------------------------- src/ipa/rkisp1/algorithms/ccm.h | 17 +++---- src/ipa/rkisp1/ipa_context.h | 10 ++-- 3 files changed, 32 insertions(+), 92 deletions(-) diff --git a/src/ipa/rkisp1/algorithms/ccm.cpp b/src/ipa/rkisp1/algorithms/ccm.cpp index 633dc2ddb834..0e54c987104d 100644 --- a/src/ipa/rkisp1/algorithms/ccm.cpp +++ b/src/ipa/rkisp1/algorithms/ccm.cpp @@ -16,13 +16,11 @@ #include -#include "libcamera/internal/value_node.h" - -#include "libipa/fixedpoint.h" #include "libipa/interpolator.h" /** * \file ccm.h + * \brief RkISP1 CCM algorithm implementation */ namespace libcamera { @@ -31,42 +29,17 @@ namespace ipa::rkisp1::algorithms { /** * \class Ccm - * \brief A color correction matrix algorithm + * \brief RkISP1 color correction matrix algorithm */ LOG_DEFINE_CATEGORY(RkISP1Ccm) -constexpr Matrix kIdentity3x3 = Matrix::identity(); - /** * \copydoc libcamera::ipa::Algorithm::init */ int Ccm::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData) { - auto &cmap = context.ctrlMap; - cmap[&controls::ColourCorrectionMatrix] = ControlInfo( - ControlValue(-8.0f), - ControlValue(7.993f), - ControlValue(kIdentity3x3.data())); - - int ret = ccm_.readYaml(tuningData["ccms"], "ct", "ccm"); - if (ret < 0) { - LOG(RkISP1Ccm, Warning) - << "Failed to parse 'ccm' " - << "parameter from tuning file; falling back to unit matrix"; - ccm_.setData({ { 0, kIdentity3x3 } }); - } - - ret = offsets_.readYaml(tuningData["ccms"], "ct", "offsets"); - if (ret < 0) { - LOG(RkISP1Ccm, Warning) - << "Failed to parse 'offsets' " - << "parameter from tuning file; falling back to zero offsets"; - - offsets_.setData({ { 0, Matrix({ 0, 0, 0 }) } }); - } - - return 0; + return ccmAlgo_.init(tuningData, context.ctrlMap); } /** @@ -75,10 +48,8 @@ int Ccm::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData) int Ccm::configure(IPAContext &context, [[maybe_unused]] const IPACameraSensorInfo &configInfo) { - auto &as = context.activeState; - as.ccm.manual = kIdentity3x3; - as.ccm.automatic = ccm_.getInterpolated(as.awb.automatic.colourTemperature); - return 0; + return ccmAlgo_.configure(context.activeState.ccm, + context.activeState.awb.automatic.colourTemperature); } void Ccm::queueRequest(IPAContext &context, @@ -90,38 +61,28 @@ void Ccm::queueRequest(IPAContext &context, if (frameContext.awb.autoEnabled) return; - auto &ccm = context.activeState.ccm; - - const auto &colourTemperature = controls.get(controls::ColourTemperature); - const auto &ccmMatrix = controls.get(controls::ColourCorrectionMatrix); - if (ccmMatrix) { - ccm.manual = Matrix(*ccmMatrix); - LOG(RkISP1Ccm, Debug) - << "Setting manual CCM from CCM control to " << ccm.manual; - } else if (colourTemperature) { - ccm.manual = ccm_.getInterpolated(*colourTemperature); - LOG(RkISP1Ccm, Debug) - << "Setting manual CCM from CT control to " << ccm.manual; - } - - frameContext.ccm.ccm = ccm.manual; + ccmAlgo_.queueRequest(context.activeState.ccm, frameContext.ccm, controls); } -void Ccm::setParameters(struct rkisp1_cif_isp_ctk_config &config, - const Matrix &matrix, - const Matrix &offsets) +void Ccm::setParameters(RkISP1Params *params, IPAFrameContext &context) { + const Matrix &matrix = context.ccm.ccm; + const Matrix &offsets = context.ccm.offsets; + + auto config = params->block(); + config.setEnabled(true); + /* * 4 bit integer and 7 bit fractional, ranging from -8 (0x400) to * +7.9921875 (0x3ff) */ for (unsigned int i = 0; i < 3; i++) { for (unsigned int j = 0; j < 3; j++) - config.coeff[i][j] = Q<4, 7>(matrix[i][j]).quantized(); + config->coeff[i][j] = Q<4, 7>(matrix[i][j]).quantized(); } for (unsigned int i = 0; i < 3; i++) - config.ct_offset[i] = offsets[i][0] & 0xfff; + config->ct_offset[i] = offsets[i][0] & 0xfff; LOG(RkISP1Ccm, Debug) << "Setting matrix " << matrix; LOG(RkISP1Ccm, Debug) << "Setting offsets " << offsets; @@ -133,29 +94,11 @@ void Ccm::setParameters(struct rkisp1_cif_isp_ctk_config &config, void Ccm::prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, RkISP1Params *params) { - if (!frameContext.awb.autoEnabled) { - auto config = params->block(); - config.setEnabled(true); - setParameters(*config, frameContext.ccm.ccm, Matrix()); - return; - } - - uint32_t ct = frameContext.awb.colourTemperature; - if (frame > 0 && ct == ct_) { - frameContext.ccm.ccm = context.activeState.ccm.automatic; - return; - } - - ct_ = ct; - Matrix ccm = ccm_.getInterpolated(ct); - Matrix offsets = offsets_.getInterpolated(ct); - - context.activeState.ccm.automatic = ccm; - frameContext.ccm.ccm = ccm; + if (frameContext.awb.autoEnabled) + ccmAlgo_.prepare(context.activeState.ccm, frameContext.ccm, + frame, frameContext.awb.colourTemperature); - auto config = params->block(); - config.setEnabled(true); - setParameters(*config, ccm, offsets); + setParameters(params, frameContext); } /** @@ -167,7 +110,7 @@ void Ccm::process([[maybe_unused]] IPAContext &context, [[maybe_unused]] const rkisp1_stat_buffer *stats, ControlList &metadata) { - metadata.set(controls::ColourCorrectionMatrix, frameContext.ccm.ccm.data()); + ccmAlgo_.process(frameContext.ccm, metadata); } REGISTER_IPA_ALGORITHM(Ccm, "Ccm") diff --git a/src/ipa/rkisp1/algorithms/ccm.h b/src/ipa/rkisp1/algorithms/ccm.h index 3d3a660065ff..6689c42092f3 100644 --- a/src/ipa/rkisp1/algorithms/ccm.h +++ b/src/ipa/rkisp1/algorithms/ccm.h @@ -9,11 +9,16 @@ #include -#include "libcamera/internal/matrix.h" +#include -#include "libipa/interpolator.h" +#include "libcamera/internal/value_node.h" + +#include "libipa/ccm.h" +#include "libipa/fixedpoint.h" #include "algorithm.h" +#include "ipa_context.h" +#include "params.h" namespace libcamera { @@ -41,13 +46,9 @@ public: ControlList &metadata) override; private: - void setParameters(struct rkisp1_cif_isp_ctk_config &config, - const Matrix &matrix, - const Matrix &offsets); + void setParameters(RkISP1Params *params, IPAFrameContext &context); - unsigned int ct_; - Interpolator> ccm_; - Interpolator> offsets_; + CcmAlgorithm> ccmAlgo_; }; } /* namespace ipa::rkisp1::algorithms */ diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h index c36c1f7e0084..6698ccfd77cf 100644 --- a/src/ipa/rkisp1/ipa_context.h +++ b/src/ipa/rkisp1/ipa_context.h @@ -27,6 +27,7 @@ #include "libipa/agc_mean_luminance.h" #include "libipa/awb.h" #include "libipa/camera_sensor_helper.h" +#include "libipa/ccm.h" #include "libipa/fc_queue.h" #include "libipa/fixedpoint.h" @@ -105,10 +106,7 @@ struct IPAActiveState { ipa::awb::ActiveState awb; - struct { - Matrix manual; - Matrix automatic; - } ccm; + ipa::ccm::ActiveState ccm; struct { float requestedBrightness; @@ -207,9 +205,7 @@ struct IPAFrameContext : public FrameContext { double gain; } sensor; - struct { - Matrix ccm; - } ccm; + ipa::ccm::FrameContext ccm; struct { double lux; From patchwork Mon Jul 20 14:59:24 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27398 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 6C4B2C32FE for ; Mon, 20 Jul 2026 14:59:54 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 3DD8467E77; Mon, 20 Jul 2026 16:59:53 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ntoqqIH2"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 8400767E42 for ; Mon, 20 Jul 2026 16:59:44 +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 90B8F15CD; Mon, 20 Jul 2026 16:58:45 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559526; bh=PZjf6FQi9gCzmouGinDVKkg+erELkT3B1ZUh0u8Sa2k=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=ntoqqIH2T+Of2hQHUWKmN0Mb2V8+Rqx09VLpLygZ/kvRrra0H4K2klHVx5tkF25TU Qn40jj5n58ue0noiQBjH148ImEZ2QtJzmI9dI4624yBknxOOwzc0AhtqU4Hl4jfOak 074OBz6aykqKaGK215a9AZWRcX2Kyjbtib5ywYoI= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:24 +0200 Subject: [PATCH v6 06/31] ipa: simple: Port to use libipa CcmAlgorithm MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-6-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 , Kieran Bingham , Robert Mader X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=6404; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=f2e+ElLswiS5th0nFoFYfobTzlHF8UV7oPYZ1lTpAwc=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfTcI+Orur590ej8FILbmo+zRgIORC8FkBhM Cgvg6iaR7uJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal430wAKCRByNAaPFqFW PJuKD/9lgwVEwZaVsoRVUyJTMU2F1OdxdD6f7085wRnioksVrXSIox293sn2ci7c7chfiuh7uHw eIRv4gjqHyarDFR4DIRlHNy5xDIjvw6csMaC6Ac4gr6orvgn+4SxT8AQpAUElqJm+zOcrAg/8CP mI0a/xLgN9LfpA4D2eE7S2KBGDdBZuxq2P8KlSCtJ55zgf/1o/6Wi/uJ9VrTLwP82a2lRfrFrMD xRSlvsfyz/eGCamLUil3oIE1lyesqY/vCuAeEjSFYY0MyY1/i4tDpuaY3YlaQvGGPpsAj5ickxV fRw0CKFPdmej3MWiZ31IA1GN1JePBs9CP1hD4QG9IrENEIykK0hEMwfxvJ66YrWXU/jT7z9wm0T Oq5pOygJQmZRdolDzEj5aGFdNG4zp289fMTLWyd0qQOrLADj8qP8yjYDVgatoE3uP7Z7VNIVaH+ Wj69bwDfTJkuG6KYjXsHhpqqUdV43v01DurAhGgsyvbox2pHhLGcX8nFTzIDzWapTp4LYfQ7FNO nBAKUUO/sxDcVxIGv1dhvgVWzIr/sum0D/95mdY1H4d4bDysZshSfwlblMpBrvlR4aPWjqt6V3c kLikM0LsfZc54RcdkHweeJxfl9qmgniw6zyt6UYsa3LZQ4eil1J5WpQvh3W1mQ4mTjTHVPxhexo yjAkO2MAFS2VBig== 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" From: Kieran Bingham Now that libipa provides a common CCM algorithm implementation, replace the custom handling with the common Ccm. Signed-off-by: Kieran Bingham Signed-off-by: Jacopo Mondi Tested-by: Robert Mader Reviewed-by: Milan Zamazal Tested-by: Milan Zamazal --- v5->v6: - Remove a few [[maybe_unused]] - Add Milan's tag - Reword title to match other patches in the series --- src/ipa/simple/algorithms/ccm.cpp | 74 ++++++++++++++++++++++----------------- src/ipa/simple/algorithms/ccm.h | 22 +++++++----- src/ipa/simple/ipa_context.h | 5 +-- 3 files changed, 58 insertions(+), 43 deletions(-) diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp index 1174784edc7e..8b6da543a719 100644 --- a/src/ipa/simple/algorithms/ccm.cpp +++ b/src/ipa/simple/algorithms/ccm.cpp @@ -8,54 +8,64 @@ #include "ccm.h" -#include -#include - -#include - #include "libcamera/internal/matrix.h" -namespace { - -constexpr unsigned int kTemperatureThreshold = 100; - -} - namespace libcamera { namespace ipa::soft::algorithms { LOG_DEFINE_CATEGORY(IPASoftCcm) -int Ccm::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData) +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int Ccm::init(IPAContext &context, const ValueNode &tuningData) { - int ret = ccm_.readYaml(tuningData["ccms"], "ct", "ccm"); - if (ret < 0) { - LOG(IPASoftCcm, Error) - << "Failed to parse 'ccm' parameter from tuning file."; - return ret; - } - + /* Informs the 'adjust' component that CCM is available to apply Saturation */ context.ccmEnabled = true; - return 0; + return ccmAlgo_.init(tuningData, context.ctrlMap); } -void Ccm::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, - IPAFrameContext &frameContext, [[maybe_unused]] DebayerParams *params) +/** + * \copydoc libcamera::ipa::Algorithm::configure + */ +int Ccm::configure(IPAContext &context, + [[maybe_unused]] const IPAConfigInfo &configInfo) +{ + return ccmAlgo_.configure(context.activeState.ccm, + context.activeState.awb.automatic.colourTemperature); +} + +/** + * \copydoc libcamera::ipa::Algorithm::queueRequest + */ +void Ccm::queueRequest(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) { - const unsigned int ct = frameContext.awb.colourTemperature; + /* Nothing to do here, the ccm will be calculated in prepare() */ + if (frameContext.awb.autoEnabled) + return; - /* Change CCM only on bigger temperature changes. */ - if (!currentCcm_ || - utils::abs_diff(ct, lastCt_) >= kTemperatureThreshold) { - currentCcm_ = ccm_.getInterpolated(ct); - lastCt_ = ct; - } + ccmAlgo_.queueRequest(context.activeState.ccm, frameContext.ccm, controls); +} +void Ccm::prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, [[maybe_unused]] DebayerParams *params) +{ + if (frameContext.awb.autoEnabled) + ccmAlgo_.prepare(context.activeState.ccm, frameContext.ccm, + frame, frameContext.awb.colourTemperature); + + /* + * \todo: Split out combined matrix into individual parameters in + * DebayerParams and perform any pre-multiplication combination in the + * SoftISP component directly. + */ context.activeState.combinedMatrix = - currentCcm_.value() * context.activeState.combinedMatrix; - frameContext.ccm = currentCcm_.value(); + frameContext.ccm.ccm * context.activeState.combinedMatrix; } void Ccm::process([[maybe_unused]] IPAContext &context, @@ -64,7 +74,7 @@ void Ccm::process([[maybe_unused]] IPAContext &context, [[maybe_unused]] const SwIspStats *stats, ControlList &metadata) { - metadata.set(controls::ColourCorrectionMatrix, frameContext.ccm.data()); + ccmAlgo_.process(frameContext.ccm, metadata); } REGISTER_IPA_ALGORITHM(Ccm, "Ccm") diff --git a/src/ipa/simple/algorithms/ccm.h b/src/ipa/simple/algorithms/ccm.h index b20a7da8aa33..27f11bcbd6e9 100644 --- a/src/ipa/simple/algorithms/ccm.h +++ b/src/ipa/simple/algorithms/ccm.h @@ -7,13 +7,15 @@ #pragma once -#include +#include -#include "libcamera/internal/matrix.h" +#include "libcamera/internal/value_node.h" -#include +#include "libipa/ccm.h" +#include "libipa/fixedpoint.h" #include "algorithm.h" +#include "ipa_context.h" namespace libcamera { @@ -22,10 +24,14 @@ namespace ipa::soft::algorithms { class Ccm : public Algorithm { public: - Ccm() = default; - ~Ccm() = default; - int init(IPAContext &context, const ValueNode &tuningData) override; + + int configure(IPAContext &context, const IPAConfigInfo &configInfo) override; + + void queueRequest(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; + void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, @@ -36,9 +42,7 @@ public: ControlList &metadata) override; private: - unsigned int lastCt_; - Interpolator> ccm_; - std::optional> currentCcm_; + CcmAlgorithm> ccmAlgo_; }; } /* namespace ipa::soft::algorithms */ diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h index 29643a655ce1..ff312ae8f4e7 100644 --- a/src/ipa/simple/ipa_context.h +++ b/src/ipa/simple/ipa_context.h @@ -17,6 +17,7 @@ #include "libcamera/internal/vector.h" #include +#include #include #include "core_ipa_interface.h" @@ -38,6 +39,7 @@ struct IPASessionConfiguration { struct IPAActiveState { ipa::awb::ActiveState awb; + ipa::ccm::ActiveState ccm; struct { int32_t exposure; @@ -63,8 +65,7 @@ struct IPAActiveState { struct IPAFrameContext : public FrameContext { ipa::awb::FrameContext awb; - - Matrix ccm; + ipa::ccm::FrameContext ccm; struct { int32_t exposure; From patchwork Mon Jul 20 14:59:25 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27399 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 6425BC3301 for ; Mon, 20 Jul 2026 14:59:55 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 04E9067E76; Mon, 20 Jul 2026 16:59:55 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="uZUON+QO"; 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 2DB9B67E45 for ; Mon, 20 Jul 2026 16:59:45 +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 31D7616F9; Mon, 20 Jul 2026 16:58:46 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559526; bh=4A9kajOFuKXSQXUsoFZ+WfGwjzpqM6yrrwEjlszpxEw=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=uZUON+QOsoqS0GFrqO6LVM1z+1ErDGZk0DvCeS+zwT1qsotwHcn6h2neCRYLqavGM jrW5s5yxEIL/ckhJNsfzEcQLcZLfpkQz/KEKNF3xsH72iyZYqfE6v8l4ezY8PO9DYK rhNX0qtrZUkLJZsa4fzkoCkWxzFFEMWuDCcPbf6o= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:25 +0200 Subject: [PATCH v6 07/31] include: linux: mali-c55: Update to support CCM and Gamma MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-7-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 , Daniel Scally , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=5761; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=4A9kajOFuKXSQXUsoFZ+WfGwjzpqM6yrrwEjlszpxEw=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfTAdgEMIa4JaulM5vUy8aESe6vNP57yl5Ns BI9Lvu0EVaJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal430wAKCRByNAaPFqFW PFNxD/wPcZ0Sw/WMDyIamc4mLAOfT+JKAaQ+puZ6aR6c/1APOASQG4HDJdRUJonMasDICiX24HM E77SmjPifcboBxIED2Ff1XbBxuwrJ4a/CLX36+vuPVwD4aWRk4QxJIgaO5My9RRGQUy2Rl+g840 b8VPk3UZwcb4QSGhk7eJ9VjkGeLdpSbhisDS5INor+lgfwb4seRKlEezQ2iqSnDVwSpdwXqICai PTUQYBdNl4UX5Y8Go0jsEI6txvl6TZN26rTfegFILFIacxhAjPHYGrT0+CD32+H8So31Yefb7fL dUsqQ1GMO7vaez+SeEohwRFtg/C3Px2Qv2itPuj4wcIZy08haN1b28HLv7l4BFZy+27wQ/PxKtu YxOcIGGLSAvX4J4VvVLM8CEcUUfX36zEYjf9GaY9rPOUZksd6/EDui2pD/W7ipg8NrDYC1z9Y4K ZRVxAo9rIAE0ySWyO4WTJZnrUpR0U++lnb0PqCpNWEDYXkIwB5dIieW38o65jHkn/8+W3VxduVc lMJUA8E5dthh5QicYWCaU7Y/W2J9lLEo2TF6B47cH6K727pNBlPbBpVF9MplFD6dJgZfluDDSjT 4eDJ4ntaiObkhHd38Yw+OYXtKTliRISi6+68UaTemYLmkKL1OAuUncelWwdsadG1t8oGqIO+s9W GqrgvTqcjI+Mv6A== 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" Update the Mali-C55 uAPI header to add support for CCM and the RGB Gamma. Patches have been posted upstream but not yet merged https://patchwork.linuxtv.org/project/linux-media/list/?series=26932 Signed-off-by: Jacopo Mondi Reviewed-by: Daniel Scally Acked-by: Kieran Bingham --- include/linux/media/arm/mali-c55-config.h | 86 ++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/include/linux/media/arm/mali-c55-config.h b/include/linux/media/arm/mali-c55-config.h index 3d335f950eeb..ef0fe17df96e 100644 --- a/include/linux/media/arm/mali-c55-config.h +++ b/include/linux/media/arm/mali-c55-config.h @@ -36,6 +36,9 @@ */ #define MALI_C55_MAX_ZONES (15 * 15) +/* Number of RGB gamma LUT entries. */ +#define MALI_C55_NUM_GAMMA_LUT_ELEMENTS 129 + /** * struct mali_c55_ae_1024bin_hist - Auto Exposure 1024-bin histogram statistics * @@ -219,6 +222,9 @@ struct mali_c55_stats_buffer { * @MALI_C55_PARAM_BLOCK_AWB_GAINS_AEXP: Auto-white balance gains for AEXP-0 tap * @MALI_C55_PARAM_MESH_SHADING_CONFIG : Mesh shading tables configuration * @MALI_C55_PARAM_MESH_SHADING_SELECTION: Mesh shading table selection + * @MALI_C55_PARAM_BLOCK_CCM: Colour correction matrix + * @MALI_C55_PARAM_BLOCK_GAMMA_FR: Gamma gain and offset for FR pipe + * @MALI_C55_PARAM_BLOCK_GAMMA_DS: Gamma gain and offset for DS pipe */ enum mali_c55_param_block_type { MALI_C55_PARAM_BLOCK_SENSOR_OFFS, @@ -232,6 +238,9 @@ enum mali_c55_param_block_type { MALI_C55_PARAM_BLOCK_AWB_GAINS_AEXP, MALI_C55_PARAM_MESH_SHADING_CONFIG, MALI_C55_PARAM_MESH_SHADING_SELECTION, + MALI_C55_PARAM_BLOCK_CCM, + MALI_C55_PARAM_BLOCK_GAMMA_FR, + MALI_C55_PARAM_BLOCK_GAMMA_DS, }; /** @@ -757,6 +766,78 @@ struct mali_c55_params_mesh_shading_selection { __u16 mesh_strength; }; +/** + * struct mali_c55_params_ccm - Coefficients, offsets and gains for the colour + * correction matrix + * + * The colour correction module converts images data from a sensor-specific + * colour space to known one. + * + * Colour correction is applied after demosaicing and each pixel is represented + * as a column vector of the three RGB colour channels on which the following + * operations take place: + * 1) An offset is subtracted from each colour channel + * 2) Each colour channel is multiplied by a gain + * 3) The pixel column vector is multiplied by the colour correction matrix + * + * This struct allows users to configure the coefficients for CCM and the + * per-channel offsets and gains. The nine matrix coefficients are expressed as + * 13 bits signed Q4.8 Sign/Magnitude fixed-point numbers, the three gain + * multipliers are expressed as 12 bits unsigned Q4.8 fixed-point numbers and + * the three offsets are expressed as a 12 bits unsigned integers. + * + * header.type should be set to MALI_C55_PARAM_BLOCK_CCM from + * :c:type:`mali_c55_param_block_type`. + * + * @header: The Mali-C55 parameters block header + * @coeffs: 3x3 color conversion matrix coefficients in sign/magnitude + * Q4.8 format + * @gains: Gains for red, green and blue channels in unsigned Q4.8 format + * @offs: Offsets for red, green and blue channels + */ +struct mali_c55_params_ccm { + struct v4l2_isp_params_block_header header; + __u16 coeffs[3][3]; + __u16 gains[3]; + __u16 offs[3]; +}; + +/** + * struct mali_c55_params_gamma - RGB Gamma correction + * + * Gamma correction is used to program a standard gamma curve such as the sRGB + * one. It provides gains and offsets to implement contrast adjustments. + * + * Gamma correction is applied on both the FR and DS pipes separately in the RGB + * colour domain where the following operations take place: + * 1) An offset is subtracted from each colour channel + * 2) Each colour channel is multiplied by a gain + * 3) The Gamma LUT is applied to each colour channel + * + * The Gamma LUT has 129 entries where each node is an unsigned 12 bit number. + * It is expected that LUT[0]=0 and LUT[128]=0xfff, with the other 127 values + * defining the Gamma correction curve. The three gain multipliers are expressed + * as 12-bits unsigned Q4.8 fixed-point numbers and the three offsets are + * expressed as a 12-bits unsigned integers. + * + * As one Gamma correction block is available on both the FR and DS pipes, the + * header.type field should be set to one of either + * MALI_C55_PARAM_BLOCK_GAMMA_FR or MALI_C55_PARAM_BLOCK_GAMMA_DS from + * :c:type:`mali_c55_param_block_type`. + * + * @header: The Mali-C55 parameters block header + * @gains: Gains for the red, green and blue channel in unsigned Q4.8 format + * @offs: Offsets subtracted from the red, green and blue channels + * in unsigned 12 bits format + * @lut: 129-node Gamma LUT in unsigned 12 bits format + */ +struct mali_c55_params_gamma { + struct v4l2_isp_params_block_header header; + __u16 gains[3]; + __u16 offs[3]; + __u32 lut[MALI_C55_NUM_GAMMA_LUT_ELEMENTS]; +}; + /** * define MALI_C55_PARAMS_MAX_SIZE - Maximum size of all Mali C55 Parameters * @@ -780,6 +861,9 @@ struct mali_c55_params_mesh_shading_selection { sizeof(struct mali_c55_params_awb_config) + \ sizeof(struct mali_c55_params_awb_gains) + \ sizeof(struct mali_c55_params_mesh_shading_config) + \ - sizeof(struct mali_c55_params_mesh_shading_selection)) + sizeof(struct mali_c55_params_mesh_shading_selection) + \ + sizeof(struct mali_c55_params_ccm) + \ + sizeof(struct mali_c55_params_gamma) + \ + sizeof(struct mali_c55_params_gamma)) #endif /* __UAPI_MALI_C55_CONFIG_H */ From patchwork Mon Jul 20 14:59:26 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27400 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 C0D7DC328C for ; Mon, 20 Jul 2026 14:59:56 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 35AF067E82; Mon, 20 Jul 2026 16:59:56 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="q+GAobXe"; 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 B1DF667E4F for ; Mon, 20 Jul 2026 16:59:45 +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 C8F446DF; Mon, 20 Jul 2026 16:58:46 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559527; bh=SOOy6+b1C5ELyjZe7grsoEYDR364OsGEhvltzEaa+NA=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=q+GAobXez2HFgTM6vHypj5IyR2PAyY1oK3fS9WwCBpDwVrtulVVsyMXrQA3x18BB6 u5Hj94vKIFlHNX+BMyAckUvu1Ru9qGDBHIVfqJtEP01vCtPoj0P5L/kKDOJ/UspQuA nD81G0IELowl5aAW+WYeEeHjNBS1oLs0vsh1sSwY= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:26 +0200 Subject: [PATCH v6 08/31] ipa: mali-c55: Implement Ccm algorithm MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-8-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 , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=9962; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=SOOy6+b1C5ELyjZe7grsoEYDR364OsGEhvltzEaa+NA=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfU6osF9mz6jdePB0O4kIjQZvznPVe9TIhqp i7a24zxjbOJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal431AAKCRByNAaPFqFW PCzFEADHN+4iRTUlsNMvgURv/iOWvNJ1XZzs/yYtOXoFJavsBhoTElCMneFHf/2u+Mhaegp3LR6 aDica5Zyjx+hCYEKlPmLmQDZvme741ASHg2ekfoEXJFfYAUanjauERjLwd36qNFRePzJutI3Yb7 bMHE+TwLIa/ELVgVZoxpaB792SEO0DtPhDloh3Kka/BFrsOry4H7BGRFAvztlUxsk/oStjkq2IV qDza+Rxxfx9AwakfSNXuPHa5h5eO19AUMVqslz6/opWmU0qy8v5yws/K05TAnuYDF+1eRzuknYf um//mONJs8L2DUodH0ASrOCZN18Kb+dJZDwCubmTRAmExWZyDi2/1b6GBjE98aE++gBN4ltFdzX tjxSpN71G4K6ANuABshqW3fMnAFi0nxuoHnTFVTnnuoGTkISkRFtPlSqr/ealMf/2BLAtuqKQ/q 0u7FrhCX7J5opw/olOkCqpXC9aXIoHse/XIDM23YQmpq5LusS0PaiORR2MUKXwFCL64P65hgBQm uWHWBcQkjRG8uovcQCswBc/LSdlo+e0Wx+ri/LWKJcULZu6RzPAAYuzYGLYsV9vLZ5lDsaiPD/x FPXJgel5a/d+GrvS08MxricVUBHS+PZmGMV5gce6ZtsEFWyIYlcTziOnS0vc8EaWSyUJSjKRNch AiaosBdKIDnUw1w== 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" Implement Ccm algorithm for Mali-C55 base on the libipa CcmAlgorithm class. Mali has configurable colour gains as part of the CCM hardware block. Fix them to 1.0 for the time being as white balance is performed by the dedicated Awb algorithm in the Bayer colour space. The existing Mali Ccm algorithm implementation is a bit more strict on when to re-interpolate the CCM coefficients. Keep the algorithm a little more strict and only interpolate on 20% color temperature changes. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham --- src/ipa/mali-c55/algorithms/ccm.cpp | 173 ++++++++++++++++++++++++++++++++ src/ipa/mali-c55/algorithms/ccm.h | 66 ++++++++++++ src/ipa/mali-c55/algorithms/meson.build | 1 + src/ipa/mali-c55/ipa_context.h | 3 + src/ipa/mali-c55/params.h | 2 + 5 files changed, 245 insertions(+) diff --git a/src/ipa/mali-c55/algorithms/ccm.cpp b/src/ipa/mali-c55/algorithms/ccm.cpp new file mode 100644 index 000000000000..b5196a7ee85b --- /dev/null +++ b/src/ipa/mali-c55/algorithms/ccm.cpp @@ -0,0 +1,173 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * Mali C55 Color Correction Matrix control algorithm + */ + +#include "ccm.h" + +#include + +/** + * \file ccm.h + * \brief Mali-C55 CCM implementation + */ + +namespace libcamera { + +namespace ipa::mali_c55::algorithms { + +LOG_DEFINE_CATEGORY(MaliC55Ccm) + +/** + * \class Ccm + * \brief Mali-C55 color correction matrix algorithm + */ + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int Ccm::init(IPAContext &context, const ValueNode &tuningData) +{ + auto &cmap = context.ctrlMap; + int ret = ccmAlgo_.init(tuningData, cmap); + if (ret) + return ret; + + /* + * Mali-C55 allows to perform WB in the RGB color space as part of the + * CCM. Fix the gains at 1.0 as we perform White Balance in the Bayer + * domain. + */ + gain_ = 1.0; + + return 0; +} + +/** + * \copydoc libcamera::ipa::Algorithm::configure + */ +int Ccm::configure(IPAContext &context, + [[maybe_unused]] const IPACameraSensorInfo &configInfo) +{ + lastCt_ = context.activeState.awb.automatic.colourTemperature; + return ccmAlgo_.configure(context.activeState.ccm, lastCt_); +} + +void Ccm::queueRequest(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) +{ + /* Nothing to do here, the ccm will be calculated in prepare() */ + if (frameContext.awb.autoEnabled) + return; + + ccmAlgo_.queueRequest(context.activeState.ccm, frameContext.ccm, controls); +} + +void Ccm::setParameters(MaliC55Params *params, const IPAFrameContext &frameContext) +{ + auto config = params->block(); + + for (unsigned int i = 0; i < 3; i++) + config->gains[i] = UQ<4, 8>(gain_).quantized(); + + for (unsigned int i = 0; i < 3; i++) + config->offs[i] = frameContext.ccm.offsets[i][0]; + + const Matrix &ccm = frameContext.ccm.ccm; + for (unsigned int i = 0; i < 3; i++) { + for (unsigned int j = 0; j < 3; j++) { + uint16_t val = Q<5, 8>(ccm[i][j]).quantized(); + + /* + * CCM coefficients are expected to be in sign/magnitude + * format. + * + * As we're here handling the case where ccm[i][j] is + * negative, its quantized representation is stored in + * 'val' as 2's complement. + * + * We need to invert the 2's complement by + * re-complementing it to 1 and adding 1 back. + * + * Let's make a practical example on how to reverse the + * 2's complement of -7 with 4 bits and BIT(5) for sign. + * + * 2's complement (ignore sign bit) + * - 1's complement of abs(-7) + * 2^4 - 1 - 7 = 8 -> 1000 + * - Add one: 8 + 1 = 9 -> 1001 + * + * -7 is then [1 1001] in memory in 2's complement + * + * Reverse 2's complement (ignore sign bit) + * - 1's complement of the 2's complement representation + * 2^4 - 1 - 9 = 6 -> 0110 + * - add one: 6 + 1 = 7 -> 0111 + * + * -7 is then [1 0111] in memory in Sign/Magnitude + */ + if (val && ccm[i][j] < 0) + val = ((~(val & ~(1 << 12)) & 0x1fff) + 1) | (1 << 12); + + config->coeffs[i][j] = val; + } + } + + config.setEnabled(true); + + LOG(MaliC55Ccm, Debug) << "Setting ccm: " << ccm << " offsets: " + << frameContext.ccm.offsets + << " with fixed gain " << gain_; +} + +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void Ccm::prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, MaliC55Params *params) +{ + if (!frameContext.awb.autoEnabled) { + setParameters(params, frameContext); + return; + } + + /* + * The Mali-C55 Ccm implementation is slightly stricter than the + * CcmAlgorithm class and only re-interpolates if the colour temperature + * changes of a certain amount. + */ + float ct = frameContext.awb.colourTemperature * 1.0f; + if (frame > 0 && (ct < lastCt_ * 1.2 && ct > lastCt_ * 0.8)) { + frameContext.ccm.ccm = context.activeState.ccm.automatic.ccm; + frameContext.ccm.offsets = context.activeState.ccm.automatic.offsets; + lastCt_ = ct; + + return; + } + + ccmAlgo_.prepare(context.activeState.ccm, frameContext.ccm, frame, ct); + setParameters(params, frameContext); + lastCt_ = ct; +} + +/** + * \copydoc libcamera::ipa::Algorithm::process + */ +void Ccm::process([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + [[maybe_unused]] const mali_c55_stats_buffer *stats, + ControlList &metadata) +{ + ccmAlgo_.process(frameContext.ccm, metadata); +} + +REGISTER_IPA_ALGORITHM(Ccm, "Ccm") + +} /* namespace ipa::mali_c55::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/mali-c55/algorithms/ccm.h b/src/ipa/mali-c55/algorithms/ccm.h new file mode 100644 index 000000000000..73649204a7ee --- /dev/null +++ b/src/ipa/mali-c55/algorithms/ccm.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * Mali C55 Color Correction Matrix control algorithm + */ + +#pragma once + +#include + +#include + +#include "libcamera/internal/value_node.h" + +#include "libipa/ccm.h" +#include "libipa/fixedpoint.h" + +#include "algorithm.h" +#include "ipa_context.h" +#include "params.h" + +namespace libcamera { + +namespace ipa::mali_c55::algorithms { + +class Ccm : public Algorithm +{ +public: + Ccm() {} + ~Ccm() = default; + + int init(IPAContext &context, const ValueNode &tuningData) override; + int configure(IPAContext &context, + const IPACameraSensorInfo &configInfo) override; + void queueRequest(IPAContext &context, + const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + MaliC55Params *params) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const mali_c55_stats_buffer *stats, + ControlList &metadata) override; + +private: + void setParameters(MaliC55Params *params, const IPAFrameContext &context); + + /* + * The CCM coefficient registers are said to be in Q<4,8> but this + * doesn't include the sign bit as the register is 13 bits wide + * (Q-format TI variant). + * + * As the Quantized class uses the ARM variant of the Q-format notation, + * make it <5, 8> to include the sign bit. + */ + CcmAlgorithm> ccmAlgo_; + float gain_; + float lastCt_; +}; + +} /* namespace ipa::mali_c55::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/mali-c55/algorithms/meson.build b/src/ipa/mali-c55/algorithms/meson.build index 1665da071634..24a27ce66562 100644 --- a/src/ipa/mali-c55/algorithms/meson.build +++ b/src/ipa/mali-c55/algorithms/meson.build @@ -4,5 +4,6 @@ mali_c55_ipa_algorithms = files([ 'agc.cpp', 'awb.cpp', 'blc.cpp', + 'ccm.cpp', 'lsc.cpp', ]) diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h index 7dc765a3542b..ef3188e347d7 100644 --- a/src/ipa/mali-c55/ipa_context.h +++ b/src/ipa/mali-c55/ipa_context.h @@ -15,6 +15,7 @@ #include #include "libipa/awb.h" +#include "libipa/ccm.h" #include "libipa/fixedpoint.h" namespace libcamera { @@ -56,6 +57,7 @@ struct IPAActiveState { } agc; ipa::awb::ActiveState awb; + ipa::ccm::ActiveState ccm; }; struct IPAFrameContext : public FrameContext { @@ -66,6 +68,7 @@ struct IPAFrameContext : public FrameContext { } agc; ipa::awb::FrameContext awb; + ipa::ccm::FrameContext ccm; }; struct IPAContext { diff --git a/src/ipa/mali-c55/params.h b/src/ipa/mali-c55/params.h index 3abcb7f94916..2ce55262031d 100644 --- a/src/ipa/mali-c55/params.h +++ b/src/ipa/mali-c55/params.h @@ -29,6 +29,7 @@ enum class MaliC55Blocks : uint16_t { AwbConfig, MeshShadingConfig, MeshShadingSel, + Ccm, }; namespace details { @@ -55,6 +56,7 @@ MALI_C55_DEFINE_BLOCK_TYPE(AwbGains, awb_gains, BLOCK_AWB_GAINS); MALI_C55_DEFINE_BLOCK_TYPE(AwbConfig, awb_config, BLOCK_AWB_CONFIG); MALI_C55_DEFINE_BLOCK_TYPE(MeshShadingConfig, mesh_shading_config, MESH_SHADING_CONFIG); MALI_C55_DEFINE_BLOCK_TYPE(MeshShadingSel, mesh_shading_selection, MESH_SHADING_SELECTION); +MALI_C55_DEFINE_BLOCK_TYPE(Ccm, ccm, BLOCK_CCM); struct param_traits { using id_type = MaliC55Blocks; From patchwork Mon Jul 20 14:59:27 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27401 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 E8D4EC3303 for ; Mon, 20 Jul 2026 14:59:57 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 705E867E7F; Mon, 20 Jul 2026 16:59:57 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="YPWlayhT"; 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 5720967E5D for ; Mon, 20 Jul 2026 16:59:46 +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 5FDB81975; Mon, 20 Jul 2026 16:58:47 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559527; bh=r5RK5eMNhW6KS9saJ6/fZWiL7ukwpBrJrnd7dbUPYDg=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=YPWlayhT4AHrQcvL7/5MoxClFNRjPbzzoETMUmCx00m34nmvrvLGNkFMf6JgkCKo8 MQoT+JjBSfuyaAAfAwV88iXmGuGspYXj5qK/Do0Gr+pJfJ/ghabjWvy5V47ZtP0tuR NApXfnarT5GoUZVu9d/sxRH07nLaVmygfZMit+7A= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:27 +0200 Subject: [PATCH v6 09/31] ipa: rkisp1: lsc: Re-architecture Lsc support MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-9-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 , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=15654; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=r5RK5eMNhW6KS9saJ6/fZWiL7ukwpBrJrnd7dbUPYDg=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfUKOlNxhw/1RZh+lsRcraQxRtmbAi/y6HBV n5lGiyIlpWJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal431AAKCRByNAaPFqFW PBYMD/0edV33IiKbJRYFBtJk48j3zzZs8d/8lYJgQC+MKHxs3vKlplWLPzqgLw2nhOb3S9r9lOO GW+WF5YxmX7uA35qbn9oKUxuJ4pSaqYiNERQwzKQckv6Pj5JsTgRI8MRGkUYVL2bAMYBxhWRAdQ 3b8RWoUBdkX90hei0hYlnLsLMbjjJo1oRS+swDai5sDZEz0u6OdZcnpjD5wChbh/8y05QhKVqaS ZxcUI+M62xy3zSsI0f4S2ebNzphh8Lim5KL3zAYwpkKfKe2Ffno9fzVojk0zNx2QphjNiTKsffY UWmYEcByziwDiF2/pDorLFbgWbrfP+VnIQ7RwpxeFH6fINUPfV6zw9RKkEc4WtlnAdtG2aUot8i 6Ls1VOSgs34QqH5JVIRRBuDJw3wijELXHB95T0Nhz1Vp8M18++BtMmq92mn3arbuxjKpWg5Vmmd /HlQ+OD3xjcgSJBSZHm6ZoTlI7t78zCm/1EuC3WoQzBffE61WcaEA+dXea+OQH5FLYccZBpPiru zQfgq7d9wIcoGOGvAElUucfDYAjn20JpVSrsVpR2jFv7LBowgpsylG9v1q4NIRKQHwQ+R4mMrZ4 9ACS4uju7MPoN0eiNSNp5upKth6XiUZ4ee8FqalKUBmZXYdKPCKji6ZUbDpaMB8u+janu4kvWQj hurUG/q5Ql8IMTQ== 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 RkISP1 LSC implementation is realized by the following components: - 2 "tuning file" parser classes: - LscPolynomialLoader - LscTableLoader which implement the "parseLscData" function that loads from tuning file a polynomial representation of the lsc gains or a table of gains respectively. - A hierachy of "ShadingDescriptors" - LscPolynomialShadingDescriptor - LscTableShadingDescriptor that implement the "sampleForCrop" interface which expands a polynomial to a list of gains for the LscPolynomialShadingDescriptor and simply return the data parsed from tuning file for LscTableShadingDescriptor. Unify the two functionalities in a single class hierarchy whose interface is defined by the LscImplementation pure virtual class implemented by LscPolynomialImpl and LscTableImpl. The LensShadingCorrection algorithm class instantiates the correct sub-class and uses the file parsing and table expansion functionalities by implementing the composition pattern. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham --- src/ipa/rkisp1/algorithms/lsc.cpp | 247 +++++++++++++++++--------------------- src/ipa/rkisp1/algorithms/lsc.h | 49 ++++---- 2 files changed, 141 insertions(+), 155 deletions(-) diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index 06f5913c5750..38380aa839c6 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -54,17 +54,6 @@ void Interpolator:: namespace ipa::rkisp1::algorithms { -/** - * \class LensShadingCorrection - * \brief RkISP1 Lens Shading Correction control - * - * Due to the optical characteristics of the lens, the light intensity received - * by the sensor is not uniform. - * - * The Lens Shading Correction algorithm applies multipliers to all pixels to - * compensate for the lens shading effect. The coefficients are specified in a - * downscaled table in the tuning data. - */ LOG_DEFINE_CATEGORY(RkISP1Lsc) @@ -72,54 +61,110 @@ namespace { constexpr int kColourTemperatureQuantization = 10; -class LscPolynomialShadingDescriptor : public LensShadingCorrection::ShadingDescriptor +class LscPolynomialImpl : public LscImplementation { +private: + struct PolynomialComponents { + LscPolynomial pr; + LscPolynomial pgr; + LscPolynomial pgb; + LscPolynomial pb; + }; + using PolynomialComponentsMap = std::map; + public: - LscPolynomialShadingDescriptor(const LscPolynomial &pr, const LscPolynomial &pgr, - const LscPolynomial &pgb, const LscPolynomial &pb) - : pr_(pr), pgr_(pgr), pgb_(pgb), pb_(pb) + LscPolynomialImpl(const Size &sensorSize) + : sensorSize_(sensorSize) { } - LensShadingCorrection::Components sampleForCrop(const Rectangle &cropRectangle, - Span xSizes, - Span ySizes) override; + int parseLscData(const ValueNode &sets) override; + + LensShadingCorrection::ComponentsMap + sampleForCrop(const Rectangle &cropRectangle, + Span xSizes, + Span ySizes) override; private: + std::vector sizesListToPositions(Span sizes); std::vector samplePolynomial(const LscPolynomial &poly, Span xPositions, Span yPositions, const Rectangle &cropRectangle); + PolynomialComponentsMap lscData_; + Size sensorSize_; +}; - std::vector sizesListToPositions(Span sizes); +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); - LscPolynomial pr_; - LscPolynomial pgr_; - LscPolynomial pgb_; - LscPolynomial pb_; -}; + 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; + } -LensShadingCorrection::Components -LscPolynomialShadingDescriptor::sampleForCrop(const Rectangle &cropRectangle, - Span xSizes, - Span ySizes) + 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; +} + +LensShadingCorrection::ComponentsMap +LscPolynomialImpl::sampleForCrop(const Rectangle &cropRectangle, + Span xSizes, + Span ySizes) { std::vector xPos = sizesListToPositions(xSizes); std::vector yPos = sizesListToPositions(ySizes); - return { - .r = samplePolynomial(pr_, xPos, yPos, cropRectangle), - .gr = samplePolynomial(pgr_, xPos, yPos, cropRectangle), - .gb = samplePolynomial(pgb_, xPos, yPos, cropRectangle), - .b = samplePolynomial(pb_, xPos, yPos, cropRectangle) - }; + LensShadingCorrection::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 -LscPolynomialShadingDescriptor::samplePolynomial(const LscPolynomial &poly, - Span xPositions, - Span yPositions, - const Rectangle &cropRectangle) +LscPolynomialImpl::samplePolynomial(const LscPolynomial &poly, + Span xPositions, + Span yPositions, + const Rectangle &cropRectangle) { double m = poly.getM(); double x0 = cropRectangle.x / m; @@ -160,7 +205,7 @@ LscPolynomialShadingDescriptor::samplePolynomial(const LscPolynomial &poly, * output: 0.0 0.2 0.5 0.8 1.0 */ std::vector -LscPolynomialShadingDescriptor::sizesListToPositions(Span sizes) +LscPolynomialImpl::sizesListToPositions(Span sizes) { const int half = sizes.size(); std::vector positions(half * 2 + 1); @@ -176,74 +221,12 @@ LscPolynomialShadingDescriptor::sizesListToPositions(Span sizes) return positions; } -class LscPolynomialLoader +class LscTableImpl : public LscImplementation { public: - LscPolynomialLoader(const Size &sensorSize) - : sensorSize_(sensorSize) - { - } - - int parseLscData(const ValueNode &sets, - LensShadingCorrection::ShadingDescriptorMap &lscData); + int parseLscData(const ValueNode &sets) override; -private: - Size sensorSize_; -}; - -int LscPolynomialLoader::parseLscData(const ValueNode &sets, - LensShadingCorrection::ShadingDescriptorMap &lscData) -{ - 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( - ct, std::make_unique( - *pr, *pgr, *pgb, *pb)); - } - - if (lscData.empty()) { - LOG(RkISP1Lsc, Error) << "Failed to load any sets"; - return -EINVAL; - } - - return 0; -} - -class LscTableShadingDescriptor : public LensShadingCorrection::ShadingDescriptor -{ -public: - LscTableShadingDescriptor(LensShadingCorrection::Components components) - : lscData_(std::move(components)) - { - } - - LensShadingCorrection::Components + LensShadingCorrection::ComponentsMap sampleForCrop([[maybe_unused]] const Rectangle &cropRectangle, [[maybe_unused]] Span xSizes, [[maybe_unused]] Span ySizes) override @@ -253,28 +236,19 @@ public: return lscData_; } -private: - LensShadingCorrection::Components lscData_; -}; - -class LscTableLoader -{ -public: - int parseLscData(const ValueNode &sets, - LensShadingCorrection::ShadingDescriptorMap &lscData); - private: std::vector parseTable(const ValueNode &tuningData, const char *prop); + + LensShadingCorrection::ComponentsMap lscData_; }; -int LscTableLoader::parseLscData(const ValueNode &sets, - LensShadingCorrection::ShadingDescriptorMap &lscData) +int LscTableImpl::parseLscData(const ValueNode &sets) { for (const auto &set : sets.asList()) { uint32_t ct = set["ct"].get(0); - if (lscData.count(ct)) { + if (lscData_.count(ct)) { LOG(RkISP1Lsc, Error) << "Multiple sets found for color temperature " << ct; @@ -295,11 +269,10 @@ int LscTableLoader::parseLscData(const ValueNode &sets, return -EINVAL; } - lscData.emplace( - ct, std::make_unique(std::move(components))); + lscData_.emplace(ct, std::move(components)); } - if (lscData.empty()) { + if (lscData_.empty()) { LOG(RkISP1Lsc, Error) << "Failed to load any sets"; return -EINVAL; } @@ -307,7 +280,7 @@ int LscTableLoader::parseLscData(const ValueNode &sets, return 0; } -std::vector LscTableLoader::parseTable(const ValueNode &tuningData, +std::vector LscTableImpl::parseTable(const ValueNode &tuningData, const char *prop) { static constexpr unsigned int kLscNumSamples = @@ -363,6 +336,18 @@ unsigned int quantize(unsigned int value, unsigned int step) } /* namespace */ +/** + * \class LensShadingCorrection + * \brief RkISP1 Lens Shading Correction control + * + * Due to the optical characteristics of the lens, the light intensity received + * by the sensor is not uniform. + * + * The Lens Shading Correction algorithm applies multipliers to all pixels to + * compensate for the lens shading effect. The coefficients are specified in a + * downscaled table in the tuning data. + */ + LensShadingCorrection::LensShadingCorrection() : lastAppliedCt_(0), lastAppliedQuantizedCt_(0) { @@ -388,22 +373,21 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, return -EINVAL; } - ShadingDescriptorMap lscData; - int ret = 0; + int ret; std::string type = tuningData["type"].get("table"); if (type == "table") { LOG(RkISP1Lsc, Debug) << "Loading tabular LSC data."; - auto loader = LscTableLoader(); - ret = loader.parseLscData(sets, lscData); + algo_ = std::make_unique(); + ret = algo_->parseLscData(sets); } else if (type == "polynomial") { LOG(RkISP1Lsc, Debug) << "Loading polynomial LSC data."; /* * \todo: Most likely the reference frame should be native_size. * Let's wait how the internal discussions progress. */ - auto loader = LscPolynomialLoader(context.sensorInfo.activeAreaSize); - ret = loader.parseLscData(sets, lscData); + algo_ = std::make_unique(context.sensorInfo.activeAreaSize); + ret = algo_->parseLscData(sets); } else { LOG(RkISP1Lsc, Error) << "Unsupported LSC data type '" << type << "'"; @@ -416,8 +400,6 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, context.ctrlMap[&controls::LensShadingCorrectionEnable] = ControlInfo(false, true, true); - shadingDescriptors_ = std::move(lscData); - return 0; } @@ -453,11 +435,8 @@ int LensShadingCorrection::configure(IPAContext &context, } LOG(RkISP1Lsc, Debug) << "Sample LSC data for " << configInfo.analogCrop; - std::map shadingData; - for (const auto &[t, descriptor] : shadingDescriptors_) - shadingData[t] = descriptor->sampleForCrop(configInfo.analogCrop, - xSize_, ySize_); - + ComponentsMap shadingData = algo_->sampleForCrop(configInfo.analogCrop, + xSize_, ySize_); sets_.setData(std::move(shadingData)); context.activeState.lsc.enabled = true; diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h index 0a256e225327..3463531f2ab2 100644 --- a/src/ipa/rkisp1/algorithms/lsc.h +++ b/src/ipa/rkisp1/algorithms/lsc.h @@ -18,9 +18,20 @@ namespace libcamera { namespace ipa::rkisp1::algorithms { +class LscImplementation; + class LensShadingCorrection : public Algorithm { public: + struct Components { + std::vector r; + std::vector gr; + std::vector gb; + std::vector b; + }; + + using ComponentsMap = std::map; + LensShadingCorrection(); ~LensShadingCorrection() = default; @@ -36,31 +47,10 @@ public: IPAFrameContext &frameContext, const rkisp1_stat_buffer *stats, ControlList &metadata) override; - - struct Components { - std::vector r; - std::vector gr; - std::vector gb; - std::vector b; - }; - - class ShadingDescriptor - { - public: - virtual ~ShadingDescriptor() = default; - virtual Components sampleForCrop(const Rectangle &cropRectangle, - Span xSizes, - Span ySizes) = 0; - }; - - using ShadingDescriptorMap = std::map>; - private: void setParameters(rkisp1_cif_isp_lsc_config &config); void copyTable(rkisp1_cif_isp_lsc_config &config, const Components &set0); - ShadingDescriptorMap shadingDescriptors_; - ipa::Interpolator sets_; std::vector xSize_; std::vector ySize_; uint16_t xGrad_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE]; @@ -70,6 +60,23 @@ private: unsigned int lastAppliedCt_; unsigned int lastAppliedQuantizedCt_; + + ipa::Interpolator sets_; + + std::unique_ptr algo_; +}; + +class LscImplementation +{ +public: + virtual ~LscImplementation() {} + + virtual int parseLscData(const ValueNode &sets) = 0; + + virtual LensShadingCorrection::ComponentsMap + sampleForCrop(const Rectangle &cropRectangle, + Span xSizes, + Span ySizes) = 0; }; } /* namespace ipa::rkisp1::algorithms */ From patchwork Mon Jul 20 14:59:28 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27402 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 7A3CAC32FE for ; Mon, 20 Jul 2026 14:59:59 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id DE67367E74; Mon, 20 Jul 2026 16:59:58 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="djHuLXCq"; 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 E903867E54 for ; Mon, 20 Jul 2026 16:59:46 +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 0F52E1B9D; Mon, 20 Jul 2026 16:58:48 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559528; bh=ZSRZQzzF3zkdj8FOn5jdywUjwHafg84s6a5G8OC+qMA=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=djHuLXCqwCWdh41PEQB77tTKqRdfnCrLedqMJVH4FzyEr7DWp39tHlt3636RsOYkZ 02OaUbDUil/MP9O4aJxjysyAoJisxiJWzqTv1PLOOD+2wzwazmldyNHMcOxAs1FkLo cZFZa3hg2mgfhpGkBURA2GTUd4UvRlVQFm58e8QI= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:28 +0200 Subject: [PATCH v6 10/31] ipa: libipa: Introduce lsc_base.h MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-10-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 , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=11286; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=ZSRZQzzF3zkdj8FOn5jdywUjwHafg84s6a5G8OC+qMA=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfUii6QEx2PlXTPAYZ6Vqjkz+LFLh8tuprEW yysqpWSYSqJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal431AAKCRByNAaPFqFW PF9NEACUIMHSKu0ZH8/FmIWlPRiZ5kMucREXIFsrz0BoBksodTZBvK84y8aTmWoN6OMZwv97kZt z/pg9oruBUREDRL/QMUjKObjGReYOm66LLH74P922w3+g1ZFlQEGpiOUusVGfVVMztG8mgQsMGM 5WNXkl4f+ITyW73YN1UqTul91oFiWzyPVoZWX/xRfKtET6X2MIkaREnnHc9KRvlk3mDg0V2DQtp 3vP+i6+aah2ADYNZImGK0ZoMrUdB29KDgRrbnkSASSukVSwZT+w1NexnDPLbXUs6p7IQoP97NEf yQFrsio/qn8jAbJ3jVwomG3/VmbtQpOtGMBb8eC4wppBzOCZkEnFbtYV6EhW8finb/QT1w3SPnh elsOBW3tp2VvaKcnMHf6AXSLEAtDCY0NyDwyinf7fmQGn+PO0Cztcadi3Aj6DAwhM9uhpt3jmBP pdYpvhlSdORY0+QckgltZDxmkqDoyHtUTJTdqW3TZ9SmNFYs4grBipDwDKzICA+uhiFJq2OWnKG /woxyyaVCBepYX89LZWDlrKdUZn3TwDfPkEAHNWXA1wyfFx/K3EYo2w4XaC2DFoCgQP6NvxnU4h B765ar3xg9VNsgcR4zWEDRIzy9Ab+aWgmLrYqyfrQGurR0bBq3XJSji/F9idfBdS3D4CJOx1Tkw kIN0TPeLnpm2Q2w== 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" Provide a header and implementation for the base definitions of common lens shading calibration data types, and move re-usable code out from the RkISP1 lsc algorithm. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Reviewed-by: Stefan Klug --- src/ipa/libipa/lsc_base.cpp | 95 +++++++++++++++++++++++++++++++++++++++ src/ipa/libipa/lsc_base.h | 53 ++++++++++++++++++++++ src/ipa/libipa/meson.build | 2 + src/ipa/rkisp1/algorithms/lsc.cpp | 28 ++++++------ src/ipa/rkisp1/algorithms/lsc.h | 29 ++---------- 5 files changed, 167 insertions(+), 40 deletions(-) diff --git a/src/ipa/libipa/lsc_base.cpp b/src/ipa/libipa/lsc_base.cpp new file mode 100644 index 000000000000..26197f4effe5 --- /dev/null +++ b/src/ipa/libipa/lsc_base.cpp @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + * + * Base classes and types for LSC algorithm implementations + */ + +#include "lsc_base.h" + +/** + * \file lsc_base.h + * \brief Base types and definitions for LscImplementation class hierarchy + */ + +namespace libcamera { + +namespace ipa { + +namespace lsc { + +/** + * \struct 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 + * + * \var Components::gr + * \brief The list of gains for the Green/Red colour component + * + * \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 + */ + +/** + * \typedef ComponentsMap + * \brief Associate a colour temperature to a LSC table + * + * An LSC table is generated during the tuning phase for a specific colour + * temperature, and a tuning file usually contains LSC tables generated for + * several different colour temperatures. + */ + +} /* namespace lsc */ + +/** + * \class LscImplementation + * \brief Pure virtual base class for LSC algorithm implementations + * + * Defines the interface for the LSC algorithm implementation. + */ + +/** + * \fn LscImplementation::~LscImplementation + * \brief Virtual class destructor + */ + +/** + * \fn LscImplementation::parseLscData + * \brief Parse \a tuningData + * \param[in] tuningData The tuning data + * + * \return 0 on success, a negative error number otherwise + */ + +/** + * \fn LscImplementation::sampleForCrop + * \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 + * \a cropRectangle. + * + * \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. For 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. + */ + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/lsc_base.h b/src/ipa/libipa/lsc_base.h new file mode 100644 index 000000000000..59b831e53fbb --- /dev/null +++ b/src/ipa/libipa/lsc_base.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + * + * Base classes and types for LSC algorithm implementations + */ + +#pragma once + +#include +#include +#include +#include + +#include + +#include + +#include "libcamera/internal/value_node.h" + +namespace libcamera { + +namespace ipa { + +namespace lsc { + +struct Components { + std::vector r; + std::vector gr; + std::vector gb; + std::vector b; +}; + +using ComponentsMap = std::map; + +} /* namespace lsc */ + +class LscImplementation +{ +public: + virtual ~LscImplementation() {} + + virtual int parseLscData(const ValueNode &tuningData) = 0; + + virtual lsc::ComponentsMap + sampleForCrop(const Rectangle &cropRectangle, + Span xSizes, + Span ySizes) = 0; +}; + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build index edf8eabd8b78..96611e90ee1e 100644 --- a/src/ipa/libipa/meson.build +++ b/src/ipa/libipa/meson.build @@ -14,6 +14,7 @@ libipa_headers = files([ 'fixedpoint.h', 'histogram.h', 'interpolator.h', + 'lsc_base.h', 'lsc_polynomial.h', 'lux.h', 'module.h', @@ -36,6 +37,7 @@ libipa_sources = files([ 'fixedpoint.cpp', 'histogram.cpp', 'interpolator.cpp', + 'lsc_base.cpp', 'lsc_polynomial.cpp', 'lux.cpp', 'module.cpp', diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index 38380aa839c6..af919f7429af 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -38,10 +38,10 @@ void interpolateVector(const std::vector &a, const std::vector &b, } template<> -void Interpolator:: - interpolate(const rkisp1::algorithms::LensShadingCorrection::Components &a, - const rkisp1::algorithms::LensShadingCorrection::Components &b, - rkisp1::algorithms::LensShadingCorrection::Components &dest, +void Interpolator:: + interpolate(const lsc::Components &a, + const lsc::Components &b, + lsc::Components &dest, double lambda) { interpolateVector(a.r, b.r, dest.r, lambda); @@ -80,7 +80,7 @@ public: int parseLscData(const ValueNode &sets) override; - LensShadingCorrection::ComponentsMap + lsc::ComponentsMap sampleForCrop(const Rectangle &cropRectangle, Span xSizes, Span ySizes) override; @@ -138,7 +138,7 @@ int LscPolynomialImpl::parseLscData(const ValueNode &sets) return 0; } -LensShadingCorrection::ComponentsMap +lsc::ComponentsMap LscPolynomialImpl::sampleForCrop(const Rectangle &cropRectangle, Span xSizes, Span ySizes) @@ -146,7 +146,7 @@ LscPolynomialImpl::sampleForCrop(const Rectangle &cropRectangle, std::vector xPos = sizesListToPositions(xSizes); std::vector yPos = sizesListToPositions(ySizes); - LensShadingCorrection::ComponentsMap components; + lsc::ComponentsMap components; for (const auto &[k, p] : lscData_) { components[k] = { @@ -226,7 +226,7 @@ class LscTableImpl : public LscImplementation public: int parseLscData(const ValueNode &sets) override; - LensShadingCorrection::ComponentsMap + lsc::ComponentsMap sampleForCrop([[maybe_unused]] const Rectangle &cropRectangle, [[maybe_unused]] Span xSizes, [[maybe_unused]] Span ySizes) override @@ -240,7 +240,7 @@ private: std::vector parseTable(const ValueNode &tuningData, const char *prop); - LensShadingCorrection::ComponentsMap lscData_; + lsc::ComponentsMap lscData_; }; int LscTableImpl::parseLscData(const ValueNode &sets) @@ -255,7 +255,7 @@ int LscTableImpl::parseLscData(const ValueNode &sets) return -EINVAL; } - LensShadingCorrection::Components components; + lsc::Components components; components.r = parseTable(set, "r"); components.gr = parseTable(set, "gr"); components.gb = parseTable(set, "gb"); @@ -435,8 +435,8 @@ int LensShadingCorrection::configure(IPAContext &context, } LOG(RkISP1Lsc, Debug) << "Sample LSC data for " << configInfo.analogCrop; - ComponentsMap shadingData = algo_->sampleForCrop(configInfo.analogCrop, - xSize_, ySize_); + lsc::ComponentsMap shadingData = algo_->sampleForCrop(configInfo.analogCrop, + xSize_, ySize_); sets_.setData(std::move(shadingData)); context.activeState.lsc.enabled = true; @@ -452,7 +452,7 @@ void LensShadingCorrection::setParameters(rkisp1_cif_isp_lsc_config &config) } void LensShadingCorrection::copyTable(rkisp1_cif_isp_lsc_config &config, - const Components &set) + 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]); @@ -518,7 +518,7 @@ void LensShadingCorrection::prepare([[maybe_unused]] IPAContext &context, setParameters(*config); - const Components &set = sets_.getInterpolated(quantizedCt); + const lsc::Components &set = sets_.getInterpolated(quantizedCt); copyTable(*config, set); lastAppliedCt_ = ct; diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h index 3463531f2ab2..b21c91c0d81f 100644 --- a/src/ipa/rkisp1/algorithms/lsc.h +++ b/src/ipa/rkisp1/algorithms/lsc.h @@ -11,6 +11,7 @@ #include #include "libipa/interpolator.h" +#include "libipa/lsc_base.h" #include "algorithm.h" @@ -18,20 +19,9 @@ namespace libcamera { namespace ipa::rkisp1::algorithms { -class LscImplementation; - class LensShadingCorrection : public Algorithm { public: - struct Components { - std::vector r; - std::vector gr; - std::vector gb; - std::vector b; - }; - - using ComponentsMap = std::map; - LensShadingCorrection(); ~LensShadingCorrection() = default; @@ -49,7 +39,7 @@ public: ControlList &metadata) override; private: void setParameters(rkisp1_cif_isp_lsc_config &config); - void copyTable(rkisp1_cif_isp_lsc_config &config, const Components &set0); + void copyTable(rkisp1_cif_isp_lsc_config &config, const lsc::Components &set0); std::vector xSize_; std::vector ySize_; @@ -61,23 +51,10 @@ private: unsigned int lastAppliedCt_; unsigned int lastAppliedQuantizedCt_; - ipa::Interpolator sets_; + ipa::Interpolator sets_; std::unique_ptr algo_; }; -class LscImplementation -{ -public: - virtual ~LscImplementation() {} - - virtual int parseLscData(const ValueNode &sets) = 0; - - virtual LensShadingCorrection::ComponentsMap - sampleForCrop(const Rectangle &cropRectangle, - Span xSizes, - Span ySizes) = 0; -}; - } /* namespace ipa::rkisp1::algorithms */ } /* namespace libcamera */ From patchwork Mon Jul 20 14:59:29 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27403 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 ADC6CC3301 for ; Mon, 20 Jul 2026 15:00:00 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 426C767E7A; Mon, 20 Jul 2026 17:00:00 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="cL2KVdYI"; 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 723F867E59 for ; Mon, 20 Jul 2026 16:59:47 +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 8C4BA1CE4; Mon, 20 Jul 2026 16:58:48 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559528; bh=iym+xPwKbdXJhKWKBEgCczMlRqIVu/lwQ9sWa5xwMiM=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=cL2KVdYIt30MwF0jOEgPhbi7x7dsYAW1Dz6uGTE0rxTepJ9XG+gdsi0w25vxc4O6p Wtpda0W7yeVcqSx8lqDkBlwZ25T4myBZOYlXHKFwnCI/i6/ZhG+RweAqbqB5MzAhj+ IpPVEZFzP3rI/GwMwa0JkZs8FZODwqluq793OzcA= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:29 +0200 Subject: [PATCH v6 11/31] ipa: libipa: lsc_polynomial: Fix polynomial parsing error MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-11-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 , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=1449; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=iym+xPwKbdXJhKWKBEgCczMlRqIVu/lwQ9sWa5xwMiM=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfVuoMw8MRDh/cnraXJOFEmNhkcJcRwMRPsk CBp7O1J3taJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal431QAKCRByNAaPFqFW PEy/D/4/AIUX6KFle4czQIDfm0NkpWcaMEwb3dYYERR+JPf6t8MlqWLwgO7U9zLNU3wVEO+V5Yv xkUPzV+Fo5xqmS/dC/NSjbut47I1UopGPIVmEk2tmEI8LBY7RgoWOUbh5Jf0O//UUpbrjxNXzRS Sy5p0ZvvYp+wYnCb4b1Ss59rG9GQSih/NCQR+znbEmimAqVypIJTlM3goAGgBOHdFHo2/PWLC+0 6EGyCtXPQezdBiJB3r0Nx8RIm3f60+j7OjL6KHK7Kdsyvk/BPLN3fW85l1JaDIUamwX4NJp5x+s UynqejYe9Hm4JmKumwhxUd/r32Kn/dAic17yOy2+0YVps5Z/1EWpLkaoCrffc1YRdizJoTwXis+ mURYwShrtNwL/HML82dhJkGCtC82GE/VCS83KT/SkmkLzU79Au68UeilQLAEyDYfAIQl/GrArtQ iPNK5xhnZa7TuEynjacaBmUAAwacncsEbTKUTUoiei8YD4q05K9uMvB8AU41ur+lNGQ2R4MDU3Y lixiz5KCMMbJX8PeoEVw9UD5ualni5nkWEgA69nL+FiY00bCvNhR8Go5zo+OsJLrp945VLrExij r2sz7gg1C6kilfRcjXL7mwM3zFbqSTs/S3Xwq+SNxZmgQArfWb6pt4dzXte/zrtJgFFsteM2hom AgV8pjcGXkMHnlw== 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" If any of the polynomial component is missing in the tuning file the ValueNode::Accessor specialization emits an error message but continues and dereferences a potentially invalid object. Fix it by returning std::nullopt if a polynomial component is missing. The caller already checks if the returned value is valid or not, causing the algorithm initialization to fail if the tuning file is invalid. Suggested-by: Milan Zamazal Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Reviewed-by: Stefan Klug --- src/ipa/libipa/lsc_polynomial.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h index d7d9ae42e360..a0d27c4b21d6 100644 --- a/src/ipa/libipa/lsc_polynomial.h +++ b/src/ipa/libipa/lsc_polynomial.h @@ -94,9 +94,11 @@ struct ValueNode::Accessor { std::optional k3 = obj["k3"].get(); std::optional k4 = obj["k4"].get(); - if (!(cx && cy && k0 && k1 && k2 && k3 && k4)) + if (!(cx && cy && k0 && k1 && k2 && k3 && k4)) { LOG(LscPolynomial, Error) << "Polynomial is missing a parameter"; + return std::nullopt; + } return ipa::LscPolynomial(*cx, *cy, *k0, *k1, *k2, *k3, *k4); } From patchwork Mon Jul 20 14:59:30 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27404 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 5A7F0C3306 for ; Mon, 20 Jul 2026 15:00:02 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 126DE67E80; Mon, 20 Jul 2026 17:00:01 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="YB2fttu5"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 1282C67E4C for ; Mon, 20 Jul 2026 16:59:48 +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 1E1091CE6; Mon, 20 Jul 2026 16:58:49 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559529; bh=yXwIPDDYuWnxiEQ7pfsAy2nzJEmWvfcuWcpTAAq3aio=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=YB2fttu5HIUPxwdsxreNg5vHR1shh5Sfsfkcmv4n0gsNLKt3yQ/Oqz2I/dKdKqxNi GqkxeVEH2HdRIwYspNNDtYbig4gYZ9j5LdqHmJBDpOt5rBRWZ4Nmqn70goEdShA9Ui C2QoeR93I5q7H9+KF1+KSFiCR62QmuW7hVfgFaLM= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:30 +0200 Subject: [PATCH v6 12/31] ipa: libipa: lsc_polynomial: Don't inline ValueNode accessor MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-12-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 , Laurent Pinchart , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=3022; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=gKG7oMvPdTnWPY3Xqu6aZDyF064c3RNYURQAJyWHzUw=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfVGg1sKi/VnboIm8dmzF/XpqZsProJZkwGP jWSmElYAKWJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal431QAKCRByNAaPFqFW PBjQEACw2aSQV1RQPkJwqlf16apbO+/HCb1FXNDhXQZSdHpbjtCfjmTUBj/rkKgfcVNXLpP3CB5 UfkQjLRLSP4pC0jc5GSMABUtxKOOajXuIG5lchG4NpPMZWrFlqIHQhgXdnQ4Sk7l4SOrTcsO2Xr PTMRIL2vuIeUbwbyn6wLxQYdAdnMmkXTl4xE06IOk/xiS9EhgnwpntrJekjYyQgS8qLb49TcKY2 XUD9yWKDPIzfG1fvkL9ED10eskOm++2EXiNgmphTxdTz9WtLlEAJN+2KTeUIch4fgtb8i64+jyR udn5ERLYvDJnfvHIiZZ6riJLBM1MVowyScw6xPOgo6ZB/W8TMt7f0fSMWNsafWAs6jK6Eo/7uFG D6jSaIrd5j1pEBFlnRLG3aL9iV52AblE+BJ3N4jOAL6vWQ8yDfUF18w642BxgLsWUyC4ItYLqde 98bKxy1KeKAUGd/X3TGJ05vc2MQ0NkZpIdW3J4uAT4wxod7oFIr2Jzrryymw5PZEZtfyZ5tTZyH +29ESPe+EnLLzaOOyLQFifHCdQpKA+eAUP7c35Bzbm5C5fQuXJu08mpH1fcb5dMOrwTW5YVPERy jHr3ZSQnjH/1p8yPqpvh/U9wQtDWJHR6n53tmu9Luii1FitACl5EONgLzBssEDLTNTh/aOu/XwF BYD0tCEMUw1QBtA== 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" From: Laurent Pinchart There's no need to inline the ValueNode::Accessor::get() function for LscPolynomial. Move it to from lsc_polynomial.h to lsc_polynomial.cpp. While at it, replace C++-style comments with C-style comments according to the libcamera coding style. Signed-off-by: Laurent Pinchart Reviewed-by: Kieran Bingham Reviewed-by: Jacopo Mondi Signed-off-by: Jacopo Mondi --- src/ipa/libipa/lsc_polynomial.cpp | 28 ++++++++++++++++++++++++++-- src/ipa/libipa/lsc_polynomial.h | 26 -------------------------- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/src/ipa/libipa/lsc_polynomial.cpp b/src/ipa/libipa/lsc_polynomial.cpp index f607d86c54c3..921af0b0bd13 100644 --- a/src/ipa/libipa/lsc_polynomial.cpp +++ b/src/ipa/libipa/lsc_polynomial.cpp @@ -77,5 +77,29 @@ namespace ipa { * \param size The size of the reference image */ -} // namespace ipa -} // namespace libcamera +} /* namespace ipa */ + +#ifndef __DOXYGEN__ +template<> +std::optional +ValueNode::Accessor::get(const ValueNode &obj) const +{ + std::optional cx = obj["cx"].get(); + std::optional cy = obj["cy"].get(); + std::optional k0 = obj["k0"].get(); + std::optional k1 = obj["k1"].get(); + std::optional k2 = obj["k2"].get(); + std::optional k3 = obj["k3"].get(); + std::optional k4 = obj["k4"].get(); + + if (!(cx && cy && k0 && k1 && k2 && k3 && k4)) { + LOG(LscPolynomial, Error) + << "Polynomial is missing a parameter"; + return std::nullopt; + } + + return ipa::LscPolynomial(*cx, *cy, *k0, *k1, *k2, *k3, *k4); +} +#endif /* __DOXYGEN__ */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h index a0d27c4b21d6..d52ec86946b1 100644 --- a/src/ipa/libipa/lsc_polynomial.h +++ b/src/ipa/libipa/lsc_polynomial.h @@ -80,30 +80,4 @@ private: } /* namespace ipa */ -#ifndef __DOXYGEN__ - -template<> -struct ValueNode::Accessor { - std::optional get(const ValueNode &obj) const - { - std::optional cx = obj["cx"].get(); - std::optional cy = obj["cy"].get(); - std::optional k0 = obj["k0"].get(); - std::optional k1 = obj["k1"].get(); - std::optional k2 = obj["k2"].get(); - std::optional k3 = obj["k3"].get(); - std::optional k4 = obj["k4"].get(); - - if (!(cx && cy && k0 && k1 && k2 && k3 && k4)) { - LOG(LscPolynomial, Error) - << "Polynomial is missing a parameter"; - return std::nullopt; - } - - return ipa::LscPolynomial(*cx, *cy, *k0, *k1, *k2, *k3, *k4); - } -}; - -#endif - } /* namespace libcamera */ From patchwork Mon Jul 20 14:59:31 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27405 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 95AF0C3308 for ; Mon, 20 Jul 2026 15:00:04 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9575567E70; Mon, 20 Jul 2026 17:00:03 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="aBJGVv1P"; 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 90EE167E42 for ; Mon, 20 Jul 2026 16:59:48 +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 AE1C81CE7; Mon, 20 Jul 2026 16:58:49 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559530; bh=k/Zxb+dsBZMFvAdkrJe72HrvcKz5+1mdvrGEcvsl2bk=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=aBJGVv1P7Lu27rNwDJMJaBymNnYcd/QYYAuCj80NWpaFtTiPbfJFxqnA17X27aDKe VqkD0wYq4uvTyCmRBOA3fTv6rHAE6kcRB4Co3sqdECXnicpBvVm2Dxhnie/P8E/OTU kEyWd5vBE6GMaw3cmRhf5jGROwMTrC33Og/q0m+g= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:31 +0200 Subject: [PATCH v6 13/31] ipa: libipa: lsc_polynomial: Rename LscPolynomial MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-13-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 , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=6305; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=k/Zxb+dsBZMFvAdkrJe72HrvcKz5+1mdvrGEcvsl2bk=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfV2IEhtmRzL2M+2Ev2B8M/MWP6xfxCeLq8W i+7YNsRGxaJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal431QAKCRByNAaPFqFW PLXOD/9UBuAE9PkHmJ19uXgGrIiJFNj5n5rOrV1gXTo3y8GwB4levSFNJogeIyLeGjs0nL+/bSs AfIkCoVIxeoUTLQTa7gx9rQKhzNV/WwdZVkK2PGw7HYY990xC995QjV2rHnuwmFZBFnCv5+HyuW Ac8WLOMbKIOxSNOrVVY1DzsbuCjgbsxR7BrsPaIWjOpfd+UxaoxY6oGcN28BPViKuTqKmRAco4W ELYvvAA7G2IGonRw7ZH5uWwKnr4w1BgVOII6WA9H+YDzxSYMn89Nc0Sg4kVqDGB+ce00SDhVvW6 +oXW3rE+Yxvc/W4Yxmh2xJ/dC5wUAJXLiYS8p5B6pdjuUBj7+6YEaSq6xoUIPgOk51+n7LKFa54 9ajwpkePeYUYwsiBLf/0V3N3/dVdS0ju/Y1Jrd1Gsq25B6Tx6QabJ5CdODslMZ79GpdGMinvBks 9Qqd6suC3b1WwraRSJihZ2/WYUE53jMoCn6ZVQWvvvmq3ReJii1ceOHCl/ViyN+KZ2GJQK/ivD9 AcUPNYI/ktt5iFKGBeE4DSluNRkKNbUCFSLolrxN28XnTQZF1jLQnB5SHiw0OgYGf/LXgy1ldVL bKawzmEreQ8EV/lD0smTiP1jHZk8P9lQLLKgzw0dXBQ1ShXaenU+NYQWfMBnXAjk24WqdJOOlER pnsZ9vq3UKZlGCw== 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" In order to prepare to introduce the LscPolynomial class move the existing implementation of the polynomial representation LscPolynomial to lsc::Polynomial. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Reviewed-by: Stefan Klug --- src/ipa/libipa/lsc_polynomial.cpp | 20 ++++++++++++-------- src/ipa/libipa/lsc_polynomial.h | 12 ++++++++---- src/ipa/rkisp1/algorithms/lsc.cpp | 22 +++++++++++----------- 3 files changed, 31 insertions(+), 23 deletions(-) diff --git a/src/ipa/libipa/lsc_polynomial.cpp b/src/ipa/libipa/lsc_polynomial.cpp index 921af0b0bd13..b93063bf8bc7 100644 --- a/src/ipa/libipa/lsc_polynomial.cpp +++ b/src/ipa/libipa/lsc_polynomial.cpp @@ -20,8 +20,10 @@ LOG_DEFINE_CATEGORY(LscPolynomial) namespace ipa { +namespace lsc { + /** - * \class LscPolynomial + * \class Polynomial * \brief Class for handling even polynomials used in lens shading correction * * Shading artifacts of camera lenses can be modeled using even radial @@ -31,7 +33,7 @@ namespace ipa { */ /** - * \fn LscPolynomial::LscPolynomial(double cx = 0.0, double cy = 0.0, double k0 = 0.0, + * \fn Polynomial::Polynomial(double cx = 0.0, double cy = 0.0, double k0 = 0.0, double k1 = 0.0, double k2 = 0.0, double k3 = 0.0, double k4 = 0.0) * \brief Construct a polynomial using the given coefficients @@ -45,7 +47,7 @@ namespace ipa { */ /** - * \fn LscPolynomial::sampleAtNormalizedPixelPos(double x, double y) + * \fn Polynomial::sampleAtNormalizedPixelPos(double x, double y) * \brief Sample the polynomial at the given normalized pixel position * * This functions samples the polynomial at the given pixel position divided by @@ -57,7 +59,7 @@ namespace ipa { */ /** - * \fn LscPolynomial::getM() + * \fn Polynomial::getM() * \brief Get the value m as described in the dng specification * * Returns m according to dng spec. m represents the Euclidean distance @@ -68,7 +70,7 @@ namespace ipa { */ /** - * \fn LscPolynomial::setReferenceImageSize(const Size &size) + * \fn Polynomial::setReferenceImageSize(const Size &size) * \brief Set the reference image size * * Set the reference image size that is used for subsequent calls to getM() and @@ -77,12 +79,14 @@ namespace ipa { * \param size The size of the reference image */ +} /* namespace lsc */ + } /* namespace ipa */ #ifndef __DOXYGEN__ template<> -std::optional -ValueNode::Accessor::get(const ValueNode &obj) const +std::optional +ValueNode::Accessor::get(const ValueNode &obj) const { std::optional cx = obj["cx"].get(); std::optional cy = obj["cy"].get(); @@ -98,7 +102,7 @@ ValueNode::Accessor::get(const ValueNode &obj) const return std::nullopt; } - return ipa::LscPolynomial(*cx, *cy, *k0, *k1, *k2, *k3, *k4); + return ipa::lsc::Polynomial(*cx, *cy, *k0, *k1, *k2, *k3, *k4); } #endif /* __DOXYGEN__ */ diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h index d52ec86946b1..3be33edfe7d8 100644 --- a/src/ipa/libipa/lsc_polynomial.h +++ b/src/ipa/libipa/lsc_polynomial.h @@ -24,12 +24,14 @@ LOG_DECLARE_CATEGORY(LscPolynomial) namespace ipa { -class LscPolynomial +namespace lsc { + +class Polynomial { public: - LscPolynomial(double cx = 0.0, double cy = 0.0, double k0 = 0.0, - double k1 = 0.0, double k2 = 0.0, double k3 = 0.0, - double k4 = 0.0) + Polynomial(double cx = 0.0, double cy = 0.0, double k0 = 0.0, + double k1 = 0.0, double k2 = 0.0, double k3 = 0.0, + double k4 = 0.0) : cx_(cx), cy_(cy), cnx_(0), cny_(0), coefficients_({ k0, k1, k2, k3, k4 }) { @@ -78,6 +80,8 @@ private: Size imageSize_; }; +} /* namespace lsc */ + } /* namespace ipa */ } /* namespace libcamera */ diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index af919f7429af..84d2b1d3bdac 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -65,10 +65,10 @@ class LscPolynomialImpl : public LscImplementation { private: struct PolynomialComponents { - LscPolynomial pr; - LscPolynomial pgr; - LscPolynomial pgb; - LscPolynomial pb; + lsc::Polynomial pr; + lsc::Polynomial pgr; + lsc::Polynomial pgb; + lsc::Polynomial pb; }; using PolynomialComponentsMap = std::map; @@ -87,7 +87,7 @@ public: private: std::vector sizesListToPositions(Span sizes); - std::vector samplePolynomial(const LscPolynomial &poly, + std::vector samplePolynomial(const lsc::Polynomial &poly, Span xPositions, Span yPositions, const Rectangle &cropRectangle); @@ -98,7 +98,7 @@ private: int LscPolynomialImpl::parseLscData(const ValueNode &sets) { for (const auto &set : sets.asList()) { - std::optional pr, pgr, pgb, pb; + std::optional pr, pgr, pgb, pb; uint32_t ct = set["ct"].get(0); if (lscData_.count(ct)) { @@ -108,10 +108,10 @@ int LscPolynomialImpl::parseLscData(const ValueNode &sets) return -EINVAL; } - pr = set["r"].get(); - pgr = set["gr"].get(); - pgb = set["gb"].get(); - pb = set["b"].get(); + pr = set["r"].get(); + pgr = set["gr"].get(); + pgb = set["gb"].get(); + pb = set["b"].get(); if (!(pr || pgr || pgb || pb)) { LOG(RkISP1Lsc, Error) @@ -161,7 +161,7 @@ LscPolynomialImpl::sampleForCrop(const Rectangle &cropRectangle, } std::vector -LscPolynomialImpl::samplePolynomial(const LscPolynomial &poly, +LscPolynomialImpl::samplePolynomial(const lsc::Polynomial &poly, Span xPositions, Span yPositions, const Rectangle &cropRectangle) From patchwork Mon Jul 20 14:59:32 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27406 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 A6847C330A for ; Mon, 20 Jul 2026 15:00:05 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D458367E90; Mon, 20 Jul 2026 17:00:04 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="eyNM48PK"; 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 1981767E61 for ; Mon, 20 Jul 2026 16:59:49 +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 366D51D17; Mon, 20 Jul 2026 16:58:50 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559530; bh=+BLekKS2khj7QSEHhQsHfSrQMgwm055rzcXMP/0cFSk=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=eyNM48PKRwWL867hYSmVv2WRPrrLKRO7TMCRhzFQeWCZO1bRMj4f3eJA0wmddu7uB +oCmAQ0ZHxzWqplFiu/CtQKiusdTgt8Tns2Z/aaExllfcA+n5AotcLz2tdezQjQ5pY rOZupXpn4+lD1TMXtfCf8PvOQg/xPMDUC90w26E0= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:32 +0200 Subject: [PATCH v6 14/31] ipa: libipa: lsc_polynomial: Do not inline functions MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-14-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 , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=4087; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=+BLekKS2khj7QSEHhQsHfSrQMgwm055rzcXMP/0cFSk=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfWAcgqvVKPJhQnKbu+c6WO0AEjEvPtdpXCz qDwybG1xmyJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal431gAKCRByNAaPFqFW PKxcD/0dWVz6TzE/qZUJnx2UWjQVMrcRL8nzAXy4wCR1WwQj6ySLSgHdRvhXA/kZH8VzAu1bgUw 4/mImkDsr6kt007Wi5G9BSQSTApsi30lXIEDk9tOV+lDpP8n6WOh+kGL2fyMaTjeGF+7U6V3wZU PxDka77AXMT2R77PO+qaDOXaMWYbY8we4zAZlGadEPCEu6zaVxKBj3AnyDIo/qKKqcut8B4qHls e5kFKKk9yUtHXW5pXByQX52f/n+VfsKXGRG/+VrUSGYET4MHZgkq1zOoBAWG/XuHwQ21cpCXPWQ KTsiO7UrdXklgVrYeGho0mNv56EWDUhQcaxwZgLW1zFZl11C/b2ig3YPRq8ZnE4YkhOpvLg7Dib HkOVXNcXo2B14c4Vtp2ACJNgGF8S6Pb+zUVbL5RRtdGFPX0Ezz0A+uIfUyXzonD4lUJeef7qZ27 n5zcZ5ixk40mZN/CFep0sCBE2bLMLIWfPWUY1DoMht4pc5vdG7Af1Y8NTu4SVRhUVTECtgng9GM YaC9tWXSxFyv2lPIVrT5XTLYVTGBbD5NM1wKBnsILEbA7fZ9ngQChR9X8wMRWqx6TnKJjIrQy1o 9vge8iMh9vE2COr5V44yR43Unxi2wNSvCcL1qSFUvW8zFg9ymApQP1hDa0VapqNeKR3mBCjXyPj dClWf8VALMVezuQ== 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" There is no reason to inline the Polynomial class implementation. Move functions implementation to the corresponding .cpp file. While at it remove a rougue empty line between class members definitions. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Reviewed-by: Stefan Klug --- src/ipa/libipa/lsc_polynomial.cpp | 34 +++++++++++++++++++++++++++++++--- src/ipa/libipa/lsc_polynomial.h | 36 +++--------------------------------- 2 files changed, 34 insertions(+), 36 deletions(-) diff --git a/src/ipa/libipa/lsc_polynomial.cpp b/src/ipa/libipa/lsc_polynomial.cpp index b93063bf8bc7..fb6f959e5a09 100644 --- a/src/ipa/libipa/lsc_polynomial.cpp +++ b/src/ipa/libipa/lsc_polynomial.cpp @@ -47,7 +47,6 @@ namespace lsc { */ /** - * \fn Polynomial::sampleAtNormalizedPixelPos(double x, double y) * \brief Sample the polynomial at the given normalized pixel position * * This functions samples the polynomial at the given pixel position divided by @@ -57,9 +56,20 @@ namespace lsc { * \param y y position in normalized coordinates * \return The sampled value */ +double Polynomial::sampleAtNormalizedPixelPos(double x, double y) const +{ + double dx = x - cnx_; + double dy = y - cny_; + double r = sqrt(dx * dx + dy * dy); + double res = 1.0; + + for (unsigned int i = 0; i < coefficients_.size(); i++) + res += coefficients_[i] * std::pow(r, (i + 1) * 2); + + return res; +} /** - * \fn Polynomial::getM() * \brief Get the value m as described in the dng specification * * Returns m according to dng spec. m represents the Euclidean distance @@ -68,9 +78,17 @@ namespace lsc { * * \return The sampled value */ +double Polynomial::getM() const +{ + double cpx = imageSize_.width * cx_; + double cpy = imageSize_.height * cy_; + double mx = std::max(cpx, std::fabs(imageSize_.width - cpx)); + double my = std::max(cpy, std::fabs(imageSize_.height - cpy)); + + return sqrt(mx * mx + my * my); +} /** - * \fn Polynomial::setReferenceImageSize(const Size &size) * \brief Set the reference image size * * Set the reference image size that is used for subsequent calls to getM() and @@ -78,6 +96,16 @@ namespace lsc { * * \param size The size of the reference image */ +void Polynomial::setReferenceImageSize(const Size &size) +{ + assert(!size.isNull()); + imageSize_ = size; + + /* Calculate normalized centers */ + double m = getM(); + cnx_ = (size.width * cx_) / m; + cny_ = (size.height * cy_) / m; +} } /* namespace lsc */ diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h index 3be33edfe7d8..2caf46d2d759 100644 --- a/src/ipa/libipa/lsc_polynomial.h +++ b/src/ipa/libipa/lsc_polynomial.h @@ -37,38 +37,9 @@ public: { } - double sampleAtNormalizedPixelPos(double x, double y) const - { - double dx = x - cnx_; - double dy = y - cny_; - double r = sqrt(dx * dx + dy * dy); - double res = 1.0; - for (unsigned int i = 0; i < coefficients_.size(); i++) { - res += coefficients_[i] * std::pow(r, (i + 1) * 2); - } - return res; - } - - double getM() const - { - double cpx = imageSize_.width * cx_; - double cpy = imageSize_.height * cy_; - double mx = std::max(cpx, std::fabs(imageSize_.width - cpx)); - double my = std::max(cpy, std::fabs(imageSize_.height - cpy)); - - return sqrt(mx * mx + my * my); - } - - void setReferenceImageSize(const Size &size) - { - assert(!size.isNull()); - imageSize_ = size; - - /* Calculate normalized centers */ - double m = getM(); - cnx_ = (size.width * cx_) / m; - cny_ = (size.height * cy_) / m; - } + double sampleAtNormalizedPixelPos(double x, double y) const; + double getM() const; + void setReferenceImageSize(const Size &size); private: double cx_; @@ -76,7 +47,6 @@ private: double cnx_; double cny_; std::array coefficients_; - Size imageSize_; }; From patchwork Mon Jul 20 14:59:33 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27407 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 6F057C330F for ; Mon, 20 Jul 2026 15:00:07 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 6DD9867E8F; Mon, 20 Jul 2026 17:00:06 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="jGRv3qdj"; 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 A81F867E53 for ; Mon, 20 Jul 2026 16:59:49 +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 BF8AFD77; Mon, 20 Jul 2026 16:58:50 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559531; bh=70FZhClw7mS5xy+Y1CktDRCjvCls/+hWNvF/+aVuNwI=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=jGRv3qdj592CDLzi4Ip6gAd4pboB2WnYhCs7WVSpr14VlURbs6W2Kz+8l3MpJfpZC OSwULOm7QyUwhZqthKEIXLG95A5URcbZs9JFRL8V9ggmaE3WLicLPFlwLZjiMEkpB+ AKO8ED0FlvR8yni0qYKylSFAxu65eNGcZQaUHzCI= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:33 +0200 Subject: [PATCH v6 15/31] ipa: libipa: lsc: Move Interpolator to lsc_base MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-15-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 , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=3386; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=70FZhClw7mS5xy+Y1CktDRCjvCls/+hWNvF/+aVuNwI=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfWAnfNGFZ3LDa1rj3DWApf5JBtYxi++0AbX 6gJeLXK/GaJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal431gAKCRByNAaPFqFW PGzxD/9MLw4no7f0xCvFei8H5bMFphVb8aKHFtehnxJ7bBri3ur2HWwbNauw+XM21IhUab5K/Kl IR0ybVVC6oaW4u11odJE7vn+zcNsC8HoHmISejglZ2E/dnCDBA3V7Y0TP9QpgYRMdwSoxu39TwD iVl0RXJOLfXtaR7MeW0WGwLoc5fTnghsjDusA2ZixmApvWrTZqVJtsg4YO4sKeqbDgJ1ucJhWLX YD9luNRu/iKGBA0rOyHisDBkKvsBdwQA7yaUMHzjkH20lqZZiHPV2h95nnQi/KVU7fYTRj46XBB MHJIEtvUCaqcGjEeomjKWDFkAHIWmDC94ZZ6/RpGsh252/ih2iO91a5ljz+/V1yhNyR0/gDSKoU XphK1cLgvMA1JzA1MdBzWXsulaMEkfQPR0rT6PRUZQJl1TR5JHhGtxVenymVbsd4zRMkyfRj6bt RckWZd67CBZQC42t0/0XQ6vRu2rVuIVoLQsjd9DQeri24zGIh7t6pPSD8MSomIrBKp5O3RlzY0+ xQ60kX1KQTpY7V6ZBNCbr0xHgAapDFpmyj7RrFy6/liR9Aaxru1RJsHflT/IbcJd9T3DEAZnq/9 Vvxo+EnYcIMCUIUEHL06E/bFb1JXnHV772CormQYMGC96+9j2BWUT78sJfbHKoa+CkXOqm16YDz jkKFhXLjuJpN/Ug== 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 Interpolator: specialization from the RkISP1 IPA to the lsc_base header. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Reviewed-by: Stefan Klug --- src/ipa/libipa/lsc_base.cpp | 15 +++++++++++++++ src/ipa/libipa/lsc_base.h | 21 +++++++++++++++++++++ src/ipa/rkisp1/algorithms/lsc.cpp | 27 --------------------------- 3 files changed, 36 insertions(+), 27 deletions(-) diff --git a/src/ipa/libipa/lsc_base.cpp b/src/ipa/libipa/lsc_base.cpp index 26197f4effe5..b50460394925 100644 --- a/src/ipa/libipa/lsc_base.cpp +++ b/src/ipa/libipa/lsc_base.cpp @@ -49,6 +49,21 @@ namespace lsc { } /* namespace lsc */ +#ifndef __DOXYGEN__ +template<> +void Interpolator:: + interpolate(const lsc::Components &a, + const lsc::Components &b, + 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); +} +#endif + /** * \class LscImplementation * \brief Pure virtual base class for LSC algorithm implementations diff --git a/src/ipa/libipa/lsc_base.h b/src/ipa/libipa/lsc_base.h index 59b831e53fbb..ca7ad6e4051c 100644 --- a/src/ipa/libipa/lsc_base.h +++ b/src/ipa/libipa/lsc_base.h @@ -18,6 +18,8 @@ #include "libcamera/internal/value_node.h" +#include "interpolator.h" + namespace libcamera { namespace ipa { @@ -35,6 +37,25 @@ 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__ */ + class LscImplementation { public: diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index 84d2b1d3bdac..8d92ee89cdaf 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -25,33 +25,6 @@ namespace libcamera { -namespace ipa { - -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) -{ - 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); -} - -} /* namespace ipa */ - namespace ipa::rkisp1::algorithms { From patchwork Mon Jul 20 14:59:34 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27408 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 72359C3333 for ; Mon, 20 Jul 2026 15:00:08 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8BCBF67E7D; Mon, 20 Jul 2026 17:00:07 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="nmBVIHq9"; 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 4092A67E70 for ; Mon, 20 Jul 2026 16:59:50 +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 4EC7112EE; Mon, 20 Jul 2026 16:58:51 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559531; bh=zV7xjj4E3N+lrt+zYVjWqNnwet3OpaLv/0Vj2kD9DjQ=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=nmBVIHq9KXjv0gLX3xzypTPDhn+O8jxEUPmEmzegST6WP24U987YU2xntQINrY5Xb Z00HchSFHHxAjkWJntcsyyLTjq4AISnzMcYX8QFnVygv9M2eatd5lsd1cPujKxIQGx EoAOfO2uzVI8GrKoXzerfaCTaozSZQXdB4n6NuKk= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:34 +0200 Subject: [PATCH v6 16/31] ipa: rkisp1: lsc: Remove rogue empty line MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-16-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 , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=658; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=zV7xjj4E3N+lrt+zYVjWqNnwet3OpaLv/0Vj2kD9DjQ=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfWOl3S2gln6swY8sJgc30WPjH2aO7Hu6oIb 2w9REDZayyJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal431gAKCRByNAaPFqFW PG7rD/9hG04mgPopgcGXZfiCALLW9H2vuu+D9nNia67V3UOKPxFdTBczYP2zBejX59093sYPMTk 0tlMZPToiZoS3NY5cHLWjF3EpgMq+Bo7utpfN5p3EyfU90KNqjA84p7MhE7SS7WpCX0y8JUHlt4 g9NWNP43FVKWHgMy4mZ8tgrWpV2vxOepEwgEbTWBEQVYrpNuZ+RwWULH+mXHCARd+7QBxQFuUV+ RuFXG4asSJEv3ERgCzfwb+nRyPZtBHmrvruPJuWUdPvcukTKZ5M27qYm6upoj22afgUFRW+YzTq pNtcxej9C/khCyY/t/uslJ0MNt92ZyEl1skvsOEKMdGBqp+duYJ7csROSm6YPuVYcZFMhORGur+ +G0wmziaSk4xU3lawAPcQDJSdBmbssXvDlwnWvS2bjaFe2aKTR3vFpBpiaoEe/KwS+nGISeFoRh 2Xo05viuEeVQ6d+5pd2Y7ARc2p+j+Gedvru2Qp3BgFEUVA5f8eYUQG3cN9zAvhX8OflHFJ7S8QQ y5jPsGlxprAME5S+PzgDcuDfa74noym1PQ2XHyH6i9+kXHqIvRMRKYCPl80aj6Pt9wb1j4CBcUi GnQFPM9P1Q/pGnpBDNo5IRoENX/oHMqquEkFIfmeesJC2Uohn5fqdEgSHWFpr7PteuMrK4MN1zF 6NIfntGbjTuCn8w== 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" Remove an additional empty line. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Reviewed-by: Stefan Klug --- src/ipa/rkisp1/algorithms/lsc.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index 8d92ee89cdaf..839525f16746 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -27,7 +27,6 @@ namespace libcamera { namespace ipa::rkisp1::algorithms { - LOG_DEFINE_CATEGORY(RkISP1Lsc) namespace { From patchwork Mon Jul 20 14:59:35 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27409 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 8FFA0C328C for ; Mon, 20 Jul 2026 15:00:09 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9FA6667E8E; Mon, 20 Jul 2026 17:00:08 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="mmMps6PT"; 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 CEC1267E72 for ; Mon, 20 Jul 2026 16:59:50 +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 D50C01D1F; Mon, 20 Jul 2026 16:58:51 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559532; bh=QL4noIprmQqcb0tBXch8xNe2gPnkldHuhJwnwMEQRBg=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=mmMps6PT1uKusjNP69LBSacjaJWmFjGKM1WqA2yE8KLrZXZ2wF2k22e12//vK3RwC kBjUns5JeU9ZstWx6aW12hioTQ+GxFxOxtsIfHRsvUiLWroc50QbNMoN3Wirann0l7 O3Dl5eqXnk1IsHUHqZDoFOU3R71reFj0bv9f3Wa0= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:35 +0200 Subject: [PATCH v6 17/31] ipa: libipa: lsc_polynomial: Move LscPolynomial from RkISP1 MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-17-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 , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=14540; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=QL4noIprmQqcb0tBXch8xNe2gPnkldHuhJwnwMEQRBg=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfXfKOuZEAd6A6LAVgyTfSqx39P7yyAx6qlL BOzVAjdBvWJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal431wAKCRByNAaPFqFW PPg0D/40qq5bv46FI14w1Od9G5ibfH/N+DavsNjS8Y1MIdadONT05UUMMysQYQZx+yfj8lEzrJ+ 8t4S7n/oAyuBx8ozH2Uyx+NdYt09jqLgJexECr92n84J+CyTrmxVDQufDejzpMSzXXxUsGnfP2S E+7QTVFhPsD3nbDfSYuKQ4r5AiATM3bT5hYaWrq1Xys3LoAO24Yl0F4eISWD3WgAVQKzlD/vOa9 TxuT+q3zfxpBAdUuTDPgXwINfNgZEv9KfJw4YMnbiDtebYB+rA0+Ybsht73RSStmmodQYS2407s i1C3sp/oVaXcYMd7K5Ik7CBABrmf4/u5Y4qa49qJofaCh7Yixxk64M3uDdjWkdZRcNoNL+LkKlV BgbLcXDUpEvcmgs6QwiKXxqsl2+BnFz7pYIbwn7jQMK/7hVh7PwTrUP1vlIZrHhdPFYQ8PI72WC Mq98kQ45EQhOB65gxpuwsH7wTtxbNFmjnSye2KC8YfrC4gDtIf75A29XhSpqeojIn4FQ0HZ1ZK1 Pa6h7H5U3H+1WniWRCrKWjM4L8nKs6zviivoqObaFgwHsiEDd1hrLwaP15vJvfhXw2s0gSiJRzU i4oM6L2h1hsGk/+mTEc+M0nHaYcP8eNAXA+/QjCRJyUJ/hwAeDNj2TfiiqI3F5ZwcHqAbEyLCOZ /2khUQBamMtPleA== 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 documentation has been added. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Reviewed-by: Stefan Klug --- 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 fb6f959e5a09..f095a5a92024 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 a 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 839525f16746..b161798763cb 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" From patchwork Mon Jul 20 14:59: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: 27411 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 31ED1C3336 for ; Mon, 20 Jul 2026 15:00:12 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8F93967E9A; Mon, 20 Jul 2026 17:00:11 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="brqJszTM"; 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 6B22667E5A for ; Mon, 20 Jul 2026 16:59:51 +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 7D3141E2A; Mon, 20 Jul 2026 16:58:52 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559532; bh=kS1f7u038Mr8e0+4qDg1fZRTIWr3MRw5ErME9c2vHVM=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=brqJszTMD1mioBxfoebbNFIahnCEW4AGImQZ+wQBpndplFJzxgOQr23nE8F7hBFZI kgdlpiLJ9DpE2qZ++EfIipC97/yRDpkKgE5/y4U8Fa7GRMujEtB5cxnt+jtozwKqWc s9CXj0smqPifZA5Kj2LcaeKtKC7Ayve25QOfB+Qc= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:36 +0200 Subject: [PATCH v6 18/31] ipa: libipa: lsc_table: Move LscTable from RkISP1 MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-18-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 , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=8370; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=kS1f7u038Mr8e0+4qDg1fZRTIWr3MRw5ErME9c2vHVM=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfXZJmhyez7HEJNSRgCSQ5MK33xITwixWERu 0hcB0nfUoOJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal431wAKCRByNAaPFqFW PNuGEAC+CDmhJGWDJ5hJgG0ye0FU6NCv5NTiM+Kk5A5GM80cy1+0/8IhDFue1xjEB2J+z+87/NO HGJ/fGTKMgExqSOSEcSYb4c1bMncpnAcUdny1/8Pa9TqARRZCkpB91XUMMT7GQQSHkbvH1B5NKw C6YPMSNKLfQ8R37glxInqRAne+QJmaLsTubj1/WKtlKdR7Bi/22OmRhNOPkkPJehZ4rc//NFYUQ IygYAaL0FvNbBouC8uI71qJ2h0sz11L5clECsTLDmNegqogfDdMFOZZQjh18uFhYBd/RHzj5nbl r7trIy3c78jeozqWi/HREPJXDi96+8ZJ1xl1n3fScV3c2t6d4xrt94MJOH+Ee9SnfShhVBu0uSC Fkr04KNzGa9KkXEKr+r3XxS/0iW0gjK720kbm3NfyX89HBhudsM5Rnar5yehyXLbWaj2VnYMFBG CEpS4TAkM34yS574bkVpHPJiLEkXEOFdmPB5KxTvSOHcsJXpPzWE5VhM7sEqSLh2B3szyZIPwzp wzJkqpsqBLsBlR5n1Sl9ZJgmxAU4sn3Rw3N61idq0APBB0VtKOj/Klmz4fnTJYC5Ik2inPjABzz AfLirtSB64FB9UsEV/bVY3DRdfjNhUJPnvMUFm6mG3xrA5zXcy/grxXznJa0bMO9f63Z+MNBFr2 nMIS/9E+rlDBWSw== 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 LscTableImpl class to libipa from RkISP1. Rename the class from LscTableImpl to a simpler LscTable. The class is copied verbatim, only documntation has been added. The implementation still contains RkISP1 specific data which will be removed once the class is generalized. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Reviewed-by: Stefan Klug --- src/ipa/libipa/lsc_table.cpp | 95 +++++++++++++++++++++++++++++++++++++++ src/ipa/libipa/lsc_table.h | 50 +++++++++++++++++++++ src/ipa/libipa/meson.build | 2 + src/ipa/rkisp1/algorithms/lsc.cpp | 81 +-------------------------------- 4 files changed, 149 insertions(+), 79 deletions(-) diff --git a/src/ipa/libipa/lsc_table.cpp b/src/ipa/libipa/lsc_table.cpp new file mode 100644 index 000000000000..47dff156963a --- /dev/null +++ b/src/ipa/libipa/lsc_table.cpp @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * Table-based LSC implementation + */ + +#include "lsc_table.h" + +/* \todo Remove RkISP1 from libipa. */ +#include "linux/rkisp1-config.h" + +namespace libcamera { + +LOG_DEFINE_CATEGORY(LscTable) + +namespace ipa { + +/** + * \class LscTable + * \brief Table based LSC algorithm implementation + * + * Table based LSC algorithm implementation. The LSCTable class implements LSC + * support using tabular LSC data. + * + * \sa LscImplementation + */ + +/** + * \brief Parse tabular LSC data + * \param[in] sets The tuning file content + * + * 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) +{ + 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; + } + + lsc::Components components; + components.r = parseTable(set, "r"); + components.gr = parseTable(set, "gr"); + components.gb = parseTable(set, "gb"); + components.b = parseTable(set, "b"); + + if (components.r.empty() || components.gr.empty() || + components.gb.empty() || components.b.empty()) { + LOG(LscTable, Error) + << "Set for color temperature " << ct + << " is missing tables"; + return -EINVAL; + } + + lscData_.emplace(ct, std::move(components)); + } + + if (lscData_.empty()) { + LOG(LscTable, Error) << "Failed to load any sets"; + return -EINVAL; + } + + return 0; +} + +std::vector LscTable::parseTable(const ValueNode &tuningData, + const char *prop) +{ + static constexpr unsigned int kLscNumSamples = + RKISP1_CIF_ISP_LSC_SAMPLES_MAX * RKISP1_CIF_ISP_LSC_SAMPLES_MAX; + + std::vector table = + tuningData[prop].get>().value_or(utils::defopt); + if (table.size() != kLscNumSamples) { + LOG(LscTable, Error) + << "Invalid '" << prop << "' values: expected " + << kLscNumSamples + << " elements, got " << table.size(); + return {}; + } + + return table; +} + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/lsc_table.h b/src/ipa/libipa/lsc_table.h new file mode 100644 index 000000000000..0983b78c3f6b --- /dev/null +++ b/src/ipa/libipa/lsc_table.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * Table-based Lsc implementation + */ +#pragma once + +#include + +#include +#include + +#include + +#include "libcamera/internal/value_node.h" + +#include "lsc_base.h" + +namespace libcamera { + +LOG_DECLARE_CATEGORY(LscTable) + +namespace ipa { + +class LscTable : public LscImplementation +{ +public: + int parseLscData(const ValueNode &sets) override; + + lsc::ComponentsMap + sampleForCrop([[maybe_unused]] const Rectangle &cropRectangle, + [[maybe_unused]] Span xSizes, + [[maybe_unused]] Span ySizes) override + { + LOG(LscTable, Warning) + << "Tabular LSC data doesn't support resampling"; + return lscData_; + } + +private: + std::vector parseTable(const ValueNode &tuningData, + const char *prop); + + lsc::ComponentsMap lscData_; +}; + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build index 96611e90ee1e..95d398d99fd6 100644 --- a/src/ipa/libipa/meson.build +++ b/src/ipa/libipa/meson.build @@ -16,6 +16,7 @@ libipa_headers = files([ 'interpolator.h', 'lsc_base.h', 'lsc_polynomial.h', + 'lsc_table.h', 'lux.h', 'module.h', 'pwl.h', @@ -39,6 +40,7 @@ libipa_sources = files([ 'interpolator.cpp', 'lsc_base.cpp', 'lsc_polynomial.cpp', + 'lsc_table.cpp', 'lux.cpp', 'module.cpp', 'pwl.cpp', diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index b161798763cb..f32c46084dd1 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -17,6 +17,7 @@ #include "libcamera/internal/value_node.h" #include "libipa/lsc_polynomial.h" +#include "libipa/lsc_table.h" #include "linux/rkisp1-config.h" /** @@ -33,84 +34,6 @@ namespace { constexpr int kColourTemperatureQuantization = 10; -class LscTableImpl : public LscImplementation -{ -public: - int parseLscData(const ValueNode &sets) override; - - lsc::ComponentsMap - sampleForCrop([[maybe_unused]] const Rectangle &cropRectangle, - [[maybe_unused]] Span xSizes, - [[maybe_unused]] Span ySizes) override - { - LOG(RkISP1Lsc, Warning) - << "Tabular LSC data doesn't support resampling"; - return lscData_; - } - -private: - std::vector parseTable(const ValueNode &tuningData, - const char *prop); - - lsc::ComponentsMap lscData_; -}; - -int LscTableImpl::parseLscData(const ValueNode &sets) -{ - for (const auto &set : sets.asList()) { - uint32_t ct = set["ct"].get(0); - - if (lscData_.count(ct)) { - LOG(RkISP1Lsc, Error) - << "Multiple sets found for color temperature " - << ct; - return -EINVAL; - } - - lsc::Components components; - components.r = parseTable(set, "r"); - components.gr = parseTable(set, "gr"); - components.gb = parseTable(set, "gb"); - components.b = parseTable(set, "b"); - - if (components.r.empty() || components.gr.empty() || - components.gb.empty() || components.b.empty()) { - LOG(RkISP1Lsc, Error) - << "Set for color temperature " << ct - << " is missing tables"; - return -EINVAL; - } - - lscData_.emplace(ct, std::move(components)); - } - - if (lscData_.empty()) { - LOG(RkISP1Lsc, Error) << "Failed to load any sets"; - return -EINVAL; - } - - return 0; -} - -std::vector LscTableImpl::parseTable(const ValueNode &tuningData, - const char *prop) -{ - static constexpr unsigned int kLscNumSamples = - RKISP1_CIF_ISP_LSC_SAMPLES_MAX * RKISP1_CIF_ISP_LSC_SAMPLES_MAX; - - std::vector table = - tuningData[prop].get>().value_or(utils::defopt); - if (table.size() != kLscNumSamples) { - LOG(RkISP1Lsc, Error) - << "Invalid '" << prop << "' values: expected " - << kLscNumSamples - << " elements, got " << table.size(); - return {}; - } - - return table; -} - std::vector parseSizes(const ValueNode &tuningData, const char *prop) { @@ -190,7 +113,7 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, std::string type = tuningData["type"].get("table"); if (type == "table") { LOG(RkISP1Lsc, Debug) << "Loading tabular LSC data."; - algo_ = std::make_unique(); + algo_ = std::make_unique(); ret = algo_->parseLscData(sets); } else if (type == "polynomial") { LOG(RkISP1Lsc, Debug) << "Loading polynomial LSC data."; From patchwork Mon Jul 20 14:59:37 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27410 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 5D44BC3334 for ; Mon, 20 Jul 2026 15:00:10 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D572067E9E; Mon, 20 Jul 2026 17:00:09 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="EY0IdF4C"; 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 DFC8567E6A for ; Mon, 20 Jul 2026 16:59:51 +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 14688258D; Mon, 20 Jul 2026 16:58:53 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559533; bh=WYRREtMMsGnWekfpemz4LN4fohagCWU8KMsfNpvYOME=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=EY0IdF4CathMbXPKFTRI7MCV6KmDKYN8ZbX0DW9XrwHpBP4xG94/ax7z5ozwgvnKe 38sDJMjU5OJbV2bOoIN/lXEtaFCg2Odsz1HG+bT8VMS5nGPKYeFXyHwQUIbSMADKvq gfLOuVobEG8j33IscU2t1nOaZcjQhCxgSp4vTx7U= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:37 +0200 Subject: [PATCH v6 19/31] ipa: libipa: lsc_base: Add sampleForCrop documentation MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-19-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=1459; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=WYRREtMMsGnWekfpemz4LN4fohagCWU8KMsfNpvYOME=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfY/Sfb4nPV6mPVmSsANnJEBHtj50MKPuN2a SQZiBpxx0iJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal432AAKCRByNAaPFqFW PPETD/9H0mJiFKwvWD3105IXCK1Gx0f2DcPVmz26e0wW/5o+PlHCBDaNoelIfh5YnI5Bn4sU6Hl OuhRJ2D8Q/Cw4Nf7tYZztWYPltlATxLsMvhLtSCpE1HRK+hriOIdH1L2/ZlRL9+nRsFHSBmWjEQ TQItwQgL8sh8dCaOpBuVMRe6lfqrWOs+oP9EtrYUMyaIG8IMNOLDkaLMW1UoL4duGJ3M4T3BBKC lTWMm6Qt8+bIH7FRTq1n1nti9ys3srv96hWRvmu0nGNK3MeD/0Luhi5sJc5lJbeY1vm7IPksY0g QXquHDKr80K46M1vd4Li8jbu+6JnslXaENB8E/8kGUoULSogBlwGQq4Hne/grO2nqLKY0sSs6Iy Z7ckEBzXtIuLgcS/AXiQ257bvLWF90ueo1O9y46FLbw+5H+aHGLB6pqTD1awBH8Ac7zhUGnQ5/Q fGqJNsMhxPQEgH+lELke1v5ryk7WOddlCGcNPb+NSdV3qjpYQ81XtNj5aS9KFJZTj4/mE7dI55b c/YsuMxlJ798dITHJ05/akDlcnwPazcSeZPBWk9/avUZD/U8Ij9Aslmt3czUuJocsMHcE3B2DfZ hg4IBhugsATpmpeFCHlYEfUm1l/PTsJyZes1i5semjBQo2ywL0mQszf8Cgrt6djkpjR+0l9vE47 oGgxeJgCIQ0XrbQ== 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" Now that we have two in-tree implementations of LscImplementation detail how the two perform lsc gain re-sampling. Signed-off-by: Jacopo Mondi --- src/ipa/libipa/lsc_base.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/ipa/libipa/lsc_base.cpp b/src/ipa/libipa/lsc_base.cpp index b50460394925..1015cdc7cf4c 100644 --- a/src/ipa/libipa/lsc_base.cpp +++ b/src/ipa/libipa/lsc_base.cpp @@ -103,6 +103,21 @@ void Interpolator:: * 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 tables are expressed in two formats: + * - A list of gain values (LscTable) + * - A radial polynomial (LscPolynomial) + * + * Table-based LSC tables are generated using an image at a fixed resolution and + * can't at the moment be re-sampled when a different resolution is used for a + * streaming session. Re-sampling a grid LSC table will return the same table + * as loaded from the tuning file. + * + * \todo: Implement grid based re-sampling + * + * Polynomial configurations are more flexible and 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. */ } /* namespace ipa */ From patchwork Mon Jul 20 14:59:38 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27412 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 1F579C3303 for ; Mon, 20 Jul 2026 15:00:13 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A789E67E97; Mon, 20 Jul 2026 17:00:12 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Tf1uhYpX"; 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 73E6167E44 for ; Mon, 20 Jul 2026 16:59:52 +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 8738529D1; Mon, 20 Jul 2026 16:58:53 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559533; bh=yBgKpKiqgzf/n4L5tBrq7FuAKFUf5UkNAiwow3alboE=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=Tf1uhYpXp/Cg+QG/+oPkYYQJxBn/TDiyktDsfHbZic9cg4dQRpXUYSQhrGJGp2NVx y3hIYSNI+3tBKiwn0Y+RoDRpENHdUKaLN0tmepSrNyF+sdyMsIEFTZOth822kCPt1u VIklPG4KC7UYhNadHLrujyTIsutdxLEXwxhxlq+c= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:38 +0200 Subject: [PATCH v6 20/31] ipa: libipa: lsc: Introduce LscAlgorithm MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-20-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 , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=11537; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=yBgKpKiqgzf/n4L5tBrq7FuAKFUf5UkNAiwow3alboE=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfY3GC5Pf3Z0buBh9WwCehA2W9zixETasyY1 Nf0dbe3O3iJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal432AAKCRByNAaPFqFW PLCXEADAeUQrA3OXVOMKzC89EYHqx61a+qg9WHudHeRuJMiWj6nYtZfsrOZvEvwX2/VShOcDY6G B7x0yj39zVojUigUxnVK2i4e8xIdi5WR8l1/j5LRFZmox2sV8Y0+MZM0oV/YoW4/j8GBM8bBqi1 r31x/7N6Cl1MsFIL97rdKDcEThszFYx8yOMEwCPkzJNoNk1fsMtDZBHl4KQrXILrbRsdzm8WeAd s76Ow79zTgSuC+PDa/YxWLBE6ZzvUPUuaMgrlD43wYB2yt/1zkpXXonmuoCbivWDzpwMYBbF2bQ tAcrJLFHxvrBYX8eZQNPDtI4U4yFUZZSLhGKwcrDtji4s75jDzIzYVPpOQyQ8o+8vWiZ8AwqE6Z zTj0kCwUZy52DRStNNTBMsMHsvX08sa60mnJRQWln+LrnDImQjIZNlul812QwMJqeF6q+OQxguL S0qmmHEDrxOv6mV7yEyWc9FltOe5uZfRwJskGKt5NZTMee8jHJwsjDvFFkMf7uFRHpOhdRi7PVw iVIb0h1AlxzlWFi1dkX616HmOTOqgz9+5wIWdnZV0NwUblAwiQs/62ySenSpmmWD5OLY4R2fST4 73R/XWX8kUtN9ZX4MLnuhzQFgQuZITr4nSMqx8RRj205+3vdSZogCzV6EC+CfIx2/I0lVS+ia1P ya9kNimqD2+6vOg== 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" Introduce the lsc.h and lsc.cpp files with the definition of the Lsc algorithm active state, frame context and the LscAlgorithm class. The LscAlgorithm class uses two backends (LscPolynomial and LscTable) to perform tuning file parsing and re-scaling of the LSC gains. Signed-off-by: Jacopo Mondi Reviewed-by: Stefan Klug Reviewed-by: Kieran Bingham --- src/ipa/libipa/lsc.cpp | 253 +++++++++++++++++++++++++++++++++++++++++++++ src/ipa/libipa/lsc.h | 71 +++++++++++++ src/ipa/libipa/meson.build | 2 + 3 files changed, 326 insertions(+) diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp new file mode 100644 index 000000000000..dd69c7a80b17 --- /dev/null +++ b/src/ipa/libipa/lsc.cpp @@ -0,0 +1,253 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + * + * libIPA Lsc algorithms + */ + +#include "lsc.h" + +#include + +#include + +#include "lsc_polynomial.h" +#include "lsc_table.h" + +/** + * \file lsc.h + * \brief libipa LSC algorithm + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(Lsc) + +namespace ipa { + +namespace lsc { + +/** + * \struct ActiveState + * \brief The LSC active state + * + * \var ActiveState::enabled + * \brief Boolean flag for the LscAlgorithm enable status + */ + +/** + * \struct FrameContext + * \brief The LSC frame context + * + * \var FrameContext::enabled + * \brief Boolean flag for the LscAlgorithm enable status + * + * \var FrameContext::update + * \brief Boolean flag for the LscAlgorithm updated status + */ + +} /* namespace lsc */ + +/** + * \class LscAlgorithm + * \brief libIPA LSC algorithm implementation + * + * Due to the optical characteristics of the lens, the light intensity received + * by the sensor is not uniform. The Lens Shading Correction algorithm applies + * multipliers to all pixels to compensate for the lens shading effect. + * + * The LscAlgorithm implements the libipa Lens Shading Correction algorithm + * using an implementation of the LscImplementation interface. + * + * This class provides support for parsing the tuning file content and + * generates tables indexed by colour temperature to store per-colour-channel + * gains for the IPA algorithm to be able to program the LSC engine. + * + * The init() function parses the tuning file and loads the gain tables either + * in tabular form (LscTable) or as radial polynomials (LscPolynomial). The gain + * tables are indexed by colour temperature with per-colour-components vectors + * of gain values or polynomial coefficients. + * + * At LscAlgorithm::configure() time the LSC tables are re-sampled on the + * sensor's crop rectangle in use to adapt them to the configuration in use for + * a streaming session. Polynomial LSC tables support re-sampling and can be + * applied to any sensor configuration. Grid-based LSC tables cannot currently + * be re-sampled and the configuration as parsed from the tuning file is used + * for all sensor configurations providing best-effort results. + * + * \todo Implement grid based re-sampling + * + * When the IPA algorithms wants to get access to the (re-sampled) tables to + * program its LSC engine, it uses LscAlgorithm::interpolateComponents() to get + * an LSC table interpolated by the LscAlgorithm class for the specified colour + * temperature. If the algorithm wants to access the non-interpolated tables it + * can retrieve them using LscAlgorithm::getComponents(). + */ + +/** + * \param[in] tuningData The tuning data + * \param[in] sensorSize The physical sensor size + * \param[in] controls The IPA list of supported controls + * + * 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) +{ + polynomial_ = false; + + std::string type = tuningData["type"].get("table"); + if (type == "table") { + impl_ = std::make_unique(); + LOG(Lsc, Debug) << "Using table-based Lsc"; + } else if (type == "polynomial") { + /* + * \todo Most likely the reference frame should be native_size. + * Let's wait how the internal discussions progress. + */ + impl_ = std::make_unique(sensorSize); + polynomial_ = true; + LOG(Lsc, Debug) << "Using polynomial Lsc"; + } else { + LOG(Lsc, Error) << "Unsupported Lsc algorithm '" + << type << "'"; + return -EINVAL; + } + + const ValueNode &yamlSets = tuningData["sets"]; + if (!yamlSets.isList()) { + LOG(Lsc, Error) << "'sets' parameter not found in tuning file"; + return -EINVAL; + } + + int ret = impl_->parseLscData(yamlSets); + if (ret) + return ret; + + controls[&controls::LensShadingCorrectionEnable] = + ControlInfo(false, true, true); + + return 0; +} + +/** + * \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 + * \param[in] yPos List of vertical positions of the LSC grid nodes + * + * Re-sample the LSC data for an \a analogCrop. + * + * LSC tables are generated at tuning time using a known sensor configuration. + * When a new streaming session is started, it might use a different sensor + * configuration for which the LSC tables need to be adjusted to. + * + * This function re-generates the LSC tables to adapt them to a new sensor + * configuration, specifically it re-samples the LSC data for a new \a + * analogCrop on a grid specified by \a xPos and \a yPos. Re-sampling of + * LSC data is currently supported by polynomial-based LSC tables. + * + * \sa LscImplementation::sampleForCrop + * + * \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 + * \param[in] state The lsc active state + * \param[in] context The lsc frame context + * \param[in] controls The list of controls associated with a Request + * + * 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) +{ + const auto &lscEnable = controls.get(controls::LensShadingCorrectionEnable); + if (lscEnable && *lscEnable != state.enabled) { + state.enabled = *lscEnable; + + LOG(Lsc, Debug) + << (state.enabled ? "Enabling" : "Disabling") << " Lsc"; + + context.update = true; + } + + context.enabled = state.enabled; +} + +/** + * \brief Populate the list of lsc metadata + * \param[in] context The lsc frame context + * \param[in] metadata The list of metadata + * + * 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) +{ + 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 + * + * 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. + * + * 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. + * + * Calling this function is only valid after LscAlgorithm::configure() has been + * called. An empty components list is returned otherwise. + * + * \return The LSC gains table interpolated for temperature \a ct + */ + +/** + * \fn LscAlgorithm::getComponents + * + * 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. + * + * \return The map of LSC gains tables per-colour-temperature + */ + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/lsc.h b/src/ipa/libipa/lsc.h new file mode 100644 index 000000000000..9a51d32c3464 --- /dev/null +++ b/src/ipa/libipa/lsc.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + * + * libIPA Lsc algorithm + */ + +#pragma once + +#include +#include + +#include +#include + +#include "libcamera/internal/value_node.h" + +#include "interpolator.h" +#include "lsc_base.h" + +namespace libcamera { + +namespace ipa { + +namespace lsc { + +struct ActiveState { + bool enabled; +}; + +struct FrameContext { + bool enabled; + bool update; +}; + +} /* namespace lsc */ + +class LscAlgorithm +{ +public: + int init(const ValueNode &tuningData, const Size &sensorSize, + ControlInfoMap::Map &controls); + + 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) + { + return sets_.getInterpolated(ct); + } + + const lsc::ComponentsMap getComponents() + { + return lscData_; + } + +private: + std::unique_ptr impl_; + Interpolator sets_; + lsc::ComponentsMap lscData_; + bool polynomial_; +}; + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build index 95d398d99fd6..42f89d9820fa 100644 --- a/src/ipa/libipa/meson.build +++ b/src/ipa/libipa/meson.build @@ -14,6 +14,7 @@ libipa_headers = files([ 'fixedpoint.h', 'histogram.h', 'interpolator.h', + 'lsc.h', 'lsc_base.h', 'lsc_polynomial.h', 'lsc_table.h', @@ -38,6 +39,7 @@ libipa_sources = files([ 'fixedpoint.cpp', 'histogram.cpp', 'interpolator.cpp', + 'lsc.cpp', 'lsc_base.cpp', 'lsc_polynomial.cpp', 'lsc_table.cpp', From patchwork Mon Jul 20 14:59:39 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27413 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 540F8C3338 for ; Mon, 20 Jul 2026 15:00:14 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8DF1D67EAE; Mon, 20 Jul 2026 17:00:13 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="R0fhzBBu"; 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 0998C67E5F for ; Mon, 20 Jul 2026 16:59:53 +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 20A3C29D9; Mon, 20 Jul 2026 16:58:54 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559534; bh=521bH3mFquNCYSVRsyGnYMHj+jq/CjYM/hPvQbe+/a8=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=R0fhzBBudYErzU8/byWSn110zDjDIuZcybzlVye549t1ELjdrbbt1lsfyeKypT3z0 Q8CNODKGR91CnxUw9sEwX2myVonZbwDJ6y/E7md1/KsHBRIH1Ve8YjFYKIhq1CtlUD R3H9VUbiw6UC9ufmt9gQ4mT5bRpNWEnrfBBxdx6A= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:39 +0200 Subject: [PATCH v6 21/31] ipa: libipa: lsc: Move sizesListToPositions() back to rkisp1 MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-21-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 , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=9723; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=521bH3mFquNCYSVRsyGnYMHj+jq/CjYM/hPvQbe+/a8=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfYepxn1aaW66QxXYRLmQkaUPDpn/KHOgk3G 0SdL46lS6eJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal432AAKCRByNAaPFqFW PBf9D/0RkRwWDnb7eexkoJuMvqpOiqrnGrQkQaEvw1sz63R8jRmlK43Vcx1qbzEYpUn1s9X0WFz Sb5e+yeQDdqfxAhG8ZvqmoYQw8fXxhyOEZYzwit56Mt8HotWmkq/jLnPfGj62u1QaW1GTL0SoaS kSNhmrqlFAHT1/SgNEopT4MjxWtyc3Yol075a/3SuqsQAhf7dkT4Py/dNUl+7btoLfsUnTgdS1u CO2Gvw3AA6Mre/CPTqragKp6CZiQVVW/dhtx5SQxGND1qjPe7MzbH7X31kiVuIH6xRYF2HBSUK6 9KX237p6WpjYdBzGv4IBtOEb+jeJFgatQZz5kfSKnF9+iDmFqngST2G0lGKDuD2v03/BkWWioWO MwrZ3etBAFW7xMCzGWP/FnMMfnjjRHy5HH0wuYLiOXZ3CacOgRUJTP9wcmFEULxqxgoGXlKV97M B09SaEs9cuUFYu/bJ3kKPuiWvOT7gFEC/KlJh4g7FlQnGxLcCWstN25q6mnYYLDF9FtNuhh84Hb 6UNPDn6kOFkkydXA5dHepAKZGroF5qomL92x1HMjNNMvHXop304UQ7RGPNbvXwzT/3pEL1vbQ/B ujqRrnYx0KQWD+wcKPCVcUy/LZE9ryFbeVBYCa9XxgRdARGi3aiPbP8v7l3F7s6abXTY4SfaRMs upLGq9d6qVXwHew== 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 LscPolynomial::sampleForCrop() function is designed to receive a list of segment sizes and translates it to a list of grid vertexes internally with the sizesListToPositions() function. As the sizesListToPositions() implementation is RkISP1-specific, move it back to the RkISP1 IPA as a static helper and change the prototype of the sampleForCrop() function to accept a vector of grid vertexes instead of a span of segment sizes. Signed-off-by: Jacopo Mondi Reviewed-by: Stefan Klug Reviewed-by: Kieran Bingham --- src/ipa/libipa/lsc_base.cpp | 6 +++--- src/ipa/libipa/lsc_base.h | 3 +-- src/ipa/libipa/lsc_polynomial.cpp | 38 ++++---------------------------------- src/ipa/libipa/lsc_polynomial.h | 4 +--- src/ipa/libipa/lsc_table.h | 4 ++-- src/ipa/rkisp1/algorithms/lsc.cpp | 31 ++++++++++++++++++++++++++++++- src/ipa/rkisp1/algorithms/lsc.h | 2 ++ 7 files changed, 43 insertions(+), 45 deletions(-) diff --git a/src/ipa/libipa/lsc_base.cpp b/src/ipa/libipa/lsc_base.cpp index 1015cdc7cf4c..b3671376a12a 100644 --- a/src/ipa/libipa/lsc_base.cpp +++ b/src/ipa/libipa/lsc_base.cpp @@ -88,8 +88,8 @@ void Interpolator:: * \fn LscImplementation::sampleForCrop * \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 + * \param[in] xPos List of horizontal positions of the LSC grid nodes + * \param[in] yPos 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 @@ -98,7 +98,7 @@ void Interpolator:: * \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 + * \a xPos and \a yPos represent the position of the grid nodes vertexes in * the [0, 1] interval. For 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 diff --git a/src/ipa/libipa/lsc_base.h b/src/ipa/libipa/lsc_base.h index ca7ad6e4051c..1bdb7d5c8ed9 100644 --- a/src/ipa/libipa/lsc_base.h +++ b/src/ipa/libipa/lsc_base.h @@ -65,8 +65,7 @@ public: virtual lsc::ComponentsMap sampleForCrop(const Rectangle &cropRectangle, - Span xSizes, - Span ySizes) = 0; + std::vector xPos, std::vector yPos) = 0; }; } /* namespace ipa */ diff --git a/src/ipa/libipa/lsc_polynomial.cpp b/src/ipa/libipa/lsc_polynomial.cpp index f095a5a92024..682e2f14fe09 100644 --- a/src/ipa/libipa/lsc_polynomial.cpp +++ b/src/ipa/libipa/lsc_polynomial.cpp @@ -184,8 +184,8 @@ int LscPolynomial::parseLscData(const ValueNode &sets) /** * \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 + * \param[in] xPos List of horizontal positions of the LSC grid nodes + * \param[in] yPos 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. @@ -197,7 +197,7 @@ int LscPolynomial::parseLscData(const ValueNode &sets) * \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 + * \a xPos and \a yPos 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 @@ -205,11 +205,8 @@ int LscPolynomial::parseLscData(const ValueNode &sets) */ lsc::ComponentsMap LscPolynomial::sampleForCrop(const Rectangle &cropRectangle, - Span xSizes, - Span ySizes) + std::vector xPos, std::vector yPos) { - std::vector xPos = sizesListToPositions(xSizes); - std::vector yPos = sizesListToPositions(ySizes); lsc::ComponentsMap components; @@ -259,33 +256,6 @@ LscPolynomial::samplePolynomial(const lsc::Polynomial &poly, 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 17c2c34499f0..d2276ecc6f6b 100644 --- a/src/ipa/libipa/lsc_polynomial.h +++ b/src/ipa/libipa/lsc_polynomial.h @@ -71,11 +71,9 @@ public: lsc::ComponentsMap sampleForCrop(const Rectangle &cropRectangle, - Span xSizes, - Span ySizes) override; + std::vector xPos, std::vector yPos) override; private: - std::vector sizesListToPositions(Span sizes); std::vector samplePolynomial(const lsc::Polynomial &poly, Span xPositions, Span yPositions, diff --git a/src/ipa/libipa/lsc_table.h b/src/ipa/libipa/lsc_table.h index 0983b78c3f6b..67c7464ea5b6 100644 --- a/src/ipa/libipa/lsc_table.h +++ b/src/ipa/libipa/lsc_table.h @@ -30,8 +30,8 @@ public: lsc::ComponentsMap sampleForCrop([[maybe_unused]] const Rectangle &cropRectangle, - [[maybe_unused]] Span xSizes, - [[maybe_unused]] Span ySizes) override + [[maybe_unused]] std::vector xPos, + [[maybe_unused]] std::vector yPos) override { LOG(LscTable, Warning) << "Tabular LSC data doesn't support resampling"; diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index f32c46084dd1..f52516aa6ce7 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -64,6 +64,32 @@ std::vector parseSizes(const ValueNode &tuningData, return sizes; } +/* + * 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 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; +} + unsigned int quantize(unsigned int value, unsigned int step) { return std::lround(value / static_cast(step)) * step; @@ -100,6 +126,9 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, if (xSize_.empty() || ySize_.empty()) return -EINVAL; + xPos_ = sizesListToPositions(xSize_); + yPos_ = sizesListToPositions(ySize_); + /* Get all defined sets to apply. */ const ValueNode &sets = tuningData["sets"]; if (!sets.isList()) { @@ -171,7 +200,7 @@ int LensShadingCorrection::configure(IPAContext &context, LOG(RkISP1Lsc, Debug) << "Sample LSC data for " << configInfo.analogCrop; lsc::ComponentsMap shadingData = algo_->sampleForCrop(configInfo.analogCrop, - xSize_, ySize_); + xPos_, yPos_); sets_.setData(std::move(shadingData)); context.activeState.lsc.enabled = true; diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h index 2a428293cb2e..b9fbc7df20ca 100644 --- a/src/ipa/rkisp1/algorithms/lsc.h +++ b/src/ipa/rkisp1/algorithms/lsc.h @@ -42,6 +42,8 @@ private: std::vector xSize_; std::vector ySize_; + std::vector xPos_; + std::vector yPos_; uint16_t xGrad_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE]; uint16_t yGrad_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE]; uint16_t xSizes_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE]; From patchwork Mon Jul 20 14:59:40 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27415 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 CE061C333A for ; Mon, 20 Jul 2026 15:00:16 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 371B867EB0; Mon, 20 Jul 2026 17:00:16 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="OOvNjfKT"; 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 874EF67E6C for ; Mon, 20 Jul 2026 16:59:53 +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 A21A82E64; Mon, 20 Jul 2026 16:58:54 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559535; bh=mGI35EtijXnwR3eGBLAvuPQc9uD9Epa859QBaNr5PvA=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=OOvNjfKTSZBMtFqqiq/SWKafifxSWUSegU05TUiAVZPFA7HMcCQoxW97z0aD/4IOk MmK0NTNxcUhX9tQkwO0g7dkqumTOc5c+VMK3jsZdHfxvVEEaSCU9zWtmRX9vQXWPzp G9DJDPYKJLxvPyXPEYLvhPThRzP01DWcd0BnPW+Y= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:40 +0200 Subject: [PATCH v6 22/31] ipa: rkisp1: lsc: Port to use LscAlgorithm MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-22-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 , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=5909; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=mGI35EtijXnwR3eGBLAvuPQc9uD9Epa859QBaNr5PvA=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfZwPOgzEKIPrWwvcYlZG7zTpXzZr+1wUQWg 7WZUnUpr0yJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal432QAKCRByNAaPFqFW PMquD/9p7RJWeD703l0Wwu5XWzjbY8PlUT3RH5KXJJ6GyJ3Bn9surwLJMISyfSoV1Ek33D5GUq6 x2E0wq2nTZ87Ff9u4tw9NhrdICzF6jNTO16V3frJs2OCbOKprdIyiQjux3p7W7xRZ8AQsQ+KGt0 rQLD5iCcqWTTKZFTBRim5CmN3knfsRc2gkd9J6s/QHZfq4gPRqy8EAYruswYIp45yNeFIQCKC25 2xGAYIkEm+ED0SELWVsRhMKIGQfiFsbjjS/gjhY1CLeYd5jBhAjqVJd4wGVKr3K9LDxao7uatId iHYA9yzUMeUuf6KGNlZXezdTZWP8B6/4fopxzahhCMN2awzSke9YINH7lMgCrgY4WgbL5Ghkguu kdQ9ETWsYIicAWSOe9GLmm4hmLjcpuNTMzl97Dl71q/CwdvtA2LFimrOYmNtAqA8ICxvZoDEVPq Tvv2ab4wM2xtTJePMr8ILrR//e/L2JLknXA4oFCOaNlHudO0/2Vvm4mKClRJnU4CVAojQtQ7MF8 OAo69zx9tHKcJzno6wBaOAIUIaNqMY9+Xjn/OKFkpsb4R6+CQPaG5CYHF6l7aLMi4+3xUBrW+0H r3Xnp3X/WLMv3uY0Ed9wnBGA8JPe6ZwVjbsOIgVvNj4Z+xc6v9uAed50f3ifnUF9kbU3Rblf4j8 OW88eUppCWTG+gg== 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" Port the RkISP1 LSC algorithm to use the LscAlgorithm class by composition. Signed-off-by: Jacopo Mondi Reviewed-by: Stefan Klug Reviewed-by: Kieran Bingham --- src/ipa/rkisp1/algorithms/lsc.cpp | 72 +++++---------------------------------- src/ipa/rkisp1/algorithms/lsc.h | 13 +++---- src/ipa/rkisp1/ipa_context.h | 10 ++---- 3 files changed, 18 insertions(+), 77 deletions(-) diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index f52516aa6ce7..b0d6649be4fd 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -14,12 +14,6 @@ #include #include -#include "libcamera/internal/value_node.h" - -#include "libipa/lsc_polynomial.h" -#include "libipa/lsc_table.h" -#include "linux/rkisp1-config.h" - /** * \file lsc.h */ @@ -129,42 +123,8 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, xPos_ = sizesListToPositions(xSize_); yPos_ = sizesListToPositions(ySize_); - /* Get all defined sets to apply. */ - const ValueNode &sets = tuningData["sets"]; - if (!sets.isList()) { - LOG(RkISP1Lsc, Error) - << "'sets' parameter not found in tuning file"; - return -EINVAL; - } - - int ret; - - std::string type = tuningData["type"].get("table"); - if (type == "table") { - LOG(RkISP1Lsc, Debug) << "Loading tabular LSC data."; - algo_ = std::make_unique(); - ret = algo_->parseLscData(sets); - } else if (type == "polynomial") { - LOG(RkISP1Lsc, Debug) << "Loading polynomial LSC data."; - /* - * \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); - ret = algo_->parseLscData(sets); - } else { - LOG(RkISP1Lsc, Error) << "Unsupported LSC data type '" - << type << "'"; - ret = -EINVAL; - } - - if (ret) - return ret; - - context.ctrlMap[&controls::LensShadingCorrectionEnable] = - ControlInfo(false, true, true); - - return 0; + return lscAlgo_.init(tuningData, context.sensorInfo.activeAreaSize, + context.ctrlMap); } /** @@ -198,13 +158,8 @@ int LensShadingCorrection::configure(IPAContext &context, yGrad_[i] = std::round(32768 / ySizes_[i]); } - LOG(RkISP1Lsc, Debug) << "Sample LSC data for " << configInfo.analogCrop; - lsc::ComponentsMap shadingData = algo_->sampleForCrop(configInfo.analogCrop, - xPos_, yPos_); - sets_.setData(std::move(shadingData)); - - context.activeState.lsc.enabled = true; - return 0; + return lscAlgo_.configure(context.activeState.lsc, configInfo.analogCrop, + xPos_, yPos_); } void LensShadingCorrection::setParameters(rkisp1_cif_isp_lsc_config &config) @@ -232,19 +187,8 @@ void LensShadingCorrection::queueRequest(IPAContext &context, IPAFrameContext &frameContext, const ControlList &controls) { - auto &lsc = context.activeState.lsc; - - const auto &lscEnable = controls.get(controls::LensShadingCorrectionEnable); - if (lscEnable && *lscEnable != lsc.enabled) { - lsc.enabled = *lscEnable; - - LOG(RkISP1Lsc, Debug) - << (lsc.enabled ? "Enabling" : "Disabling") << " Lsc"; - - frameContext.lsc.update = true; - } - - frameContext.lsc.enabled = lsc.enabled; + lscAlgo_.queueRequest(context.activeState.lsc, frameContext.lsc, + controls); } /** @@ -282,7 +226,7 @@ void LensShadingCorrection::prepare([[maybe_unused]] IPAContext &context, setParameters(*config); - const lsc::Components &set = sets_.getInterpolated(quantizedCt); + const lsc::Components &set = lscAlgo_.interpolateComponents(quantizedCt); copyTable(*config, set); lastAppliedCt_ = ct; @@ -302,7 +246,7 @@ void LensShadingCorrection::process([[maybe_unused]] IPAContext &context, [[maybe_unused]] const rkisp1_stat_buffer *stats, ControlList &metadata) { - metadata.set(controls::LensShadingCorrectionEnable, frameContext.lsc.enabled); + lscAlgo_.process(frameContext.lsc, metadata); } REGISTER_IPA_ALGORITHM(LensShadingCorrection, "LensShadingCorrection") diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h index b9fbc7df20ca..63f0887f0838 100644 --- a/src/ipa/rkisp1/algorithms/lsc.h +++ b/src/ipa/rkisp1/algorithms/lsc.h @@ -7,10 +7,13 @@ #pragma once -#include +#include -#include "libipa/interpolator.h" -#include "libipa/lsc_base.h" +#include + +#include "libcamera/internal/value_node.h" + +#include "libipa/lsc.h" #include "algorithm.h" @@ -52,9 +55,7 @@ private: unsigned int lastAppliedCt_; unsigned int lastAppliedQuantizedCt_; - ipa::Interpolator sets_; - - std::unique_ptr algo_; + LscAlgorithm lscAlgo_; }; } /* namespace ipa::rkisp1::algorithms */ diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h index 6698ccfd77cf..7b4346c54ed2 100644 --- a/src/ipa/rkisp1/ipa_context.h +++ b/src/ipa/rkisp1/ipa_context.h @@ -30,6 +30,7 @@ #include "libipa/ccm.h" #include "libipa/fc_queue.h" #include "libipa/fixedpoint.h" +#include "libipa/lsc.h" namespace libcamera { @@ -141,9 +142,7 @@ struct IPAActiveState { double strength; } wdr; - struct { - bool enabled; - } lsc; + ipa::lsc::ActiveState lsc; }; struct IPAFrameContext : public FrameContext { @@ -217,10 +216,7 @@ struct IPAFrameContext : public FrameContext { double gain; } wdr; - struct { - bool enabled; - bool update; - } lsc; + ipa::lsc::FrameContext lsc; }; struct IPAContext { From patchwork Mon Jul 20 14:59:41 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27414 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 B07C9C3339 for ; Mon, 20 Jul 2026 15:00:15 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1EADE67EAC; Mon, 20 Jul 2026 17:00:15 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="OEZpsIhH"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 1F84467E45 for ; Mon, 20 Jul 2026 16:59:54 +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 31D6F2F83; Mon, 20 Jul 2026 16:58:55 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559535; bh=UO6WVoNQMx1q1QWULleBKSxVbeniXAs8Xc14pJqRt6I=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=OEZpsIhH4ycogaYSKc6lOVWmX0P3OrIDiJepeLImah8rCjGDDZEhzmKSPO6JgwPiE pujt+HY5jU/MW7c+4hCnKtfKMFzLX9uNXXWrR04UcgcJ48vewbycQx0IskCkYD36RD 030ZK9oyc8QHqUgJEeOs7+TPfQ5aczpVNCIuMfVw= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:41 +0200 Subject: [PATCH v6 23/31] ipa: libipa: lsc: Make Components a map<> MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-23-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 , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=18087; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=UO6WVoNQMx1q1QWULleBKSxVbeniXAs8Xc14pJqRt6I=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfZn5XgPC4YNxqDZy4cfKHP/kWYrJkZ+40vE qHCo7nJ2TiJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal432QAKCRByNAaPFqFW PDVQD/wJU0YL2KFgS66eGUKRMMjMquV2VjzNNJ04fjZEN8Q75/jV7ETeHZwCWzrQKaNsss0tSR7 6dlJ2z7kKBA4x1Inv+f12AucyMivfaobAG0DF7JyLr63aBO5ld1Qs/xm6k5OV/Ed3NvMy6Jai7Y HSdgDgxewH0khXQEJeOcD0hx96IWM8P5arWeAJCK5Bx5XovdwJ9V//oDyCzyQxwHsuIdlG0VsVq rE0OJh6wb9V9vXsdxq54hn/gKfdwitXuQAGupjg+Za0KvHVBCwQURn9BZvFfqvBS9z0NBg+4k1F 9ndQ7FIkLfqJaOPzG5irbt9GG1bAcci2a6Rc0yrBijbTD2prSXJ/Ir2rE45KKnadpbWI4IgRDLd cd8b3jOmSpPZUh78muJcADAaAryFElH3qWjffZGnPOV5GBTJyjeItuEUp6jn4sKcLERi/3Fs58l uurXfZ1aCQgadc3P9ESSbPd4DaY+odgCqokfgfH3G21DvBpzanYoYbQanFsYl2I5cS21GAN4axJ Jr4zQ8wspoE/7UrexH4jomgCyxCf0Fv4zYMqSa46DwFSB1AC9Ke51RyvWvTz7AF/Q/klIUYx3SR zanELKkQmpC4Lt7Ysi+hadFmNEtyzS8xCO8t7Y1Suaxy+aFMRBfXKS85mzrmiZM7q5BBo4dkOeh wfc8Pc8fHotpMcA== 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 | 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 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..01616d1c2e2a 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 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::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..b0b9b3b0712d 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 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..e17ba85448be 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 47dff156963a..92e24bc5160a 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.numHSamples, + descriptor.numVSamples))); + 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 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]); } /** From patchwork Mon Jul 20 14:59:42 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27416 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 E15CDC32FE for ; Mon, 20 Jul 2026 15:00:17 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5D1C067E95; Mon, 20 Jul 2026 17:00:17 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="qDzxEU+k"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id B89F267E6F for ; Mon, 20 Jul 2026 16:59:54 +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 BF1203398; Mon, 20 Jul 2026 16:58:55 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559536; bh=EIoRTYcRxBXUh/gCEob8/0V/PQOEtmC11Bd6tGcGu5A=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=qDzxEU+kSM2qcL/qF0ooN2jp7pJKG6fDwkvelTQtPq4GaHKDyvDlim3qNU45OWbNu L5whSinyeaxtblYHOZCRq3O+blDz+Xx7hxS9HhmUUf07NqK4LlPe/i/56DXsSf52Fg lFUN+Sb2h1qlNAgyesqYZLkfqeSzEe2cuFsw1994= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:42 +0200 Subject: [PATCH v6 24/31] ipa: libipa: lsc: Document Components parsing MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-24-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 , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=4373; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=EIoRTYcRxBXUh/gCEob8/0V/PQOEtmC11Bd6tGcGu5A=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfZ9uc5U5gMfEcl2UzFtdpHVCMyvay6kPJMv m5U8q31M9SJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal432QAKCRByNAaPFqFW PFbXD/9TFbN6MIPtl/7CjzjIfmB6GKbNSZScxpIDenUBv2WVC0YIVc2yugonrxss2uTYsQM6OMZ EJa5X9BTIdk3b+ce4l5VAIbObQaFU3yr8CkwYcYlKyc5CX9HT4inZNFmXFk6nHJHhyPI2lvdjSz TUVJNuvE/UlbBKm7Dwfrnp690RTX47vzuHt/pfNwhKAN0g23y4Hx7eBzHNFIWw+Q96XqjIfuWui Gcb66OrR5zHl456+P47BM+JSRd57YAojCnJwXbcxVeGUHBL+UuyGaD6l+PJgMaMipImuZJpXsm3 Ejs2m4aXEZCpi9BEprjJGT3jo8C0oTXYHzLhwTbFxrCMqErS5Pr3OCEtChB3x/I36aUz/Tvgujq DiD2xrbpu/CYmhRDKIc2yaPJVTurSArw7UUbV8IiV1qY4I6klCFSqwNkwcwkpEUZXAfckqGaXJA /nTbaby/K7LnclApx/GTBTHHtmzWezNEugSKz53uiuAziZh/2V14UNcz6V2AKZrAw5fHImBLLIx RQtqESU6XjKOFVDI5kGtFwpS/R6U6oDOsMbkcEnglvgX8st68g8QEkrS1TJy8O7J8xYPET9aSp2 X37B95sCFxVslYQg+WfaNj8THuEioTC7Farrzocm/Z+HAg2/JxtnBz2yQiZrr3t2M7iScu+10zK JwJGZC2SRN+srmQ== 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" Document with examples how different IPA modules parse the tuning file depending on the number of colour channels they support. Signed-off-by: Jacopo Mondi Reviewed-by: Stefan Klug Reviewed-by: Kieran Bingham --- src/ipa/libipa/lsc.cpp | 111 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp index 831f6b003df1..32626049a2e2 100644 --- a/src/ipa/libipa/lsc.cpp +++ b/src/ipa/libipa/lsc.cpp @@ -68,6 +68,117 @@ namespace lsc { * tables are indexed by colour temperature with per-colour-components vectors * of gain values or polynomial coefficients. * + * The colour components names are IPA-implementation specific and depend on the + * ISP LSC engine design. Some LSC engine support 4 colour components (r, gr, + * gb, b), some only support 3 colour components (r, g, b). The name (and + * number) of the expected colour components shall be provided to + * LscAlgorithm::init() using the LscDescriptor::keys field. + * + * Example of a tabular lens shading tuning file with 'r', 'g' and 'b' colour + * components. The gain table has been omitted here, but the expected number + * of entries has to be equal to + * LscDescriptor::numHSamples * LscDescriptor::numVSamples. + * + * \code{.yaml} + * - Lsc: + * sets: + * - ct: 2500 + * r: [ + * .. gains table omitted.. + * ] + * g: [ + * .. gains table omitted.. + * ] + * b: [ + * .. gains table omitted.. + * ] + * - ct: 6500 + * r: [ + * .. gains table omitted.. + * ] + * g: [ + * .. gains table omitted.. + * ] + * b: [ + * .. gains table omitted.. + * ] + * \endcode + * + * Example of a polynomial lens shading tuning file with 'r', 'gr', 'gb' and 'b' + * colour components: + * + * \code{.yaml} + * - Lsc: + * type: "polynomial" + * sets: + * - ct: 2500 + * r: + * cx: 0.5006571711950275 + * cy: 0.510093737499277 + * k0: 1.5393282208428813 + * k1: -1.1434559757908016 + * k2: 4.332602305814554 + * k3: 0.0 + * k4: 0.0 + * gr: + * cx: 0.5009320529087338 + * cy: 0.511208038949085 + * k0: 1.5634738574805407 + * k1: -1.5623484259968348 + * k2: 4.846686073656501 + * k3: 0.0 + * k4: 0.0 + * gb: + * cx: 0.5012013290343839 + * cy: 0.5128251541578288 + * k0: 1.526147944919103 + * k1: -1.4316976083689723 + * k2: 4.792604063222728 + * k3: 0.0 + * k4: 0.0 + * b: + * cx: 0.49864139511067784 + * cy: 0.5162095081739346 + * k0: 1.0405245474038738 + * k1: 0.05618339879447103 + * k2: 1.8792813594001752 + * k3: 0.0 + * k4: 0.0 + * - ct: 6000 + * r: + * cx: 0.5006202239353942 + * cy: 0.5099531318307661 + * k0: 1.4702946023945032 + * k1: -0.8893767547927631 + * k2: 3.920547732201387 + * k3: 0.0 + * k4: 0.0 + * gr: + * cx: 0.500907874178317 + * cy: 0.511084916024106 + * k0: 1.5336172760559457 + * k1: -1.39964026514435 + * k2: 4.565487728954618 + * k3: 0.0 + * k4: 0.0 + * gb: + * cx: 0.5011898608900477 + * cy: 0.5126797906745105 + * k0: 1.5013145790354843 + * k1: -1.2747407173754124 + * k2: 4.514682876897286 + * k3: 0.0 + * k4: 0.0 + * b: + * cx: 0.4987561413116136 + * cy: 0.5159619420778772 + * k0: 1.0102986422191802 + * k1: 0.13263449763985727 + * k2: 1.686556107316064 + * k3: 0.0 + * k4: 0.0 + * \endcode + * * At LscAlgorithm::configure() time the LSC tables are re-sampled on the * sensor's crop rectangle in use to adapt them to the configuration in use for * a streaming session. Polynomial LSC tables support re-sampling and can be From patchwork Mon Jul 20 14:59:43 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27417 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 05FBCC333B for ; Mon, 20 Jul 2026 15:00:18 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 735D167E72; Mon, 20 Jul 2026 17:00:18 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Y5z5l75n"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 3787767E7B for ; Mon, 20 Jul 2026 16:59:55 +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 5EEA3339C; Mon, 20 Jul 2026 16:58:56 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559536; bh=k2Iq3q+zwr6OqfqbNHCbdw0aMUyO+rV/Rakq/K3/TGs=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=Y5z5l75nYpl1ylxBFCM5pco2Vw4xIvZVUwSL0c01iV6n8hMGNA2dPh5BM6SFkszoX 5HLV3WzUxeRDnt0SjgOkEeMrVvUWrG/qrKgOewvh3z4XhLVpgsYAaL9wnsuQaR5XK3 SEKk9Rj6LE1sjOQwcQ31QndFwPxCIrd/trQJEkk0= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:43 +0200 Subject: [PATCH v6 25/31] ipa: libipa: lsc_polynomial: Remove sensorSize_ MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-25-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 , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=3310; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=k2Iq3q+zwr6OqfqbNHCbdw0aMUyO+rV/Rakq/K3/TGs=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfaJHHrwcC5dN3ytGxn0nnhPOog1e7001pUY 6s56tB0EP6JAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal432gAKCRByNAaPFqFW PGdLEACb0bc9TV7cQCaSRgL/pqbMLKgnMocnHtWYpj4MDvx10iXlmwzQuvb/rTsCOsVf1fSmLLM 6QhA2agpw0N9AH4qG9OjIx+SQUUCDn+D9X99qYGmnfPB49QqkZIlyGsPsu4flTO1Sr7+fLZ9Lat XQGFtti7Ro3IEbPAiNh4MjwUs+eV71Df5uXK8Gx9SveY5vOyWLiEu40B/TR9UhRUwqe2TVrSLzF yw3X7O6qjasXscc29DYj3uPSV7MERxPvb+VMhA82HYfWnMdi8eU7SnCzJPa7FEklrrQ3GrUZa+x aOO9ZIWtAVGBVUfu0ammmNdwrMoDKgBQhFXzT5lEgz3Iqbe2FSCRZbVRJYEspET1Nn8H1p797Rc 3nJBuO6iGNr7deRGrmgvkZZ2nZIOv32Ys0JnB248BhD4vDtMlELf+dEeytLOCr/GhLhSbvQekEs 07DdaSnycLsq0x2XDYtKl5Ucl0JuBkcTyFWy+Tv3AA0I/4+Ghy9zgdNhl6PF2fzO2t0WHYyybNM QzWmh4OJau1fBzAlHOT6JVicYOaAuSqJDchK8N2dBVM3Vkr1C3qc0K/aieHVUv44uopoztMA+p5 su9DJ+/r70kBbxw8VJib7V11Gm87QiGs9utvbWoEBGaMZjRvu0XavVtkYUdarcnM+k0mCrEXm72 157+KX+/bjApPWQ== 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 sensorSize_ field is only used during the init() function and is provided to LscPolynomial using the LscDescriptor type. There is no need to store it as a class member and to pass it in at class construction time. Remove it and move the \todo comment in the LscDescriptor type documentation. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Reviewed-by: Stefan Klug --- src/ipa/libipa/lsc.cpp | 6 +----- src/ipa/libipa/lsc_base.cpp | 3 +++ src/ipa/libipa/lsc_polynomial.cpp | 7 ------- src/ipa/libipa/lsc_polynomial.h | 6 ------ 4 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp index 32626049a2e2..2062b795991d 100644 --- a/src/ipa/libipa/lsc.cpp +++ b/src/ipa/libipa/lsc.cpp @@ -215,11 +215,7 @@ int LscAlgorithm::init(const ValueNode &tuningData, ControlInfoMap::Map &control impl_ = std::make_unique(); LOG(Lsc, Debug) << "Using table-based Lsc"; } else if (type == "polynomial") { - /* - * \todo Most likely the reference frame should be native_size. - * Let's wait how the internal discussions progress. - */ - impl_ = std::make_unique(descriptor.sensorSize); + impl_ = std::make_unique(); polynomial_ = true; LOG(Lsc, Debug) << "Using polynomial Lsc"; } else { diff --git a/src/ipa/libipa/lsc_base.cpp b/src/ipa/libipa/lsc_base.cpp index 01616d1c2e2a..cf2c3bfc382f 100644 --- a/src/ipa/libipa/lsc_base.cpp +++ b/src/ipa/libipa/lsc_base.cpp @@ -78,6 +78,9 @@ void Interpolator:: * \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 + * + * \todo: Most likely the reference frame should be native_size. + * Let's wait how the internal discussions progress. */ /** diff --git a/src/ipa/libipa/lsc_polynomial.cpp b/src/ipa/libipa/lsc_polynomial.cpp index e17ba85448be..c61c85fbf2b2 100644 --- a/src/ipa/libipa/lsc_polynomial.cpp +++ b/src/ipa/libipa/lsc_polynomial.cpp @@ -123,13 +123,6 @@ void Polynomial::setReferenceImageSize(const Size &size) * \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 diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h index 2a26db499d68..6d19e46a50b7 100644 --- a/src/ipa/libipa/lsc_polynomial.h +++ b/src/ipa/libipa/lsc_polynomial.h @@ -57,11 +57,6 @@ private: using PolynomialComponentsMap = std::map; public: - LscPolynomial(const Size &sensorSize) - : sensorSize_(sensorSize) - { - } - int parseLscData(const ValueNode &sets, const LscDescriptor &descriptor) override; @@ -75,7 +70,6 @@ private: Span yPositions, const Rectangle &cropRectangle); PolynomialComponentsMap lscData_; - Size sensorSize_; }; } /* namespace ipa */ From patchwork Mon Jul 20 14:59:44 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27418 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 9FCC9C333C for ; Mon, 20 Jul 2026 15:00:19 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1877D67EA8; Mon, 20 Jul 2026 17:00:19 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="BZukmN6H"; 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 BE80167E5D for ; Mon, 20 Jul 2026 16:59:55 +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 D9F01339E; Mon, 20 Jul 2026 16:58:56 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559537; bh=6Obv0Wz/HJF7mOUnS6V31ONAzqtsTuCPOBefw8+/xCs=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=BZukmN6HYKrFjPXHpwDtQSVmVX+HEwFljSQec/1pLGbsPDpdYkpeZ4ihiJ+hu5Wcx B6w7GWTGuU/XHqUvyKpI6pwfUTFQQ6rkoeF0nsjhIRcfuUEs0V6vEzSe7y+9WdFfb5 vSJJ3T/+3klSQnmyiFKvUBqfvSqIBEa1FQtBJyPA= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:44 +0200 Subject: [PATCH v6 26/31] ipa: libipa: lsc: Document LscDescriptor::sensorSize usage MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-26-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 , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=1079; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=6Obv0Wz/HJF7mOUnS6V31ONAzqtsTuCPOBefw8+/xCs=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfaBoKFN+Kn0uuyVVcyDyfFiFzLZL3dPOOyF usdM1sUSp2JAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal432gAKCRByNAaPFqFW PPafEACyzfXw9Ss1XLpixpXTWZcFasxlkz2kqjk1IJOmldYJkopgvsI4ssZbFGv2W8aNp1U/D5+ l8mwK3EmlKsAR0lG3zhYhF3M6nmVGEfOI8T28AuRArc76E9LUia3DiFbcgDhD7kITU/kCGiImGf 9HlSnKPOpoQ9Sqo8BSsgdPNosAkzJGA2l8HJHNeiVTeria0S8ljRSct15RKRCjIdwUvbi1Q/sLr 9TMXFJrOmzIlA/u64nXF3CvxbAONisdkFoo6pEg/zBzT7wE8OTiPZZ3CnRfoY7HRSDALYm5rXzI jtvyx6UDbdK81Fh6OcuxME2yD/Qxg5jyYhAKTHicbHAYvD0p5Hcr7JhCgHUE6Lq15OgNTjci5yh pZ1aeIU7AD7eXO0pDL8KC3k5YYO22/F9f01J+IomqBIJsO1Z6+KI4Dt/MOXRXbObCezyJ/xdwJW qHsM4hoAM40PADmjzcUOAlIumT5Gxru45/y4XLs+jKI5nc/Dxgihs/dXDqQqKdHAz8WP1BR0MmH nz7BzA9cOULJkNW9FElhMvHuMn+Ud5sVewEnyDkkWKp3kmI/zp1lVZyMTK5bO1/cjKSnpuXn/OU nrWTq/mbh5BTZT+lJhn9hnhw2CK6JK4gwjqjLqRdnJWSUXNtOYdjfOwwDw5ac3VLKPGjrpPFPJB 3U6I26Ctp/nCkOg== 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" Now that sensorSize_ has been removed from LscPolynomial and is passed to the Lsc algorithm through LscDescriptor document its intended purpose. Signed-off-by: Jacopo Mondi Reviewed-by: Stefan Klug Reviewed-by: Kieran Bingham --- src/ipa/libipa/lsc.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp index 2062b795991d..8c5c28077cce 100644 --- a/src/ipa/libipa/lsc.cpp +++ b/src/ipa/libipa/lsc.cpp @@ -179,6 +179,10 @@ namespace lsc { * k4: 0.0 * \endcode * + * The LSC polynomial definition is generated at tuning time in relation + * to the full sensor pixel array. This needs to be specified in + * LscDescriptor::sensorSize. + * * At LscAlgorithm::configure() time the LSC tables are re-sampled on the * sensor's crop rectangle in use to adapt them to the configuration in use for * a streaming session. Polynomial LSC tables support re-sampling and can be From patchwork Mon Jul 20 14:59: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: 27419 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 E4C42C333D for ; Mon, 20 Jul 2026 15:00:20 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 6542267EA4; Mon, 20 Jul 2026 17:00:20 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="rLN1wwr9"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 5033D67E83 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 671811CE9; 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=1784559537; bh=dlfNgaxvpp06Pxl62gjETV+oyFIXVXC6rt1lzcZEpYo=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=rLN1wwr9zqi/qgIR9hvlKTL6j2N52d1il4reeM25lYqKzdLQV2VrNtfy3GjuXWe2C FY3Mt7Z0qbvNhFEWXevh4MyrfnHMD1PiurGtUgN54pIrhD4cKHSzD3p//MSFdwNA9W WJqUjgl2kSGIymB56WmoKLL2mxsuad6LCNNOMrro= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:45 +0200 Subject: [PATCH v6 27/31] ipa: libipa: lsc: Quantize lsc gains in IPA MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-27-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=6809; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=dlfNgaxvpp06Pxl62gjETV+oyFIXVXC6rt1lzcZEpYo=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfap9BmU1yuAc8zHJbfjCem5Bbqplt9GgcTC p3rSAbANH2JAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal432gAKCRByNAaPFqFW PHW5D/92cTnLNYkHcsM8jwT3I881kfdRWTW1qJ4EBqduADra+BhV+lhYMZy9oyZF84IqLSxiZCB AUZCXDThq46QuBRM7r05g8Y/PN71TwJdlu4pkW5dfKoH5CX4Cg9tPi7phAAn4iviowGNIBSGoHO X48CKdtorsQgJlIu76Ty1wyGRgT0ArEtd3DQTwVlE7cDT52z3rWIHaPZhrkN0btQV+Mcvq+yvR1 EY9Ef9Rzi8hs7lMcNQPHIL0UzToF9l+NU5cEePw+P6kZbKm88JvhHRZMhWrhw/6UUADbjydt+XG jNe0b0jY1N4vcVgn1L/hLwWWLGobfDZzssk0nqzYvCTBoUedcbUf49ahWhcu8ikD6ianWnD+t6A jpZV8xisjVcN5GXGZMlMRMM4D6C0GRuszFo8XN0lDNFtYTEt7enSfM0aBcCNPFaqoNXUf/2VN3Z VI3eFFlvMG3XCOAWEag8w8p5hL+QLfQi6xRa/QPKu/LuM6wvIR4L6fBM4e6gd6oLPE5dqQTveOZ g8dY8c5UqxHrsku5oyHnNDr0Ua5ejXQBzX+MIQdRbi8Iqw83bGlyU4LNv4e2W6fsalxZw3PmKwa 0PKbypLNhQ6jVxRy7kuNwybPmHQ0L5oW9vt8CEu7wj8tFqatnQ32FBDd3YW+5fJYDykZ2bDUOBd R3HXq53IaMtYadQ== 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 libIPA Lsc algorithm currently assumes the register format of the RkISP1 platform (16 bits Q2.10 format), as that's where the implementation has been derived from. Move the register quantization to the plaltform IPA and use 'floats' as the exchange format for the table-based and polynomial-based LSC implementations. Signed-off-by: Jacopo Mondi --- src/ipa/libipa/lsc_base.h | 2 +- src/ipa/libipa/lsc_polynomial.cpp | 17 +++++------------ src/ipa/libipa/lsc_polynomial.h | 8 ++++---- src/ipa/libipa/lsc_table.cpp | 6 +++--- src/ipa/libipa/lsc_table.h | 2 +- src/ipa/rkisp1/algorithms/lsc.cpp | 34 ++++++++++++++++++++++++++-------- 6 files changed, 40 insertions(+), 29 deletions(-) diff --git a/src/ipa/libipa/lsc_base.h b/src/ipa/libipa/lsc_base.h index b0b9b3b0712d..386cac55be34 100644 --- a/src/ipa/libipa/lsc_base.h +++ b/src/ipa/libipa/lsc_base.h @@ -26,7 +26,7 @@ namespace ipa { namespace lsc { -using Components = std::map>; +using Components = std::map>; using ComponentsMap = std::map; } /* namespace lsc */ diff --git a/src/ipa/libipa/lsc_polynomial.cpp b/src/ipa/libipa/lsc_polynomial.cpp index c61c85fbf2b2..e31a63e06cc8 100644 --- a/src/ipa/libipa/lsc_polynomial.cpp +++ b/src/ipa/libipa/lsc_polynomial.cpp @@ -218,7 +218,7 @@ LscPolynomial::sampleForCrop(const Rectangle &cropRectangle, return components; } -std::vector +std::vector LscPolynomial::samplePolynomial(const lsc::Polynomial &poly, Span xPositions, Span yPositions, @@ -229,7 +229,7 @@ LscPolynomial::samplePolynomial(const lsc::Polynomial &poly, double y0 = cropRectangle.y / m; double w = cropRectangle.width / m; double h = cropRectangle.height / m; - std::vector samples; + std::vector samples; samples.reserve(xPositions.size() * yPositions.size()); @@ -237,16 +237,9 @@ LscPolynomial::samplePolynomial(const lsc::Polynomial &poly, 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); + + samples.push_back(static_cast + (poly.sampleAtNormalizedPixelPos(xp, yp))); } } return samples; diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h index 6d19e46a50b7..8d526531075b 100644 --- a/src/ipa/libipa/lsc_polynomial.h +++ b/src/ipa/libipa/lsc_polynomial.h @@ -65,10 +65,10 @@ public: std::vector xPos, std::vector yPos) override; private: - std::vector samplePolynomial(const lsc::Polynomial &poly, - Span xPositions, - Span yPositions, - const Rectangle &cropRectangle); + std::vector samplePolynomial(const lsc::Polynomial &poly, + Span xPositions, + Span yPositions, + const Rectangle &cropRectangle); PolynomialComponentsMap lscData_; }; diff --git a/src/ipa/libipa/lsc_table.cpp b/src/ipa/libipa/lsc_table.cpp index 92e24bc5160a..33f6182de1bd 100644 --- a/src/ipa/libipa/lsc_table.cpp +++ b/src/ipa/libipa/lsc_table.cpp @@ -82,15 +82,15 @@ int LscTable::parseLscComponent(const ValueNode &yamlSet, return 0; } -std::vector LscTable::parseTable(const ValueNode &tuningData, +std::vector LscTable::parseTable(const ValueNode &tuningData, const char *prop, unsigned int numHSamples, unsigned int numVSamples) { unsigned int lscNumSamples = numHSamples * numVSamples; - std::vector table = - tuningData[prop].get>().value_or(utils::defopt); + std::vector table = + tuningData[prop].get>().value_or(utils::defopt); if (table.size() != lscNumSamples) { LOG(LscTable, Error) << "Invalid '" << prop << "' values: expected " diff --git a/src/ipa/libipa/lsc_table.h b/src/ipa/libipa/lsc_table.h index a33208761c15..6d9b0c692b7f 100644 --- a/src/ipa/libipa/lsc_table.h +++ b/src/ipa/libipa/lsc_table.h @@ -42,7 +42,7 @@ public: private: int parseLscComponent(const ValueNode &yamlSet, unsigned int ct, const LscDescriptor &descriptor); - std::vector parseTable(const ValueNode &tuningData, + std::vector parseTable(const ValueNode &tuningData, const char *prop, unsigned int numHSamples, unsigned int numVSamples); diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index 7a95bfcfb93a..dba1a01118ee 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -177,14 +177,32 @@ void LensShadingCorrection::setParameters(rkisp1_cif_isp_lsc_config &config) void LensShadingCorrection::copyTable(rkisp1_cif_isp_lsc_config &config, const lsc::Components &set) { - 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]); + /* + * 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]); } /** 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 */ From patchwork Mon Jul 20 14:59:47 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27421 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 D8B0CC333E for ; Mon, 20 Jul 2026 15:00:23 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 71C0D67EC0; Mon, 20 Jul 2026 17:00:23 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="vzATD6+Y"; 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 4B2FC67E52 for ; Mon, 20 Jul 2026 16:59:57 +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 6FEDD33A5; Mon, 20 Jul 2026 16:58:58 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559538; bh=z/2FHW4blFgIZFDkrKhTDQPDwONQEdKNRjBHv0iCkEQ=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=vzATD6+YIhrU3l2A9SyJZ9GgQ0YoJ3tslvR0YfCkmXRxgwgN+S1GHN81mM5yAkEhE CVleia5UKua9MuCnks+ofqRilgnMgTOR/fZYU0RUsI3dUDujoI903XxHq5TPtUuy9M oKFE9/ou9KWpPJDDLg5TjWFSECuA6i9CMKnI+FXI= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:47 +0200 Subject: [PATCH v6 29/31] ipa: libipa: lsc: Re-sort LscAlgorithmBase documentation MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-29-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=6638; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=z/2FHW4blFgIZFDkrKhTDQPDwONQEdKNRjBHv0iCkEQ=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfb+IlfP7c8YOh4yFppclv2NuORoaWnmzG+j VegZ6u8aDGJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal432wAKCRByNAaPFqFW PMhmD/9ek6XyI1uP1HE0gkFZPyCiUtH+/SIWk6imtmzpIcEAIgY8uIK0Y0vD/nNhA2s7fmyDdKh 1cdxhJ5YbXFh8CfXZMgVQopUc8hNzU15fvCJzPlKW18X3Wd+2ysoDJ8MzdMEPihDrB3Dx2zcbUJ K5YHbveguugy/M1rtABU5i8DMo8bX3sGrhEjqg6ao1yrUE/W5edot/FIQbPbdOClovhPg9LU/oW 2cl5A5bBg73vnyEU6qEKA91tDgwb5t+L3xFDKhD20cTHmxMMhS3fqssibX9wC/4mf1+WZNMP89g i3YziHYzwKUvgcuZEhjRflGidXmSbiMnkbE82K7NN8XAMVA9rtsycxO2i+nmfTptGeCNjoio1hA Xv44+h5q4B6EOVOT4o5Pgy6e3LpwoDwQo0X4TR6u3CITLjok77zphdbBVRRouIcL9zBKdfHQeI+ exvRKOI5Vyw4lcvOYYNyvbAObImtHIldQWLnrLz/xt9kR0ZAFl93mUMegGUQb43HUYidbBmD8Dk wKtTV8zTGcaWHnWYoKL98Kb6SJH80KP8OKBTDNET2vYdgCvr9y6YvLrawTtIhGESWI4/0g7ZUh0 5IybK1IYJkT6cQty3eUs018cGmWNEoV+olkDUkVFedVekuWhSFlLRuorTCvBmFeba1LP35/Qov3 6uZiSVTnpshByRg== 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 documentation had not been resorted in the previous patch to keep the diff short. Re-sort the documentation of the LscAlgorithmBase and LscAlgorithm classes to match the declaration order. Signed-off-by: Jacopo Mondi --- src/ipa/libipa/lsc.cpp | 168 ++++++++++++++++++++++++------------------------- 1 file changed, 84 insertions(+), 84 deletions(-) diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp index 8f881edef94c..adaae253b740 100644 --- a/src/ipa/libipa/lsc.cpp +++ b/src/ipa/libipa/lsc.cpp @@ -55,6 +55,90 @@ namespace lsc { * Base class for LscAlgorithm for non-templated functions implementation */ +/** + * \param[in] tuningData The tuning data + * \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 LscAlgorithmBase::init(const ValueNode &tuningData, ControlInfoMap::Map &controls, + const LscDescriptor &descriptor) +{ + polynomial_ = false; + + std::string type = tuningData["type"].get("table"); + if (type == "table") { + impl_ = std::make_unique(); + LOG(Lsc, Debug) << "Using table-based Lsc"; + } else if (type == "polynomial") { + impl_ = std::make_unique(); + polynomial_ = true; + LOG(Lsc, Debug) << "Using polynomial Lsc"; + } else { + LOG(Lsc, Error) << "Unsupported Lsc algorithm '" + << type << "'"; + return -EINVAL; + } + + const ValueNode &yamlSets = tuningData["sets"]; + if (!yamlSets.isList()) { + LOG(Lsc, Error) << "'sets' parameter not found in tuning file"; + return -EINVAL; + } + + int ret = impl_->parseLscData(yamlSets, descriptor); + if (ret) + return ret; + + controls[&controls::LensShadingCorrectionEnable] = + ControlInfo(false, true, true); + + return 0; +} + +/** + * \brief Queue a request to the lsc algorithm + * \param[in] state The lsc active state + * \param[in] context The lsc frame context + * \param[in] controls The list of controls associated with a Request + * + * Queue a new list of \a controls to the lsc algorithm. + * The only supported control is controls::LensShadingCorrectionEnable. + */ +void LscAlgorithmBase::queueRequest(lsc::ActiveState &state, + lsc::FrameContext &context, + const ControlList &controls) +{ + const auto &lscEnable = controls.get(controls::LensShadingCorrectionEnable); + if (lscEnable && *lscEnable != state.enabled) { + state.enabled = *lscEnable; + + LOG(Lsc, Debug) + << (state.enabled ? "Enabling" : "Disabling") << " Lsc"; + + context.update = true; + } + + context.enabled = state.enabled; +} + +/** + * \brief Populate the list of lsc metadata + * \param[in] context The lsc frame context + * \param[in] metadata The list of metadata + * + * Populates the list of \a metadata with controls handled by the LscAlgorithm + * class. The only supported metadata is controls::LensShadingCorrectionEnable. + */ +void LscAlgorithmBase::process(lsc::FrameContext &context, ControlList &metadata) +{ + metadata.set(controls::LensShadingCorrectionEnable, context.enabled); +} + /** * \var LscAlgorithmBase::impl_ * \brief The LSC algorithm implementation @@ -232,51 +316,6 @@ namespace lsc { * \brief Map a colour temperature to an LSC componenet */ -/** - * \param[in] tuningData The tuning data - * \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 LscAlgorithmBase::init(const ValueNode &tuningData, ControlInfoMap::Map &controls, - const LscDescriptor &descriptor) -{ - polynomial_ = false; - - std::string type = tuningData["type"].get("table"); - if (type == "table") { - impl_ = std::make_unique(); - LOG(Lsc, Debug) << "Using table-based Lsc"; - } else if (type == "polynomial") { - impl_ = std::make_unique(); - polynomial_ = true; - LOG(Lsc, Debug) << "Using polynomial Lsc"; - } else { - LOG(Lsc, Error) << "Unsupported Lsc algorithm '" - << type << "'"; - return -EINVAL; - } - - const ValueNode &yamlSets = tuningData["sets"]; - if (!yamlSets.isList()) { - LOG(Lsc, Error) << "'sets' parameter not found in tuning file"; - return -EINVAL; - } - - int ret = impl_->parseLscData(yamlSets, descriptor); - if (ret) - return ret; - - controls[&controls::LensShadingCorrectionEnable] = - ControlInfo(false, true, true); - - return 0; -} - /** * \fn LscAlgorithm::configure() * \param[in] state The LSC active state @@ -300,45 +339,6 @@ int LscAlgorithmBase::init(const ValueNode &tuningData, ControlInfoMap::Map &con * \return 0 on success, a negative error code otherwise */ -/** - * \brief Queue a request to the lsc algorithm - * \param[in] state The lsc active state - * \param[in] context The lsc frame context - * \param[in] controls The list of controls associated with a Request - * - * Queue a new list of \a controls to the lsc algorithm. - * The only supported control is controls::LensShadingCorrectionEnable. - */ -void LscAlgorithmBase::queueRequest(lsc::ActiveState &state, - lsc::FrameContext &context, - const ControlList &controls) -{ - const auto &lscEnable = controls.get(controls::LensShadingCorrectionEnable); - if (lscEnable && *lscEnable != state.enabled) { - state.enabled = *lscEnable; - - LOG(Lsc, Debug) - << (state.enabled ? "Enabling" : "Disabling") << " Lsc"; - - context.update = true; - } - - context.enabled = state.enabled; -} - -/** - * \brief Populate the list of lsc metadata - * \param[in] context The lsc frame context - * \param[in] metadata The list of metadata - * - * Populates the list of \a metadata with controls handled by the LscAlgorithm - * class. The only supported metadata is controls::LensShadingCorrectionEnable. - */ -void LscAlgorithmBase::process(lsc::FrameContext &context, ControlList &metadata) -{ - metadata.set(controls::LensShadingCorrectionEnable, context.enabled); -} - /** * \fn LscAlgorithm::getInterpolator * \brief Retrieve the LSC tables interpolator From patchwork Mon Jul 20 14:59:48 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27422 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 6BDF1C3306 for ; Mon, 20 Jul 2026 15:00:25 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id ED36367E96; Mon, 20 Jul 2026 17:00:24 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="XQskVpNy"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id D8AE167E87 for ; Mon, 20 Jul 2026 16:59:57 +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 E934933B3; Mon, 20 Jul 2026 16:58:58 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559539; bh=ax8BgGImoAq8vCACjcTubXq3ibzUonWpyk48NOKhiQI=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=XQskVpNyrk1HrKwLyHUwtEBL8Qdn8C3LkNZPqj54Gh8n9hCzpRAQvPappS7Jff48l nHcYcJ5aEql1n27aEKeoHRhwWxesQqvPg4lXOAjeVnEqMFC+Pt338LSV50PXVieAGf yT9MHlJ3t1M02yFHh393Gd0ju5Bz6FCHFDvxO8YE= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:48 +0200 Subject: [PATCH v6 30/31] ipa: mali-c55: Add sensorInfo to IPAContext MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-30-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 , Kieran Bingham , Daniel Scally X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=1573; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=ax8BgGImoAq8vCACjcTubXq3ibzUonWpyk48NOKhiQI=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfbXw9b77t014TTPD+VANZMqiVfuTxt0ifHX A9M9tsLHNeJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal432wAKCRByNAaPFqFW PPnTD/9KL8p/Wq8ebua+q+Ad9t5KU7rOWy+FvEk+cjWYEj5ms3IgODM0RfCSWqGJI0dzpEAAvVX /M1Br8Xm+hMMLYUZYjeHwcHdjHN4g0EokGk5L7LuLnnYJjpjXPLuNfQItA2TK3vTL2ts+3y9sDN I4CgUWafiawniVpqL7rDgofShRDEK7Qk2AQdUthfOCFGhaSzt59m13yPVmKTNs2QJNCpsIyLHlb 4rlCsQYnBhWinAN29RSPg4DfgVBMGKnef81fHJdyS4NeaVzClhCD3wVMTQnPscmHgj5DpdS1Lf7 g4bNT4eO0nPST2B8qESpoIyeZH5ECLuNW+hVjVvFmgfaUWFNr0inPFUkR7reK+evsZy/2KDM/G1 wNcZzimZZrRtcisCpe7cof4Gw4CwsSrUoHsIvoEuclqsR6guIADxnDCXyO+akwdUNJNo8hHE4xp hjLF+LbHkOGxuHXE6V34TQv2XELxdMFhhalo7neDYK9UDLQm2H+4Oail+4vowPmT8kTH1c2PZmK 7covhWOg8r6orv0k+3NWx0JjjNsz8eDJdrI5P5tUk5mCILdVfCaPWfflpdeRQM2vJilZ2fqOwIB RM4INQ50410EcU/82FKqAlcWywqt5rlg5QlMsUhD1oOJqOQ2Mo5WfX9+rNADA2vdF948sPIZyjV rB65bYV23o07ung== 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" Store the sensorInfo in the IPAContext. It will be used by the Lsc algorithm to support polynomial LSC tables. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Reviewed-by: Daniel Scally --- src/ipa/mali-c55/ipa_context.h | 3 +++ src/ipa/mali-c55/mali-c55.cpp | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h index ef3188e347d7..deb3f39e0857 100644 --- a/src/ipa/mali-c55/ipa_context.h +++ b/src/ipa/mali-c55/ipa_context.h @@ -10,6 +10,8 @@ #include #include +#include + #include "libcamera/internal/bayer_format.h" #include @@ -77,6 +79,7 @@ struct IPAContext { { } + IPACameraSensorInfo sensorInfo; IPASessionConfiguration configuration; IPAActiveState activeState; diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp index 1d3af0627fdb..da0dbe4b31b8 100644 --- a/src/ipa/mali-c55/mali-c55.cpp +++ b/src/ipa/mali-c55/mali-c55.cpp @@ -101,6 +101,8 @@ std::string IPAMaliC55::logPrefix() const int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig, ControlInfoMap *ipaControls) { + context_.sensorInfo = ipaConfig.sensorInfo; + camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel); if (!camHelper_) { LOG(IPAMaliC55, Error) From patchwork Mon Jul 20 14:59:49 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27423 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 2A368C333F for ; Mon, 20 Jul 2026 15:00:26 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id BED5E67EB7; Mon, 20 Jul 2026 17:00:25 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="iFusTk7k"; 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 72D2867E89 for ; Mon, 20 Jul 2026 16:59:58 +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 8721815CD; Mon, 20 Jul 2026 16:58:59 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559539; bh=1l2MVess0u7issVwAA4hX20emJkhhQCSMUcC+M3n6zU=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=iFusTk7kYb1BCbr5YLDX9S42gjd78D15HZ73/0JkUrtCstJysnhsC3+e4vexiGZeP vnHTJVq7Gff84YhwrlbLSttxx4o7MePJmsOjx47GZELO25Rccz8wWw8uvQli0jrIM5 r0HgM+OeWM3Stxm1jo+wn77FRPw7TLiTeqCu7cqA= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:49 +0200 Subject: [PATCH v6 31/31] ipa: mali-c55: Port to use LscAlgorithm MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-31-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 , Kieran Bingham , Daniel Scally X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=10841; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=1l2MVess0u7issVwAA4hX20emJkhhQCSMUcC+M3n6zU=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfbfpIYLHWummCD9+YAs5w41IS6oRCgloXKW ai6TG0JtYKJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal432wAKCRByNAaPFqFW PEarD/4jzxZ9Ly5oIDJpCTIY4UE0CE3NpMvzw8oFV3vtBgVxvXlLdWY0lbm9KcmGU4PHquIPuYf WN+BGbW7itMcvdpQUkVZNOhUH8SoV3LHTczb2Lcrwvg7Bpe8J+hoYMy8touTBgQGcRVXdWtoy3K KnUmGFR5ErmehVHE8YwL2cVIB+MtWdw8N0/QzA9AATddSS+/avmQFWjd3skeHrcN4ekIATbaK2F 8qUyR0FXxLGz8trDM/vbfrb0rtscBHCWqtoBrjG6KBJotTlkpr7W9tja4vmMUNnyw8LKIMMv3q8 eD/9s5GjhnBwxbcDB5jNMIHdC/fvfqWRbNgjWPTt8FalJb+pAX6RlrNnZAbDroChh24Ucm1s/vj gbDkYV9IF/hOB26QhCrCoql4L9YLjYxqQ9+ArgS+ObOPyADlXfhH2bQJlkYhsw+mdN/zzKr+fc4 sT1loor5WQ2bd7dRBq4TmKzkFjTwaWRFwvBd51PyEGF3t4oVexi+3CQDmzP8d4I3vF0pr6r4uCY Sa2MefrrbL26u4vnIxrcoG2BSIsidzygOWLfD+wOLJQx3QyUDry5QJszSKxfXHnDYlDtiLkXwRB 8Timn1/xQGEUCnHhWlXSuc+lxnustn5XdxzzNPvTtggX99UtdQV3/YhUZhk33TqcnL5zte9SfkY 8Rdpb65Z1Q+qhaw== 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" Port the Mali-C55 LSC algorithm to use libIPA LscAlgorithm. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Reviewed-by: Daniel Scally --- src/ipa/mali-c55/algorithms/lsc.cpp | 156 ++++++++++++++++++++++-------------- src/ipa/mali-c55/algorithms/lsc.h | 32 ++++++-- src/ipa/mali-c55/ipa_context.h | 3 + 3 files changed, 122 insertions(+), 69 deletions(-) diff --git a/src/ipa/mali-c55/algorithms/lsc.cpp b/src/ipa/mali-c55/algorithms/lsc.cpp index 097239dcef3e..daa28c3e28e0 100644 --- a/src/ipa/mali-c55/algorithms/lsc.cpp +++ b/src/ipa/mali-c55/algorithms/lsc.cpp @@ -9,38 +9,74 @@ #include -#include "libcamera/internal/value_node.h" - namespace libcamera { namespace ipa::mali_c55::algorithms { LOG_DEFINE_CATEGORY(MaliC55Lsc) -int Lsc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData) +/* Gain values in [1, 5] range */ +static constexpr unsigned int kMeshScale = 6; + +/* Mali-C55 hw supports configurable mesh sizes; we fix it to 32. */ +static constexpr unsigned int kMeshSize = 32; +static constexpr unsigned int kGridSize = kMeshSize * kMeshSize; + +/* Per-colour component page offsets in the mesh table. */ +static constexpr unsigned int kRedOffset = 0; +static constexpr unsigned int kGreenOffset = 1024; +static constexpr unsigned int kBlueOffset = 2048; + +/* + * \todo Clarify if Mali-C55 can support up to 4 colour temperatures. + * + * The uAPI only expose MALI_C55_NUM_MESH_SHADING_ELEMENTS (3072) gain elements, + * which correspond to three pages of 1024 (32x32) entries. + */ +static constexpr unsigned int kMaxColourTemperatures = 3; + +/* + * The LSC algorithm implementation only supports 32x32 grids. Create a list of + * positions from the grid size. + */ +std::vector Lsc::segmentsToPosition() const { - if (!tuningData.contains("meshScale")) { - LOG(MaliC55Lsc, Error) << "meshScale missing from tuningData"; - return -EINVAL; - } + std::vector positions(kMeshSize); + for (double i = 0.0; i < kMeshSize; ++i) + positions[i] = i / (kMeshSize - 1); - meshScale_ = tuningData["meshScale"].get(0); + return positions; +} - const ValueNode &sets = tuningData["sets"]; - if (!sets.isList()) { - LOG(MaliC55Lsc, Error) << "LSC tables missing or invalid"; - return -EINVAL; - } +int Lsc::init(IPAContext &context, const ValueNode &tuningData) +{ + gridPos_ = segmentsToPosition(); + + return lscAlgo_.init(tuningData, context.ctrlMap, { + .keys = { "r", "g", "b" }, + .numHSamples = kMeshSize, + .numVSamples = kMeshSize, + .sensorSize = context.sensorInfo.activeAreaSize + }); +} - size_t tableSize = 0; - for (const auto &set : sets.asList()) { - uint32_t ct = set["ct"].get(0); +int Lsc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) +{ + int ret = lscAlgo_.configure(context.activeState.lsc, configInfo.analogCrop, + gridPos_, gridPos_); + if (ret) + return ret; - if (!ct) { - LOG(MaliC55Lsc, Error) << "Invalid colour temperature"; - return -EINVAL; - } + /* Re-initialize the mesh tables and reserve space for enough entries. */ + mesh_ = std::vector(kGridSize * kMaxColourTemperatures); + colourTemperatures_.clear(); + /* + * Get the lsc tables per colour components and populate mesh_ with + * their content. + */ + auto &components = lscAlgo_.getComponents(); + for (auto const &[ct, component] : components) { if (std::count(colourTemperatures_.begin(), colourTemperatures_.end(), ct)) { LOG(MaliC55Lsc, Error) @@ -48,44 +84,29 @@ int Lsc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData) return -EINVAL; } - std::vector rTable = - set["r"].get>().value_or(utils::defopt); - std::vector gTable = - set["g"].get>().value_or(utils::defopt); - std::vector bTable = - set["b"].get>().value_or(utils::defopt); - - /* - * Some validation to do; only 16x16 and 32x32 tables of - * coefficients are acceptable, and all tables across all of the - * sets must be the same size. The first time we encounter a - * table we check that it is an acceptable size and if so make - * sure all other tables are of equal size. - */ - if (!tableSize) { - if (rTable.size() != 256 && rTable.size() != 1024) { - LOG(MaliC55Lsc, Error) - << "Invalid table size for colour temperature " << ct; - return -EINVAL; - } - tableSize = rTable.size(); - } + std::vector rTable = component.at("r"); + std::vector gTable = component.at("g"); + std::vector bTable = component.at("b"); - if (rTable.size() != tableSize || - gTable.size() != tableSize || - bTable.size() != tableSize) { + /* Only 32x32 tables of coefficients are accepted. */ + if (rTable.size() != kGridSize || gTable.size() != kGridSize || + bTable.size() != kGridSize) { LOG(MaliC55Lsc, Error) - << "Invalid or mismatched table size for colour temperature " << ct; + << "Invalid table size for colour temperature " << ct; return -EINVAL; } - if (colourTemperatures_.size() >= 3) { + if (colourTemperatures_.size() >= kMaxColourTemperatures) { LOG(MaliC55Lsc, Error) << "A maximum of 3 colour temperatures are supported"; return -EINVAL; } - for (unsigned int i = 0; i < tableSize; i++) { + /* + * Create the mesh table entries by assembling up to 3 gains per + * colour temperature in a u32 word. + */ + for (unsigned int i = 0; i < kGridSize; i++) { mesh_[kRedOffset + i] |= (rTable[i] << (colourTemperatures_.size() * 8)); mesh_[kGreenOffset + i] |= @@ -97,35 +118,36 @@ int Lsc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData) colourTemperatures_.push_back(ct); } - /* - * The mesh has either 16x16 or 32x32 nodes, we tell the driver which it - * is based on the number of values in the tuning data's table. - */ - if (tableSize == 256) - meshSize_ = 15; - else - meshSize_ = 31; - return 0; } +/** + * \copydoc libcamera::ipa::Algorithm::queueRequest + */ +void Lsc::queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, const ControlList &controls) +{ + lscAlgo_.queueRequest(context.activeState.lsc, frameContext.lsc, + controls); +} + void Lsc::fillConfigParamsBlock(MaliC55Params *params) const { auto block = params->block(); block->mesh_show = false; - block->mesh_scale = meshScale_; + block->mesh_scale = kMeshScale; block->mesh_page_r = 0; block->mesh_page_g = 1; block->mesh_page_b = 2; - block->mesh_width = meshSize_; - block->mesh_height = meshSize_; + block->mesh_width = kMeshSize - 1; + block->mesh_height = kMeshSize - 1; std::copy(mesh_.begin(), mesh_.end(), block->mesh); } void Lsc::fillSelectionParamsBlock(MaliC55Params *params, uint8_t bank, - uint8_t alpha) const + uint8_t alpha) const { auto block = params->block(); @@ -198,6 +220,18 @@ void Lsc::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, fillConfigParamsBlock(params); } +/** + * \copydoc libcamera::ipa::Algorithm::process + */ +void Lsc::process([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + [[maybe_unused]] const mali_c55_stats_buffer *stats, + ControlList &metadata) +{ + lscAlgo_.process(frameContext.lsc, metadata); +} + REGISTER_IPA_ALGORITHM(Lsc, "Lsc") } /* namespace ipa::mali_c55::algorithms */ diff --git a/src/ipa/mali-c55/algorithms/lsc.h b/src/ipa/mali-c55/algorithms/lsc.h index 3d35fd72bfa8..5f752eebaef5 100644 --- a/src/ipa/mali-c55/algorithms/lsc.h +++ b/src/ipa/mali-c55/algorithms/lsc.h @@ -5,10 +5,19 @@ * Mali-C55 Lens shading correction algorithm */ -#include +#include #include +#include + +#include "libcamera/internal/value_node.h" + +#include "libipa/fixedpoint.h" +#include "libipa/lsc.h" + #include "algorithm.h" +#include "ipa_context.h" +#include "params.h" namespace libcamera { @@ -21,23 +30,30 @@ public: ~Lsc() = default; int init(IPAContext &context, const ValueNode &tuningData) override; + int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override; + void queueRequest(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, MaliC55Params *params) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const mali_c55_stats_buffer *stats, + ControlList &metadata) override; private: - static constexpr unsigned int kRedOffset = 0; - static constexpr unsigned int kGreenOffset = 1024; - static constexpr unsigned int kBlueOffset = 2048; - + std::vector segmentsToPosition() const; void fillConfigParamsBlock(MaliC55Params *params) const; void fillSelectionParamsBlock(MaliC55Params *params, uint8_t bank, uint8_t alpha) const; std::tuple findBankAndAlpha(uint32_t ct) const; - std::vector mesh_ = std::vector(3072); std::vector colourTemperatures_; - uint32_t meshScale_; - uint32_t meshSize_; + std::vector mesh_; + + std::vector gridPos_; + + LscAlgorithm> lscAlgo_; }; } /* namespace ipa::mali_c55::algorithms */ diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h index deb3f39e0857..7017dfc87c2d 100644 --- a/src/ipa/mali-c55/ipa_context.h +++ b/src/ipa/mali-c55/ipa_context.h @@ -19,6 +19,7 @@ #include "libipa/awb.h" #include "libipa/ccm.h" #include "libipa/fixedpoint.h" +#include "libipa/lsc.h" namespace libcamera { @@ -60,6 +61,7 @@ struct IPAActiveState { ipa::awb::ActiveState awb; ipa::ccm::ActiveState ccm; + ipa::lsc::ActiveState lsc; }; struct IPAFrameContext : public FrameContext { @@ -71,6 +73,7 @@ struct IPAFrameContext : public FrameContext { ipa::awb::FrameContext awb; ipa::ccm::FrameContext ccm; + ipa::lsc::FrameContext lsc; }; struct IPAContext {