From patchwork Wed Jul 8 15:50: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: 27309 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 2A008C3261 for ; Wed, 8 Jul 2026 15:51:18 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 54BAB6608D; Wed, 8 Jul 2026 17:51:16 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="F6JOcAa9"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 9C3DF61451 for ; Wed, 8 Jul 2026 17:51:12 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 37EF519A2; Wed, 8 Jul 2026 17:50:22 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525822; bh=RpUDcWqrtYtmnystDS0mFOGu34COlhfbnzDYcV9x7j0=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=F6JOcAa9AVbNdtFs6BGdjG5XsPXuJ7FAPUdqMwLrIzR45kcpj7l8AH5e/xJp4+9VG eOwvTALLlgwjbwX6DAnelfnQQ331wSNysCn0ejJZM6c4y01QkkNopgCSYTJdmLMTuO tLBQt1TztLDQEVSerTjgrylLOwzkMIc/w/2otwIw= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:50:43 +0200 Subject: [PATCH v5 01/36] ipa: libipa: awb: Reimplement AwbAlgorithm MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-1-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug 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/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHfwURb7Libm70U2qAN5ynoUTwbQX82qv/3l I6mEPI+MuKJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x3wAKCRByNAaPFqFW PEpVEACFS7+1HnmtKu7o7dRE6FjERKvNdjDd5Vvl0GalG75/jgDrw8UY8XTd6TPU23CoPI912xV /Zx2Vc23Qd3laII/GdCujnrZOp5gpl9D8iYPsIUMW8fkW/GkIrT3bTm8sPmEm2eQpqz7M9mjhyC pNwBTuJCc2t9g7Wd5C1mZCU4QpznQiVMMYCZQ5124IeR89mtgUPxJJhlECu82WzIa+UZJC7iDXI C8gf/NNbOSBunNHUmT6d3jDYcx1kBB8N9SRxJx9U/f0QCVnWTWzNeMJ7aZ3IplUYwr94XMP6Gyc PWbO3ERSe9OyBJJR/qvJWG2T6V+m69Z7jS5NyX3LChud7mtH3/RXxmDQtW1jS8hH34T9U5TQyxM yGiDDoDVfpc1nvQ2MHIpvLjPzjRgh8G2tohrzPf4n+ZaPf+Xqpw1AcRazuPxf0h4b6fQLaqp6H0 VQDLW7endfjHyNJ962Lm1o3v5ans5LBeTTOr1usBMgdzUvdHtXmp670JD69pw0hKCiBUo01A7QE WHIslQ7398ELsGCRG3dg/cRQJfTF+pcxGGO72/vUXVgjBG3xiKd1aEeAaQoBL0K2wn8jsFmgVjf pvhSL9h4F/ezYtZHAsyb9j2Bxp60P50w3lLhjdl6H7pG0l97j2X/xfTPvF+RdgV7CehpgHZw3Jk 73TVVSSYOiK1aLw== 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 Wed Jul 8 15:50: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: 27310 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 6AD0DC3261 for ; Wed, 8 Jul 2026 15:51:21 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8ED9F660CE; Wed, 8 Jul 2026 17:51:18 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Jx7Izp1P"; 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 CDFF76608D for ; Wed, 8 Jul 2026 17:51:12 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 9B9841AC5; Wed, 8 Jul 2026 17:50:22 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525822; bh=dhl5gblzCqtHc/mQBW5GOxetdk2axICofVb774mO1ss=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=Jx7Izp1PvEn5dCY450FnNcM4sJAtyRwV4NhH3pet55/k4ZIRv82iAfBJnFuCdFA1j U4mtlI88iaVFyWQ7W8SYDNRzgloZCGSQajdzys/bQsy37rWpwxIJefJ7oA9MsOz327 wiH11qPPOcSNGHywAEgmdJYo5ef/Mhesi2bXi7zs= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:50:44 +0200 Subject: [PATCH v5 02/36] ipa: mali-c55: awb: Port to use libipa AwbAlgorithm MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-2-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug 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/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHgH3lORO+vxn4s9lj1bS14ZJis2JiHcdSz3 52i+Y79WquJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x4AAKCRByNAaPFqFW PF3WD/0UbrIj02OiEdM7zGg4wAwWERndMuYI7eg1uOT0oDb/kumNi8uq90US3W/a7jRXgLMftUq 4kQIu/k2a3AJ3vjCtlDJmpl7A2SoAqix/d8qpEXenTbScRosb1STu3iYM7PSKgL5bhTaXmZUsuY dZQvJdBmI1cwzcUEBkRLo5BrQhYU9QAN3mdA5cuFNMVp7igdfx5PK7ALaSFHUUTqnqTJgHbvRoR DH4qb+iHicql7rZhCsJEcHMCN90OHFIW9OBX9ODWy/vObcQtfRGPxo6/JH91iPbDAATQBxcNsEz 1XchD7ZI8sp4/3i4h/9laoS7jlfTv6M4i+chdsiYzn3DmArgg5TFwcxmyXd5S3do6pofeHSOBM8 Vb4T3oeD2ac1qYQsl04baLf6F7WLaF553ND5HxKBNF96PNrumNtfSphbbHeScu/58uSs14FMXMM izPQ0yNG1KLb41kyIsWzhKsD14/cAmGdVrm84e4pj1u2FjqW01Fc+Adwv9UyiJWLrYLd2OWmtzK wFqNfBk/yNdjMLC4pj88SdPqR92IEy5Bj0AliH9n4kOwTEBrdvjOsbSPYlZMggPH4KWg0mL+xL1 cYDhzPiwaR6cg9Y21eS8aWM9IRluY4FE797EXvPKeNTDEagHssYAxsSfeegCZogGwjaKcr4LNBd wpZGGVGHqN7Wrfg== 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 Wed Jul 8 15:50: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: 27311 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 8EA48C3261 for ; Wed, 8 Jul 2026 15:51:23 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D482B660DB; Wed, 8 Jul 2026 17:51:19 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ijfQdLuc"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id DFAB0660B1 for ; Wed, 8 Jul 2026 17:51:12 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id ECA171BCF; Wed, 8 Jul 2026 17:50:22 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525823; bh=kmNUq70BbFCrmBu5/t0w+AafCluJg95fYiP6PaPrpb8=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=ijfQdLucUEiGTBCXgMCMtDOb/Y0I4UN9zQg2aXF1WXdM39AOlJ4QlYd+e7L+rPBZE aszVeJjoJJc07cp9ToF6xx1QYDS4BoVtvkyN3zkwF0rHkvEY5LCIgQBLXYJpyBbzNL X+NqG9QZzzFmj7/JFtV1e1a7cfw1pB86SRGhvjs8= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:50:45 +0200 Subject: [PATCH v5 03/36] ipa: simple: awb: Port to use libipa AwbAlgorithm MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-3-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi , Kieran Bingham , Robert Mader X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=13677; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=jPiJcbEiCJucyZ6BJxSpVNb+9LFJKY6hvOG/cwFj3Ms=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHg9mUr7Dq+VrsXAg7KTF3xw0lhzcBnvZcOk +GOe31Oz4GJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x4AAKCRByNAaPFqFW PKUMEADBPSpuIvdOoD9tH6Df1dIhoe4miI+YXMjYijN3p55YbKUhKYQ30hkglhFb2Z16vvxcw6u PYsk92auLykzoNPtO4owllI/5bqwdiFQ7nea/3/3wNxsobI3NUFBrIoKe/Q40XueDylpOEgGdEp 9Gf8Yk02ca79nbJKvrPMPO/6/kzVSTR3slz11u8p1162yRQUKuUseVukKBZgga7a1glehGzR3T6 MSTUVpLmXJiJGPMQh2oh5vBaX96S4Zdftt8ZjsyjWqwhPb+VwNDRoBctq9QbtGVN9vjzqwFUnhj NEmItJPb1ZpTe5WzJnCw3PSJAWRz5f1aU6Hc2dqVq45GlJwPsUaoYDN9/yCYPg3mjnxAcxFymcO 82FZeLc5/fyba44ZUtBrtdJZhD1BmToP5zmsh4YFCCfbzhPe3M4Zd1t+NZnQVQIP68tgj2e1B9I v0+zKK+sl95bOsa32xkWUup/Yf6HJNVTBl/qocZB10x/W3lhy+41ozhizMTponPifwO/z49tSgF kVZkFd/Re5Jy7Aq45GmmDRwH/0UQYkGlH799PEWcBr9YIHjiMbDje4JkMju+HhGs87Ng4SU2Xeq 9NCSe2WclZY8rqVnZcjnw3yrzZijF1F/zs9yI0h5pG7KzQCBJWT+hvtf1iupKXWOYx5Ldz/jDCy r12Fxj+AK5gddLw== 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<4, 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 --- .../internal/software_isp/debayer_params.h | 4 +- src/ipa/simple/algorithms/awb.cpp | 141 ++++++++++++++++----- src/ipa/simple/algorithms/awb.h | 29 +++++ 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, 151 insertions(+), 51 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..64ce4f91b21c 100644 --- a/src/ipa/simple/algorithms/awb.cpp +++ b/src/ipa/simple/algorithms/awb.cpp @@ -15,7 +15,6 @@ #include #include "libipa/colours.h" -#include "simple/ipa_context.h" namespace libcamera { @@ -23,41 +22,108 @@ 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 +133,37 @@ 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; + RGB rgbMeans = { { static_cast(sum.r() / nPixels), + static_cast(sum.g() / nPixels), + static_cast(sum.b() / nPixels) } }; + /* - * Calculate red and blue gains for AWB. - * Clamp max gain at 4.0, this also avoids 0 division. + * \todo Determine the minimum allowed thresholds from the mean + * but we currently have the sum - not the mean value! + * + * Currently set to SimpleAwbStats::minColourValue() = 0.2. */ - 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; + 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..fec48c6eea1c 100644 --- a/src/ipa/simple/algorithms/awb.h +++ b/src/ipa/simple/algorithms/awb.h @@ -7,19 +7,37 @@ #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, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, @@ -29,6 +47,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 Wed Jul 8 15:50: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: 27312 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 666FAC3261 for ; Wed, 8 Jul 2026 15:51:25 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7911F660C7; Wed, 8 Jul 2026 17:51:21 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="TyJrfT3b"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 38819660B2 for ; Wed, 8 Jul 2026 17:51:13 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 4AA951C2A; Wed, 8 Jul 2026 17:50:23 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525823; bh=f9+XfBVeEJ4VSD1bfCxQGr3LUyOFZa7nG5xmDQhXjKc=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=TyJrfT3b8VCMzfj54F4FOGR1zIMY6KENurWbfICshBys6rYLmBz06T+uTb7FtWBGc 8QbrAK7FGfQZLWAtBFQZVfjleBOAsfCRvan4nv62mo64SiRoEAvnENAxCaHYMLI8Jt MqAkREqhtCG9SwSAG98ramaKmaCWb9u5CNs7IjlY= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:50:46 +0200 Subject: [PATCH v5 04/36] ipa: libipa: Add CcmAlgorithm to libipa MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-4-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug 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/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHhjrgg9HYT0QZVYfreea3WrVsLxQnZSx8MO +Hk2wZmQxaJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x4QAKCRByNAaPFqFW PAJID/0VM5rzVD3ldxaxDeScPtqtB5BFKITEFBMGVwZpy5Ac8RaA+9j31uDW7O/phFzsZN7liey W4sI56bnmrMIrQIazM1hbUvbt7km8Aag/yZTdOMcW4qmKi6OIjTw/bnmNC0rdCqxJfr2c/QhuxE Q3cxG2Dy7Amvdcxj+lRcumAfPI156a5+fpvrjDQwovVRG5q8eJWHCQQgol87PGbTbzKZA6oWC8Z jjKZia7v8lgpiwCWF9ccPYa9zcKFk3oFD7Kk9jLrbZsgP7dDWwYfwdl7kpC2LsyvM3/ICJZSVmz BIQYsF5SVvGOrSvOm02hmKVnvtahqRBkbte2pDrK4e0A5KnCPCo7I8dgM4Ww/fusOXAN8xxVj7y Wr1AI7EG38suX+KAyyOgLsRMPeu1lWsD6pmTJ4Gugt3jJEnYge8zL6kvXNrjN1uwzbQQCAaXUWV WmT3qLGF9qdDdK3BmbctwwL+0om7ZxqU10wyax636co2BfIQrjkvUC1KdFnsnWtzNJjP4D8or4F eWF9ELPxA0EJJXgf1NEiyD3ns8SDkeHyrpCDlixxx6PP1FIU1mge+vWghFXGaDbQ14gaJEtuIS4 RhlCeloqKLihA2+nPl69dTocw7aKXt14DIB9yqso51eYOwVPiH9xk0U8/X29YOFYtDx00SYiB3x XTURNFksrUQRXHA== 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 Wed Jul 8 15:50: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: 27313 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 11CDDC3318 for ; Wed, 8 Jul 2026 15:51:27 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 13793660E9; Wed, 8 Jul 2026 17:51:23 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="YHeNoB7x"; 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 8BE37660A7 for ; Wed, 8 Jul 2026 17:51:13 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id A295B1C4D; Wed, 8 Jul 2026 17:50:23 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525823; bh=ltIYhnnM1bUd815aMZ307m3Gl3PwVM2zbZHeIKk9o7s=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=YHeNoB7xM2GUz0h0QSIZmVDztk+bFj5b3rYAxEVQQnieK9PN9T/pTTe6L8xl84zAz OrZgopq/EccPBP5ciyg5AzmVb9DbPDrXsNOlgVHkItLTkhxkfKqEkInJZxfEOMU2Y8 Pr9IxpPSa1KPEurYUY1ETCvAOXZJ0iC8M12MjBUE= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:50:47 +0200 Subject: [PATCH v5 05/36] ipa: rkisp1: ccm: Port to use CcmAlgorithm MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-5-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug 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/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHhlW1WLcWJfSu3BwOUpAT0zhQl+/PsNRUGS iehagCrGqyJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x4QAKCRByNAaPFqFW PHYiD/96udX/Qo0mw6fzvssQ1qNRRnzefHu4uHAD8hOjm7KzYNfNHDLeuhZv7BM9uOBCIlqghjT FjM4ScC3teSKsdbXnevZ6aqomJOxZD66paI1Uryd/Awjrlrp1VEq5DBSQVd6E7OgXnsYpb8xalC tppJwn0D18zOGA34gVWz1sVQjMNWC4eejUJXY4Aui8mCxlgV3AzieJjS2vfbkQXgNzTh7vYfPkm yRmSUePKkpQ4ZOmm+6JkOVIU5usfDv3YASNvl58+mXzetDVClitHfHcCDOKD5F5qG032n7pq+La uva4O2FBGoUSqQ+KkCtuWA8CYBi0Bi/GMSy+ui5av6ruoAnt1dUrdJOb7Mm8pTYAaIaIF/0EmN5 Ca9gP65fdL5x8TlbT8YQqnH2yS5PGnAL13rdRTRFSlg0K2HYjC7Gzgp74GXL2vq36vww4aBE9ii HRTyLeP5w1YIa2Eub/nOAhZ1eXpwJiQ7xTZrrAz52/AoQPGkhYNMmpGuUJvI4lNKihYhA1hQT/m 2DWJX6hhIvONs7Qhv0pRo3dPnu6KAZjsv4pb3f3w39FecffQwRUSeJVY4iAYrd08pB9kmuo0/a8 2SE4Q+3C0RmvOv2Q1UjYgba6QYzDbVUj88sLVKyS1g2X4FuWUIyKDeosoIFkZqzc737BeAuivS5 vcrj2WPJ1f6Gttg== 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 Wed Jul 8 15:50: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: 27314 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 85A74C3261 for ; Wed, 8 Jul 2026 15:51:28 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 755D3660E6; Wed, 8 Jul 2026 17:51:24 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="APK7YZRW"; 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 DEE03660AB for ; Wed, 8 Jul 2026 17:51:13 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 03FA31C94; Wed, 8 Jul 2026 17:50:23 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525824; bh=tNSoqcW8/apEIrBh9kAAY6iv6Q4lrCS2K5o5rQ0PL+Y=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=APK7YZRW4REhaeq3bkvaFd8ACiu/KtkkFOirtpvmH5cOV78ONikK7yg8jym2bKTn6 KkMvI/jmiMQhiqopfd/FXTa/+HmM007qVftGmM39ie71M8np6lKmTv1j33zr9Qmm2S yF6vjdA7NxQnizTTYBuVZhHFUGt287kd5ohQmxJo= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:50:48 +0200 Subject: [PATCH v5 06/36] ipa: simple: Use libipa CcmAlgorithm MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-6-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi , Kieran Bingham , Robert Mader X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=6161; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=4RB8F20OAh55bZc/z6C6O4lhhVyZ0OlK/hVmRwh/ACo=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHh17qTD58+O9TJobkLhaR+n2Lt95ZKId9sM nkoPgtATHOJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x4QAKCRByNAaPFqFW PMLgEADGPS/CUeaLAsZ2YM6O9TWRNCIsqP7qDsmafXG9B33jPU0gWg8hbgFueIZoK3wsUwZE0pU L7JXNvZU1l3TV3IgFtucN90ADycJRXTG3Fyf+7SCLgyYvDu1/GfHoh3fS+xwp6Uu/HLEq06AIAf HseEbSWcNhNI/njJZHG9uPXGQ54sCcJDhUzyaXwvHttFUG4y7X9ybPH5IcJiVASJ0j6uqm5XOc7 9/4JnwAKnzLaP2W+Km6IafoM72l3ozEswyV6trWa3/UTzDv1cZZwrRaGm6xY9Ur1rJWJRIMISzc PKS+rPDu1ivX7h0L+xCnh+ATp51r9CrAEySJTsGr5bJyh4JM2aGWzRPdeI35C/FSADeyHjp7taw DloXkS3f7ASNLTvTMZoG8aWfGSQlem9IVu15SPLVy8qds52wwKhVg+o+QKQ1Bzl0voMQy68EhLI TrATh9Y1d+bzrTUYiyDnV5WFq9s7kBHTU2LBQ6pSLYrS4by6LnKCFwtYW+zB3MzoxoPFNT5DUXu a7d8R1WYaacb00dOWUoKqXq8N/i6vDb/nokdaRhTL9/ZXhdVMKG9toK0Nlf201Z9sYvolOgv6GG ZoDzko1y+s+NhSEaXLBMC/1a0QUcQz9kEq9iJkky7YEO4eV0fWdXCV9V+Rb5eANmRt2OkFMEhVZ JuO766Ymfz1Izog== 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 --- src/ipa/simple/algorithms/ccm.cpp | 72 ++++++++++++++++++++++----------------- src/ipa/simple/algorithms/ccm.h | 23 ++++++++----- src/ipa/simple/ipa_context.h | 5 +-- 3 files changed, 58 insertions(+), 42 deletions(-) diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp index 1174784edc7e..c6f270825913 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) +/** + * \copydoc libcamera::ipa::Algorithm::init + */ int Ccm::init([[maybe_unused]] 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) { - const unsigned int ct = frameContext.awb.colourTemperature; + return ccmAlgo_.configure(context.activeState.ccm, + context.activeState.awb.automatic.colourTemperature); +} - /* Change CCM only on bigger temperature changes. */ - if (!currentCcm_ || - utils::abs_diff(ct, lastCt_) >= kTemperatureThreshold) { - currentCcm_ = ccm_.getInterpolated(ct); - lastCt_ = ct; - } +/** + * \copydoc libcamera::ipa::Algorithm::queueRequest + */ +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::prepare(IPAContext &context, [[maybe_unused]] 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..0d35347ea583 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,15 @@ 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, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; + void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, @@ -36,9 +43,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 Wed Jul 8 15:50: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: 27315 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 B6516C3318 for ; Wed, 8 Jul 2026 15:51:29 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B670E660EA; Wed, 8 Jul 2026 17:51:25 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ZbURqipa"; 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 3EC48660B3 for ; Wed, 8 Jul 2026 17:51:14 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 5BD621CDC; Wed, 8 Jul 2026 17:50:24 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525824; bh=4A9kajOFuKXSQXUsoFZ+WfGwjzpqM6yrrwEjlszpxEw=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=ZbURqipaiAo3Y+24ZGKPKB0N+NeSue/bJNMBmD3lxKE2rmkTW1XNtugCQKZBx06cf SS1GOCBi83M/clzONtn0d1hj+zwzdpWpcFAFwSYzdqK1R0k58iSr2kQwhyLPA1lmLJ asOqCyFXbKGBo0U7Zr4vPrJpXZTV8O3LDh9uquac= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:50:49 +0200 Subject: [PATCH v5 07/36] include: linux: mali-c55: Update to support CCM and Gamma MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-7-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug 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/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHijFYcrlkHTZocv89OqtgJ3gv1YtJwhodsJ dCAb14+KtWJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x4gAKCRByNAaPFqFW PLDaD/wOhhZwQxXaeTrRyfKPbNBq7BB4mOlwE0e+TOQo0NFIk/UntL801dCfTWXuq2wg4H8jTBf 3KcsdAFJsIzO1ewu5ePrJIB4iTDTU/N9aF4VE0vgHYRHjSrXzsNxnw2jO3jvRpcU1zGv+/g83ZV BoHtgqGqHPyK+yar743Rur19Xfs5CIhTFhMwk0a21Uq4W2fVxoyC/k0D/TVxmzzbA1W0uprI7wd fpzziTrs+Rdfhlfkyj1C5w/21KS2dUjVJiM0Wni5n8bZbjjl9cgG8M55QrVGyBbGJzOLgNXwvrh pVBxZ1o7u89M5A+yp3oPo93zjDtVbjvuLSpQ7wSrcjWVVPxivXpjMsGG3LCuNtX6cAeX5gyOW9L WsQUSyMhkM3UTjBtKxXGokuYoSYuiS+HbEKaqAkXux3bUv+Fck2SrDqoZbElAYWmEUJ4ukLrvrA JCuDn5u44/gOocVwWOwCTRYemxi4WLZqA9soZ17E2911O9/XzgaAJ9nyNF3oMgGo61yXrayKmzZ VSfv1dO54wwEgIXEGYUmEAYWRczkfi2l2TpayOPcbEf4+QxVzwk69y/Ezt4CDpUSg14Qy7irM82 7IAcAOIpuRisURvxD/8XJsxcKxbAsXepZA03RqF0Ld9Rhj/67bS5pJQYPR0o7BDgrjz/qz1b0YC lAR/Mhqc4mO4yWQ== 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 Wed Jul 8 15:50:50 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27317 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 C8134C3318 for ; Wed, 8 Jul 2026 15:51:32 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 11D97660F3; Wed, 8 Jul 2026 17:51:31 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="IVlGjas7"; 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 813806609B for ; Wed, 8 Jul 2026 17:51:14 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id AA7B81CFD; Wed, 8 Jul 2026 17:50:24 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525824; bh=bRL+cvadKBepGDYjRs3g72dQEyqODsUmc0jD0kXxbRo=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=IVlGjas7r6gmxOxykKitvZVyqP9JVRmFs7uTid1Rd/JkcYP+qINxbo8wi4MRdbrpX FhKQ+pRXozwLIOiSSIVrv4l1Dut3jQ6/ldAp9+BroKJMj9PbyA3nipSUmWUdpH1tVZ 13Iranauj8fh4+/6xUnRLIa158ssUmMRUdb6I5Jk= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:50:50 +0200 Subject: [PATCH v5 08/36] ipa: mali-c55: Implement Ccm algorithm MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-8-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=9899; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=bRL+cvadKBepGDYjRs3g72dQEyqODsUmc0jD0kXxbRo=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHif4Cy6jqZSghVal7Wt4RpQub9wHu2UHQm5 XhkrbEPHp2JAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x4gAKCRByNAaPFqFW PK0ND/4niQJXpqiU1kl/btrJY3iX56WXuafgbNub2XCrVMsH7nUAZPvAGzoxT5LTc06skSXwj1p 3iW9aPO1NES81opdarqpwaCKWjpjRmPMISu3+GYfrt5uOKwVbL+km2v/0sQIlb1tWDK1lqdRXSK U9CxGHxGxnhGay6PBIntF+0LzY5FFu7xH5slvCYhT+P8eB9eahcpPrB5Hw2KwWW58hbGjEP+Ke4 lSYKaelPfzQeVO7TbuNiafaiXodDfkDtUzZBpp6lE8uwgCMh5+tvM8kJ93eeNLJx7UYxzJ5i1u0 VXJS5QJbFCua19XgGn/pfaYz2IYJY7ywCMC66jNxZ1WOqNWqeLgMorQnT+HoITgLIsMBA+Nmmi3 gqbGT2lhvgfxXOXIqIXo4AiFkFVyok6px1PRGg8dMZHd7n3FQwqQ2/y+In7y4Ei995Urqlv9+ew VbiT5Gn1JC+dtZJ/HdD14DAYpM/cT6tMI7lXA6JOMJstLwt3TrKT8egmoDnIwS+oSpLd6Jl5kUc MJ2LeS5xXADMHX0EAbOfAlPOLGFzJKLHNNj5LBqgT0bb06a5/3pH62cvP/bqFAJ75PAE87RMLJs 2KkaTQCJ5pI1KiYqHoOg7Hs9zu37Q+bSwZkwqfIRHnMaruN5FwxfWFsz/R+6zxh15Dcpbehyq1X SCWPJ64QISTPqlg== 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 --- 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 Wed Jul 8 15:50:51 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27318 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 2BBADC331D for ; Wed, 8 Jul 2026 15:51:34 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8E1A7660A5; Wed, 8 Jul 2026 17:51:32 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Td18xhFJ"; 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 D5086660B5 for ; Wed, 8 Jul 2026 17:51:14 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 0064D1D03; Wed, 8 Jul 2026 17:50:24 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525825; bh=/YB+sHNbmPImz60FaygKFvKtjE2RV5KVOUMkfOU9+mY=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=Td18xhFJfAr95heZz4wQmvFINgraHdOJ9gq+Hknl1S6PwzLMaS+c8SmLcEilD9sbT tPP/rUN53uETh5nuJYH+vV1Z+nVzEBPGV/1MlIJr74Wr1NAJqTT/9NqOZ5AWgWGk2j jSIArjvYJL4iE4I4r8dPHSwhV6X5jnz8H9MDL4GM= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:50:51 +0200 Subject: [PATCH v5 09/36] ipa: rkisp1: lsc: Re-architecture Lsc support MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-9-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=15591; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=/YB+sHNbmPImz60FaygKFvKtjE2RV5KVOUMkfOU9+mY=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHiX6ITQLTjqimEHqrWfkWebQQ0JqyAS3R0g 1yRRUrz69SJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x4gAKCRByNAaPFqFW PBQnD/4pCZwKEx3J6EIcxzowUeyJ55u58/v567L29HUZey48398W3vsrW2PNiSMunEPj3kWKlKR q65ZZ9Q5NabosEkDwzi2oLhzmr5vZPxzqCzdxUQCZ0xOGzEoyWW4qkXmfC0/pRF7UIjCld1gMDM kHZgD9XnIMhjZDizDwNZAvG7L+oAn0qy5Qk9E9Y37Ij43R4lkb/JH8yzTBtFDQenCnwj1vRCU2V Cob3Etxd46EBZuFGMRWl3E4+8PXbkFvQgGN/tijHyuEbdzM2hXE7H5Tvvwhrxc0RCo823AFTF6H nJTlvJpC/aRytiivYb28jz0/GSJJT3LHU5ClNjb94UiVqpc613Iqt4XxSHRhIi90w0rYGzEMSfo QM0/Nf0paHCre/YPRYdJSLZEOGIA54Hn9QBQg1F62KGEsQWAAFI0vns1ASN1BjVSJpplF3/RkU1 CpxRpLxur52bdT7gkl5SKzN+5oeohm4m3vkirFvv3s/xhFCbjaZX/ZiVY4eshqGO+JyoZ3YSqF6 MqE5zHjBmO4GM6MiiiOtYqqy0JMTCEKUU4Cted9BkJSoYLUiXp6Sgt/9cG7eiIeXVTXFhoh7RaK ER55iyBRiqf6chfJLWiohHRauYbMTK0f+NEMd6WfQWl6rMfzIX0jrO7zzyjrQlCdQ8lphRqn0Yf xUTKaFT1fvL3NAA== 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 --- 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 Wed Jul 8 15:50:52 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27316 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 84775C3261 for ; Wed, 8 Jul 2026 15:51:31 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D4720660DA; Wed, 8 Jul 2026 17:51:27 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="uW9vr1HU"; 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 22D9661451 for ; Wed, 8 Jul 2026 17:51:15 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 4E6E51D18; Wed, 8 Jul 2026 17:50:25 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525825; bh=hiz0uT3/3AsxSoMkNhh5IRTSxtldTuT51MbBXVsOjgQ=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=uW9vr1HUukbrmCNUG8Ml/lc2+GBg7uW6U9YLhTHIP8DZhtNTyZx4ol6T2hl5M8+Lw H7dGVV7NL3FCHHkEqDeriB1SRnMJABciacDDiXWGTvwYronP4Yq4RTYxzdi6k9xh/P 3iFqckI7gSzkal4BjnD1EQBV/oM6w4kS3IC3nCq4= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:50:52 +0200 Subject: [PATCH v5 10/36] ipa: libipa: Introduce lsc_base.h MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-10-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=11124; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=hiz0uT3/3AsxSoMkNhh5IRTSxtldTuT51MbBXVsOjgQ=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHjQrPSvmXE+aqDj4quKI/Bqw0ulGky8z8UD 8UOawhxw7mJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x4wAKCRByNAaPFqFW PKoaD/9CkHc2Cx477b+zMnrhgBhGgobnNOGDuqMBJbP5YljkJZDG52kvagH8769/Uzli7fyCT5c tsO3L/S0HlOMt476SJx7Sm3Pif1hG5g5cJjVZc2myBltcbl7VL+5oBUefGVamyJkngjS1OJjh9P i+icSgz00jkkBclhz4RdIEq3V/dDoY3eLoUJfWmBDO3VoT/TFIOg3ZmDToEgN+YmHZutFRzZN6l viYtBEOgeZzqbCTRmhroMNJjLd/XoPVIbSylSiKgbrpDMefMdChGCrCfV+ymPpCTwOyh9eSnnQb 1V5FBdeUPZiHhdQj+Xue2mN6GNur0oPQjJU85HnUCo8DUPR+beM9RKkGCFjxXz12hCVxHGZwZEn Q13LNIuF2gc6QYax33bxHTpl2vJQg4LhF0ek1U+GE9uzD3srQA1XZbLfCZSSmIQF9GGZGmPDUPZ XcsHxNwXlYrYc1v1XdfhudW6rZ+QaXiX9H68zMXQxUZGJO7PNrXEVnvEsoDReKYcTaNTnBpBVBb 4Nc5Rvb1SVdBu2E9xzDIjQPvS4Z5bVLTVyk4UI3vXeTn2KnvIGgM2Vj6vHEDW3RWWvdQOkEmEq1 lIPN3IRfIOoqCgYvN8N+S/SsGMe+Q07f+WnG3gogxYXq8f20tgYnTi5LtcIK28THrf+lDPX1ysN 7eDbEvGNLaVKAWA== 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" Group in an header file part of libipa the definition of the lsc Components and the LscImplementation interface definition. Signed-off-by: Jacopo Mondi --- 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..d05075b988d7 --- /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 algorithms 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 a colour components with a list of gains + * + * LSC tables are defined as a list of gain values associated to a colour + * component. + * + * \var Components::r + * \brief The list of gains for the Red colour component + * + * \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 light + * 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. 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. + */ + +} /* 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..93f819d3c2af --- /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 algorithms 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 Wed Jul 8 15:50:53 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27319 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 5637FC3261 for ; Wed, 8 Jul 2026 15:51:35 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A4BCB660EA; Wed, 8 Jul 2026 17:51:34 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="gyQ7Kcl1"; 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 630BE660A5 for ; Wed, 8 Jul 2026 17:51:15 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 93A3F1D27; Wed, 8 Jul 2026 17:50:25 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525825; bh=rFLCmzTAiGZxfdqplp4EQ86F28eJB4eSKIzc7E7rlXE=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=gyQ7Kcl12DQqEYXYB5UgERb/Z2TwDFWmTUOz3KFL8UgrFKwNN7ZAUHovyQFvBdgn3 cqwfna3s0/tqXMVclhmadnwsgFvoPONFJ+rXhwRA6AIRFtVhzDiCRlZQOcY0Q42Ld8 sCBA7+NeVA/hRQ/UCoMGrZzw20UmjgyTldaagT9Q= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:50:53 +0200 Subject: [PATCH v5 11/36] ipa: libipa: lsc_polynomial: Fix polynomial parsing error MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-11-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=1278; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=rFLCmzTAiGZxfdqplp4EQ86F28eJB4eSKIzc7E7rlXE=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHj2cTRs9HXA/ikX2koXWd8sDF3uvSLiZCXy 2OClU0f4COJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x4wAKCRByNAaPFqFW POm5D/9wNVfbCcJWHsJRlvWEOqaEtXVyL22s9BYbzZGDmnisxmP5evQ5SsaaT9IWHU2HgSxxxXE EYPXjBHgrEkTtx2K1ckzbca3+V3PqOtzudAr4BJKOaUUD4jd92p/1jVRu7zroPDWyTYFScg/BAO 6TE8ltBFQnla5rPjjomJSJIqYjVOHNxqYs0dX4K4eRKFUi28m2ZpOYaVgfmP7znQLoBGAUYgpM/ aHPeJlm0JTO3ISpnl0ZwU+MlH47ZRNB5AEFRRT/Agpi61D1H2GLoEIFu+2OqbCm/DScNGdILm0i wxK7NJiOs02MaQMMpxtMnIdBmJ9xa3MtlJ9hyvJtFtPUA4S4MIPQeDfhGqyVkVZa8xzgjIALzr2 UUxaR9e6DWi327IY2/IPjkFHUrmvSkI+4Fpw39dwEJgaNQi/wekTcOssf9tLyT2h0bZxcg5wq2I rP2wwfPbbmn9nEFBJFTDFF8OTT7yH16+J0qcWOJ9k2sPMZFSfL4Vyq5PY8U+Z9PfVGLzx9ZlroE 35qjnVyDmoGaR6RuvznENDUp/EZxhkPEzngh7r9A7Y8RbtvN2LhUjoiJVvGZqNfRN8JBF+mGwsD SVk9SsDmKE6Z0XgzRCy33PNvacEN7QwyqNSbn0K5Iiv0GNsX9+taPJS4ddX5LIj7TRm2EONw3iq rCgIgrWKZnws8Vg== 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. Signed-off-by: Jacopo Mondi --- 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 Wed Jul 8 15:50:54 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27320 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 89E91C3323 for ; Wed, 8 Jul 2026 15:51:36 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id CD46E660EF; Wed, 8 Jul 2026 17:51:35 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="SriVvzXI"; 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 B3C5B660B9 for ; Wed, 8 Jul 2026 17:51:15 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id D4CB01E3A; Wed, 8 Jul 2026 17:50:25 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525826; bh=yXwIPDDYuWnxiEQ7pfsAy2nzJEmWvfcuWcpTAAq3aio=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=SriVvzXIznAtE6Z/itvzyuHPyAj2LBM/JL7tDx7ADH15I/NkeBcs7oc5Ojs3VOny2 ExY3DdtjHU4VIEIoXGg3ChSvk2HqgJpWh98YoVy0fQsVhcys3L1vUkaVKVmJ477N6a 66VwTE97y+v85Fx6ufuTZfZquZw/DbltWFjatOrw= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:50:54 +0200 Subject: [PATCH v5 12/36] ipa: libipa: lsc_polynomial: Don't inline ValueNode accessor MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-12-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug 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/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHkbCSP2ITtLCo8waEz3hDkkk0TOZLUc76pt D+ccNv2f5yJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x5AAKCRByNAaPFqFW PGcJD/wPrWSHFrQNrcFw4Pe9H6wrIX9s7Qt6G8ez14AdtcrnAZfp426zI32ut7bLLmoq4Kv033F hmZ2O6jKoz7OcrZ4Ql8HBSbXryAb2r8dkekZpXPk590Juxb0Oyjsi993D05sU9nPHScyogf1RiL MOgymCEftazvb5o2K5gBTnWuffBAGNQCr00MkwVjrk0RgRt4vH5bZm3IP84vIo7pnMF81adIDw1 5M4GimxyAHKx4PnFotk/humBbjDTgXzLYnzzIOf2/aEvG2luEPihAiX0MO9hekiHmAIfQlucHi7 zBN9FHwL+1BKeO2FrRqVaR9zrrM1VujO+phwlu0MYrkom6eqsZg6M8sh6gRKRF0hFNTl558egxH LAFnLCMmKfx60BHd1hhYXPN3lmyyEGUaDr89GwofjJpbah4gIK+41ZKepLe7oK4Fim2OdW59MKM J3TBE/eEn5CVSPKpmXqhP1WZkRRNM0KkyI9d/KQbMN7sGgg8ulCI8rxhol/qrKBrSwiiqKMPYrw vacuLZvvtTF3tDlEFEtDJfQ+Ukpmp/lzhvxJCOfCHXbZVVB7hy2o0PYr1bIFAoDRgzVmtyCIKeb g06U7w37k7lT7BjJ7FPJ3rvAU2IfyOXGIQzZYtAZz7IkttlygKjWn+xRJycSQG9wU4Z0vFRuse8 YCUDzDuSuz5ZtNg== 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 Wed Jul 8 15:50:55 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27321 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 08BA3C3318 for ; Wed, 8 Jul 2026 15:51:37 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 812E3660BE; Wed, 8 Jul 2026 17:51:37 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="KQHTQgkq"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 31C17660BB for ; Wed, 8 Jul 2026 17:51:16 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 5C8CB28CB; Wed, 8 Jul 2026 17:50:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525826; bh=7uJ7nJvYLEGDo9t0CiFQTeXU/IuOD74DEL7USRhD4Kw=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=KQHTQgkqS9NEmRRWpeJCPV9Hfzx0tYNlb5tHOJTPe28wJsMv8xE7pW1/tA+DClEOT OVMMMYsVI9WBdRv99pZ5L4f+AV//n9UObcRTRCoP2fiOIt+anWHKGX000iY7LTnotw mTnhLkGwOrnzokTZAxngDAolTcX6CMwZexQxzLfg= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:50:55 +0200 Subject: [PATCH v5 13/36] ipa: libipa: lsc_polynomial: Rename LscPolynomial MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-13-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=6248; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=7uJ7nJvYLEGDo9t0CiFQTeXU/IuOD74DEL7USRhD4Kw=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHkePoWB9EgY6pVDiaL8VGbvtso88nLYEey1 csIp7Cf0SiJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x5AAKCRByNAaPFqFW PEl6D/9Nuezq+5UJ5OwpRYp+s1fnOUc3vQK5xiI7UXh9ijur4RvxGaANCBjjTEqtWgjUK5POGsm RCugB6HbCW0dX51eMOShHXR3Z6nKJvn5kam/BSVR9WATTNgto6LbG+AYZBj0AqnuarmV8Pf1rTX MHRmu8iSOSCKN7MSw/ItDHwOpFREotMdwt0BumnUAylZOOnBQuE2wx0yFD6G2d4nbDPqhdRYiIT 5SyQl1+mnU78TIjAbXnDHXWymizTpXeTL9fUoIYIxYuOmXkSOiVAvr0laJ9g7CPkJ5doyUCRpd5 YDWb+V1jkPdqdNKJY+qP0NWA4iD7nGahs4j2YlGFhyb0RSHxh0FcKqdg5mzAc9IhoLRSjZXo3ai d75qVhSd/de6sFzOImNc2uZpGQrBu+9w/aVIBG3nMvkQ43nr87tWIVmQGoT6y7d1nkqlKDhUW7d JjUYv8eLtDfhoeLVMnrKgj1UxllQwf+lZqtX662tDwuOSmNqHgZPMN0nDNdGkDkGzNtAftFzRto Ytjv3Fesspv9oMngWa7Y7HQXOpUyigz5xlr8q2BijjVfVzWX3tIGx82M1xIUdyzD69jhaPPK6s5 kuptV6HmiICjqn9eYJfrNlbLxg4cleGqefTO8+z9pCF6N5lV+EoGOMuFhZlGOrWguHtDHQhyW8l ImYieOh1dkt5GYg== 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 --- 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 Wed Jul 8 15:50:56 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27322 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 8C991C331D for ; Wed, 8 Jul 2026 15:51:39 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0977E660F1; Wed, 8 Jul 2026 17:51:39 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ikdKjuiu"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 8EE35660C9 for ; Wed, 8 Jul 2026 17:51:16 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id A2FAC3F97; Wed, 8 Jul 2026 17:50:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525826; bh=5k891vikBPfm1WiwjQEALqn9lIwdCnR+t4kgIvPiJug=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=ikdKjuiuSRIWBczLBkMOQUi/1mqgWi557N2AToYv0AmI5n74TwFnn3Nt9Jai16LKJ BMfLivG8u7ZgeVlAL+z+tO5d8P2EsN5yfpUswTMPuklRpa1GZgg5qi6xvG1swEUXJw 8iVkGpo80cv4i4kE4R67OHykachly7rpPfcpxB2U= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:50:56 +0200 Subject: [PATCH v5 14/36] ipa: libipa: lsc_polynomial: Do not inline functions MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-14-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=4030; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=5k891vikBPfm1WiwjQEALqn9lIwdCnR+t4kgIvPiJug=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHlOrZTfGplGpQgZZ9fC/QNgULvQKTvN3gyx UfSHEM1SOeJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x5QAKCRByNAaPFqFW PK8UD/4zAqVMdaWDDmb779sW672QzNXARSZYSDHIaEo9OSfZctWi5MPIZ+bS31b4Nij23ljiTCw jc+p3rw0oybMPc5saSaZE2NJcrIQZQDvYcILRu0zAwrasyYEOaMFR4yccWuXcDy9dlyxgazQPtO 1lefqwjapj5PEq7XiUSJTsUpyoKsFg8hBhW0DLi/koNB+1P3CZ+ErI8qoLZZS+nzeQ8NVWQDwQ+ 0Juo8HboKCr2KklNwx+nGCdAi13gjH8z8hCTabU1iXGwFKiiKqAtRmziS6Is8MYNaehq6R5yT2+ MAkeRAz5hhIlM7aRiWc+lR1QAvXFbWIZ8NluFaC90gcnY1tGAIEbaXIrjAFGMYKN7bgTPIaKImb dZrzFMcjL8idVwlsNcQyrFWHZtDley8Zz69HlT2X77SCVI/0SN7tBOm2J8BL0rpVWEFcBT5ICfE LnNoXvteNB/qjONf7IkuIUgLeQTUbmFX3/Ix1x9Vhzkkn0/xzmzeE4szfNqprS9dCVzbao7hbMF VRy2gg7itjGFqL1fjB6WVTuupJeCk2sTpX01f/t/AH5g9vva7XYTE5x1eiXzCiAb5MS+/T3gzzq 00El+rYyTGfRNzPqSxRlMz0z03gxZSB/ViijXg9pFKIl7vz8qtthOpUSKsG0Eu0zr+CbjDTrCLy NxHxdU3/8yMRjIQ== 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 --- 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 Wed Jul 8 15:50:57 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27323 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 C1655C3261 for ; Wed, 8 Jul 2026 15:51:40 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 49B0A660E9; Wed, 8 Jul 2026 17:51:40 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="NWMWxTtY"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id E0C4C66094 for ; Wed, 8 Jul 2026 17:51:16 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 064303FA5; Wed, 8 Jul 2026 17:50:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525827; bh=FI6u9HiVyZE3mGOS+nR17xOPN1xl6wtQAM1eP5szdPw=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=NWMWxTtYA9rhYlBwHItlw4Lf2mdty2Jex06SviJQviV83r0dcNQkdB5Nsb82sICnP jteCDvovNGr7BNOdVthxmgSCJobtqvHJqD+7kL2/X8qZ4TjHBr13CLNBj+7rhiB46Q 4O0mKywQRBW79Y/gCSv6a4aq/jw7elOmX7qLz0Jk= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:50:57 +0200 Subject: [PATCH v5 15/36] ipa: libipa: lsc: Move Interpolator to lsc_base MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-15-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=3329; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=FI6u9HiVyZE3mGOS+nR17xOPN1xl6wtQAM1eP5szdPw=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHlmC/PICoFh418AoklK0rWHV8yG89KsFeIq 0E0GqwZoFSJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x5QAKCRByNAaPFqFW PPqHEACz9xQBBJZ9CserFVgnC8pEwV5VoZ6tfKTcId9F1IpNOwuSXxoJ98743k9SWDvk+ahlJSk dI3yAkS6EqOcB8jP83q65JvZW7aATHs/q08Tu5gkifEp1EKZ+/Y10QS/UPdW5I9wJiClk55YuX/ XSKaZYgOv/GvtSHQ56cCKmIrcBaq89vdq5oj88qmgSVQ3uIWiGpH82Hyi8AiTCPJdITXhYbO9Dn z4/NoxZmRGba7AbutSpF6U/p11pxU1kchWesAmGL6n9XmkZ04kaA7QL934tnEg7IXpWC7Ah3uPE 8+Jkv9mQbaq5eXs5f0U+5Jyyb7QDs924dOdJL4YX4k11zh4dktu+Gzm4H4+FBF4bWJh8keubMDD HbcQB6wPXtBX1yMuss4I5ywMXT3xoA6zeI7FqGGILQ0ReMZjoRLwoV/NqTFkOUq+R0gWC+lMmBS rwZN/TCtDx3vzjAK4kFWGvBOwBgUm5JGoeqZqydl4Q+5OP14EyRPmwQhTeGYnjQOTy28bTbXTLt Qp9+lC6Ti3CxqEgwX6wiNPrOPEweYb7RwEMFYLrIPmE17mAYhdc9oh+J37aNmpEfpZI9MqBQlnf 5Mz2HJj9/BfLBgelsqZG5mUffgB/V0pi65W7RzAVd5XtE9Wu86FgAe6whX4GylAMaii7uORYQzy 2qCuQh8Ymh37QSg== 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 --- 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 d05075b988d7..945020794b57 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 93f819d3c2af..51cc35888209 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 Wed Jul 8 15:50:58 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27325 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 EF380C3323 for ; Wed, 8 Jul 2026 15:51:42 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5EF2866100; Wed, 8 Jul 2026 17:51:42 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="UdSJogjY"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 2A083660C2 for ; Wed, 8 Jul 2026 17:51:17 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 5539B3FA9; Wed, 8 Jul 2026 17:50:27 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525827; bh=paWyK9DQsOR+SgPmSSZW4pV2/QQ3ktSeNE457DY+JaU=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=UdSJogjYGh4x/kBVtslEQQmvIrue4FQBAa7pcxvG1G+/ilfIhrlrsLZt3FaJqgXVV k+0Qz/UwOqH0+LfNsgcbKw18YJ04y+LR4BQxkoVotA26qC075389QW5hQtcoNWD6iH PyTQWVsVSEAX7bv2hRAOhPFc+ZGatVpApbxLo5Lo= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:50:58 +0200 Subject: [PATCH v5 16/36] ipa: rkisp1: lsc: Remove rogue empty line MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-16-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=601; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=paWyK9DQsOR+SgPmSSZW4pV2/QQ3ktSeNE457DY+JaU=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHmjUmtgBrNhocd73D4kU0FnkIxJ5pFTV3Cu MT5QyLaFvKJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x5gAKCRByNAaPFqFW PIbNEADA93gdGf15xsJw5fgz3aBvsROQXt4TGQp8jM9bMV5wG/mc/NZZns4QADcU/ROyEnzgOKe oZPQlgdk1aril9KumjmuZeoe0QMQdi0CptRFsjjO/y0YmhSOFwKgSuevoDoRU/N/H+o6x4Nn6wj wFEyKHw2SrDtE9VFNux8IYViGC57LU5BPpjLdDSM/nGBJZAkau+X3J7KuQS49jz+f8TmMoHWmED XAhK0WB/DLK37CjYpVvFimKIdD+Rcs+CAE4xQB0aHiq/MRX02W42TCDHnt2NfpVP8uwfzYq1FzM qdfVIOasnACHE4ixWcoRUEj+5PIjMz1fvp1g0xquAUrrdDR212uyXyD8rGD9A5li/5qzHuRWwnU JMHnl3M1ovKFkcVctR6Ob0aylehRqjQ4ixvVasm++akg4imFGdmc1/uSkyLp+4kkeEvu+jgG4J9 ifttraOm+vItbp/JmOXiPF9sKEjiDQBzfrDpD6DVU8ntRUNQTjGnkYEMxgQT/kJTbeOsZ8FeRH7 HlJF67BW8RimAQCHkphTTI5wvr9tuB0V2+KGN5yPFCIlPsXIZlJvqW6oS27YRXWjIH3ZAMurtRv eVBUymN3P8uXa8TCKvYQAUzEliAk/bhfd2JMY7aPu/vKW15gFIQFVTgcstyO8om5UBYkRvwd7Dm aQ9xrvg5tHVjIsg== 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 --- 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 Wed Jul 8 15:50:59 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27324 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 E1FA6C3329 for ; Wed, 8 Jul 2026 15:51:41 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 59A72660FE; Wed, 8 Jul 2026 17:51:41 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="swiXskrh"; 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 81FB8660B2 for ; Wed, 8 Jul 2026 17:51:17 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 9E9783FAF; Wed, 8 Jul 2026 17:50:27 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525827; bh=qt6i7bjo/tb5NGVMolwXV7Jjuj2XNAjUiBC6pSydVac=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=swiXskrh/bo88OCcjZiLxD35SQrsX7FAiu21P9VDWrmHWaeWIYfeZa0KVJoJUIPci sbHUy+rKyqupq25moOtyyFzcpHbVEtlwGb0N7FRdFPm+kzOX7fCALm+EkecxgpMB0Y +VNQho7UL+8l9VvGEiO2Ti106DFBAwMqi5eQM7uA= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:50:59 +0200 Subject: [PATCH v5 17/36] ipa: libipa: lsc_polynomial: Move LscPolynomial from RkISP1 MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-17-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=14483; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=qt6i7bjo/tb5NGVMolwXV7Jjuj2XNAjUiBC6pSydVac=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHmcxkcZvnHrdvHE/u9BpFcZlx7JE963e3Zn N3mpTUFffeJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x5gAKCRByNAaPFqFW PAQwEACjMoVRzm+8A7pwW+X8c1vnNMsBD+l/5gX+VKN5+253Fii+CKi+/7kyD+5kcgO9bBWNX0W 9YhVQ9o249y1U1HsGfF/aQ4MUrrqWBApJG4u/t6PHwUVWOmoyzGxAcpPUYP+rw9vAHvnr7yeePR GEGsAloUnS7naulBFOW74Okz1OU9w8TskrY+2NseAdP0RBYQn3TFEHYA9Bm7vucY7CqCnZZDyl9 +RgXPpcFK/WlFJCUJcsWJ5Tfk88y/qi/NwXFtwQqH+/T2Q2tA1VISMYAXjH9EBOLkGW86R7Cft2 BuDIDMCYhkeHighk9dbuSVtf21QTL5RUjV7FnRRN1J6jtAeRvxLk6GptFYag5uyXvFuWUwlc4E3 LfWt/XNTqOKz8Z1cBkwOquO7U0pUj4bY6D5MJbf4HKzrZTlnRnJRHDTrAs+lFW8h5DW81BLG0af T/Ap/jAViAITvE0Y8WxohrkOHXNrzoIYzzfT60X6sCX978Wx1BpqKNAlD/oj4XCW9TNwSI6o/e+ N9gDhZ5EvUOXR+eON9bdnYiGA7WvRxZvXSaRHSonYQDEHK7aNOFgXrGNloEA3cWoxZLVV9SR7CE loq5Q/F5Pj/xWC69mPk1wwOzNvXPQoaQX6beUsNAxLX28wbFH8tWDwIiuLAgthwKhgmR9A2Gu9l BvebrIzBGzpV0zw== 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 --- 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 Wed Jul 8 15:51:00 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27327 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 2AAE4C3330 for ; Wed, 8 Jul 2026 15:51:45 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id BF2B566112; Wed, 8 Jul 2026 17:51:44 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="NjuNL8iH"; 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 D83BB660A7 for ; Wed, 8 Jul 2026 17:51:17 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id EFCD93FB2; Wed, 8 Jul 2026 17:50:27 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525828; bh=Xx5zJIB1zkJdCGevazKOxH+Pfpu5Y2Nin5HQC5SwJro=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=NjuNL8iHPORAO0SZhRMeWNOWX6qktSnsPdu6M4Auffn9SdkXt3FhLuT8TsqwJEu4r X8VjqTRLSdpQnIfCso06fgAZR1x3B1FPzn5KdVB6KrKcqmRtZfEqGqWEyliiGEHxmQ 0RNFPMq0namsx2LPg5mS46sAIaU5ah6vyyEY2GYg= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:51:00 +0200 Subject: [PATCH v5 18/36] ipa: libipa: lsc_table: Move LscTable from RkISP1 MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-18-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=8313; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=Xx5zJIB1zkJdCGevazKOxH+Pfpu5Y2Nin5HQC5SwJro=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHm2lXJhzaCYOtMDp/oPJJ1zKVenT5DTBlc8 PwYIj2viFSJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x5gAKCRByNAaPFqFW PJ3/EACnPXG35lF1d+9gE1cBshVPUwHJEEeHBdr0AwGq7kxx21zNF+4hNN/LIV8Fiv75GqxkkDp wed3LU3Xg9PWkxcSg9Ia6CubCtd2nR1meT4laDm7KSFElSl7ZWmuSkSQb2Ozpit7o+dySmdHJwe qwGuAkfPi4pTFfvuWUg4p1TuX3/GnbTvnWqziuDsPaW2iKghy8kwoLwnbqlVsH6iYjHnKTguLHS Ket3qr5dkqVKzBkEKx6vk1os8wwJ+XpXAusnAhxBmvJvL9ts5e+XKBPlLXxEIKIEqdaYwXJ0fpS 5ZVeHv3zfTEUKf3eI+JjsR1eBHYTu7ri7BIpX5VUy/1jPignUNSBxh+HOq+Pn6smrqgJt9kNRDq zsGn1mSIhEz6L8RRP62hUzPj3+Y+JaaVEqSqBmkH+rs+do7l5jImaJb4sbkSEgu8z/pUdtXfb+D S8znAoA7ksY41LLk72h752jkVo5/5Vi0fMfksn2hDlmVaXp0pBbuFjmmdtxyiGRdidFEHzS2Dus Rx8CqIeXHV2KaYzhacudYqQTzWppDwEFOdW+p0v+C252fLAiK0/6LVy4RLUitr2KF06Z6GQsj+6 g1YlqAS1pV//wMTgROw2MvqMRACl4vQRF0OsKnqrTsOTaseQpn3sENr8PWynvg9br/06UZdNx4b 683e+k1ncNSMrFg== 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 --- 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 Wed Jul 8 15:51:01 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27326 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 0F328C3318 for ; Wed, 8 Jul 2026 15:51:44 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5F486660EE; Wed, 8 Jul 2026 17:51:43 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="c6yXRBU1"; 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 213BC660BE for ; Wed, 8 Jul 2026 17:51:18 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 53B013FB7; Wed, 8 Jul 2026 17:50:28 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525828; bh=ZQB2/8/lfwl0b3X1amVOEn3MSG9P/pJyN8UbLOUxTSE=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=c6yXRBU12MRAQal9cJREuD8K7cfaxLslgQ40NW4mg1oPIVbhm36Brnm8tI0xc6TMu r2Ui6MShe5/oMdnh57lt+VDCFx4dVywTh5snRgOjl+XtKxeDLv8+pZGi4iHyy+aSQh jFUU2Te/wZDA9bFMdnRcDv2+M29eb7OC7Zz9KPIc= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:51:01 +0200 Subject: [PATCH v5 19/36] ipa: libipa: lsc_base: Add sampleForCrop documentation MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-19-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=1459; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=ZQB2/8/lfwl0b3X1amVOEn3MSG9P/pJyN8UbLOUxTSE=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHnMiLNSER5oAgaxDfZZp6izihxUAE6ik4su 88W7ogQBL6JAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x5wAKCRByNAaPFqFW PF0xD/9mZ8rnC7bCPXpHWGrsN4krpDftJqgx3Oz/AaISJB7bJUtbqXO1W761OnqgpG8gLGJawtR Woc4K6xmxcxj5MTUir9xIBG+ELITkzkW87k2uzRPHFU4f+nb1DJmW6UPVnvXK8iCkErGWjCqSeP TJzn9x7/75PvfMziVi3jZZamr89Q+GAHg4hlC08VRBQ926ip76PYXOrIHbB9F41DXhMWAw2h71f zWyksXBxccuAIzfFWCSMBCojYJx1AwgATUp+Wt/08rWkm4jFMBuVQJ+BXwka9w+ltAbTDolF5vy FmaqRQsu186QtOX/cUwbFuoZRy6tceVwYEy1URtaxOJG7+Q1d6dwBhBJguJk8KkxAHz7hxLqLDG RTTIx8yRl4yhtK60ZMeLZpxe4RDPIcdlQCXKGfMjrEqsuyl5HuzTTsP8x4kooXKV0egnTTMe+9E 9v30Ex8zAG80y1Cug6l8RF0WgrPBKA+mDrjd8JPgyOp5Fiz5bjo3lPKHtnEEyH71kUs6gDy+/aW HSYgBhmAO5mtmLowa1LXQq6NAmfSyPgT8ZlInJMmC3LRvDEdB0mUk9f47l1JPZ00lPl2kF9gc4I 5jqi/cK+X4TbcQKqId7vuqDYxj3mvgfKHH2PdQmFYf0pKoPoMF/24RuaSHCzJDvBvFSoTzFfInF AjAe203yQmffTaw== 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 945020794b57..678ba8a9a808 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 Wed Jul 8 15:51:02 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27328 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 8D930C331D for ; Wed, 8 Jul 2026 15:51:46 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E920166119; Wed, 8 Jul 2026 17:51:45 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="FZYcuV6B"; 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 71583660BC for ; Wed, 8 Jul 2026 17:51:18 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 931FC3FB9; Wed, 8 Jul 2026 17:50:28 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525828; bh=pxt5DHoOyWUjji1QJzKfDgjBXNlzEGrF4ewNXFDC9v4=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=FZYcuV6BTdesC+wQWxFwslmFId7pMPNe4j3uv9VSmrhq4007ry9mx5AKDmNj5V8PM J0bOvxvig2OkKRcUJ7Nyzigv9/Qq59/fc+kuIZf8lDFuf7ORPPfmPqQsZx+2IxVB4h m2+hSxR1sZCXdADD5MclMLwjV1uJ6EF7t2cHZiLk= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:51:02 +0200 Subject: [PATCH v5 20/36] ipa: rksip1: lsc: Make parseSizes() a class function MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-20-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=3547; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=pxt5DHoOyWUjji1QJzKfDgjBXNlzEGrF4ewNXFDC9v4=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHo7ErOyP0ef+58CuJFn7xZxom9baUDpV+8D azvfNxZ0z6JAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x6AAKCRByNAaPFqFW PGSTD/9RAcCaEnpMxNEl8CAOS0uGmFWjeqVsUQuvJ1P49Jcmi6t8k3HJ0fe/cJGBvwnBCmS5g2V JDBWJE8DPrTVOteqTT78ZnnIEMiNN1QV8q93jWb91+ViQJLQxiQgSsFVsMEHXv1u/43S4AXwa9K iWESj7Xj/JjTVJp5JVu1MFW7tXSe57zJBFTE3qUjM8/U5cVc1m8Jv3E6olVeGWZN3lY+lJ6r7YC 5RCTlu7CN8iwKZLp7EX0E2Sm6znvo9rPLqtF0jTNLFpsVWZE/l/VJibv6At2PUybwpW9DYeaV6j a5j9xpOJWDOX0mk9cQm4wM8O75b2I0KFSE63qdgDLyJuAMvmYmqqMw8oV6v/cA7rSRzLFfH9iIz EprLQMmt1yc8K6evItV+K4jMY6xv97+bARsJ2kMVsIfopJQzwk7cXoSyrQFEp++nrRhs5NR3zrt PKsxmxoOTpTWhUJrLAM+Ne9H2MyduDpv7GsYjTiNv4hTA83hlIDYxvzo4EraSnzPdkC+O/00RjW YHtZnEQivYestQS4wcN/L1PY3rtGvnJqvaRjAQUjn1NlrC/orRdteVD3Ur1tXE+zZEpsVADcQ/L 8xrCsMyiiWZx5gFRblhpIs3mA1Sp+57WSTNmdyx9IKQuZsJVwvAmRQIy/K4zrbAxs1/a08Fyz7c 68gpyz5Ael0adqQ== 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 are no reasons to have it as a static helper. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham --- src/ipa/rkisp1/algorithms/lsc.cpp | 60 +++++++++++++++++++-------------------- src/ipa/rkisp1/algorithms/lsc.h | 2 ++ 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index f32c46084dd1..e03bf361c4b9 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -34,36 +34,6 @@ namespace { constexpr int kColourTemperatureQuantization = 10; -std::vector parseSizes(const ValueNode &tuningData, - const char *prop) -{ - std::vector sizes = - tuningData[prop].get>().value_or(utils::defopt); - if (sizes.size() != RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE) { - LOG(RkISP1Lsc, Error) - << "Invalid '" << prop << "' values: expected " - << RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE - << " elements, got " << sizes.size(); - return {}; - } - - /* - * The sum of all elements must be 0.5 to satisfy hardware constraints. - * Validate it here, allowing a 1% tolerance as rounding errors may - * prevent an exact match (further adjustments will be performed in - * LensShadingCorrection::prepare()). - */ - double sum = std::accumulate(sizes.begin(), sizes.end(), 0.0); - if (sum < 0.495 || sum > 0.505) { - LOG(RkISP1Lsc, Error) - << "Invalid '" << prop << "' values: sum of the elements" - << " should be 0.5, got " << sum; - return {}; - } - - return sizes; -} - unsigned int quantize(unsigned int value, unsigned int step) { return std::lround(value / static_cast(step)) * step; @@ -138,6 +108,36 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, return 0; } +std::vector LensShadingCorrection::parseSizes(const ValueNode &tuningData, + const char *prop) +{ + std::vector sizes = + tuningData[prop].get>().value_or(utils::defopt); + if (sizes.size() != RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE) { + LOG(RkISP1Lsc, Error) + << "Invalid '" << prop << "' values: expected " + << RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE + << " elements, got " << sizes.size(); + return {}; + } + + /* + * The sum of all elements must be 0.5 to satisfy hardware constraints. + * Validate it here, allowing a 1% tolerance as rounding errors may + * prevent an exact match (further adjustments will be performed in + * LensShadingCorrection::prepare()). + */ + double sum = std::accumulate(sizes.begin(), sizes.end(), 0.0); + if (sum < 0.495 || sum > 0.505) { + LOG(RkISP1Lsc, Error) + << "Invalid '" << prop << "' values: sum of the elements" + << " should be 0.5, got " << sum; + return {}; + } + + return sizes; +} + /** * \copydoc libcamera::ipa::Algorithm::configure */ diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h index 2a428293cb2e..ee037353bbe4 100644 --- a/src/ipa/rkisp1/algorithms/lsc.h +++ b/src/ipa/rkisp1/algorithms/lsc.h @@ -39,6 +39,8 @@ public: private: void setParameters(rkisp1_cif_isp_lsc_config &config); void copyTable(rkisp1_cif_isp_lsc_config &config, const lsc::Components &set0); + std::vector parseSizes(const ValueNode &tuningData, + const char *prop); std::vector xSize_; std::vector ySize_; From patchwork Wed Jul 8 15:51:03 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27329 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 AA99DC3261 for ; Wed, 8 Jul 2026 15:51:47 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 4F203660DD; Wed, 8 Jul 2026 17:51:47 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Ni+mNbAH"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id AD141660D0 for ; Wed, 8 Jul 2026 17:51:18 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id D890A3FBB; Wed, 8 Jul 2026 17:50:28 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525829; bh=8OCi4uYq/mK5icgwliwM3R+3rB9MckbE7JtDIY89XRI=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=Ni+mNbAH7h2XCyauap3+ELLWBEZSGuF9koyyEyz2Q6w/oQQOpWxPhvIqH6dWpZb06 2FYu91cchmPeyQPDmuB+fSdfrYoEm/aeL2T8g4stDLpwlWCrJXzaIRAxHXiHCPTMfr dp1BiHRE3buME/2XC/M5dz5jgHNYTW1t93aGW7EM= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:51:03 +0200 Subject: [PATCH v5 21/36] ipa: libipa: lsc: Introduce lsc::ActiveState MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-21-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=2220; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=8OCi4uYq/mK5icgwliwM3R+3rB9MckbE7JtDIY89XRI=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHoL8041rSkbgYSZ9Xfss8HYL+YGhKlImZm4 gWd0xFXT4GJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x6AAKCRByNAaPFqFW PMyLD/0Uy4mt/OraoVm4kvWz0d/63rJbGvvOWUPMZso5Lo18TpuBVrVQUJDFoZUByfBvuO/4s7G lVv066niaXSChC1M6yjafchUWjPpl+J8a4271G58FoHiQHOjskiBzrBh3/+hKUU+vufy/rgeJKy Xj5fEEfLve89hJwvIMr2KgL0WtaFKIkkZhVNnGPIGAhWbrHoVn664qyRVEkaDiqH97UbZh7WuW7 sm5u9I079jZTrJH0+zd2iEAiUQHCpB+AICM1u1b69Fjbboo5liM1fllrjKpUO5++WEzQrD0X23d jKN+bUYjtj35Eb+cb1/AgAAJ7+HbM6KftIFZyUFKM9Es/i1jSUdJZSWInPCftNn8ENxiIjLynP/ YTj0QGBiV3JTjBhbK03wDfbbX+EfGBahjVjHQhZI56NwPvR0624+S9o0yUEve7jYAhxMGCdgI40 uBaZwLEmP212KE3aoPvGq1GsjLEFa7Wxg51nCLuC/6PK+VzyXhltpObwkMJHotF4avwWrF6+sFE yXM8nIVZPKKJSZGgVnaHMUXrRGU6FbvqRkJUzubn1mWPU2szdcuj9/7XpIkqaPsbWT4Nb41bkUX xVfs2DOTbw5a4MNKVyZ9tJrg0B+Id/328mKB2E535UOpBlRU0YNK7QmHn8Z44pwodO8RhYSsxjO EXHcbGD3D2P/dkA== 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. Signed-off-by: Jacopo Mondi --- src/ipa/libipa/lsc.cpp | 33 +++++++++++++++++++++++++++++++++ src/ipa/libipa/lsc.h | 24 ++++++++++++++++++++++++ src/ipa/libipa/meson.build | 2 ++ 3 files changed, 59 insertions(+) diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp new file mode 100644 index 000000000000..437666855354 --- /dev/null +++ b/src/ipa/libipa/lsc.cpp @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + * + * libIPA Lsc algorithms + */ + +#include "lsc.h" + +/** + * \file lsc.h + * \brief libipa LSC algorithm + */ + +namespace libcamera { + +namespace ipa { + +namespace lsc { + +/** + * \struct ActiveState + * \brief The LSC active state + * + * \var ActiveState::enabled + * \brief Boolean flag for the LscAlgorithm enable status + */ + +} /* namespace lsc */ + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/lsc.h b/src/ipa/libipa/lsc.h new file mode 100644 index 000000000000..18c9fa25e53a --- /dev/null +++ b/src/ipa/libipa/lsc.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + * + * libIPA Lsc algorithm + */ + +#pragma once + +namespace libcamera { + +namespace ipa { + +namespace lsc { + +struct ActiveState { + bool enabled; +}; + +} /* namespace lsc */ + +} /* 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 Wed Jul 8 15:51:04 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27334 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 93232C3318 for ; Wed, 8 Jul 2026 15:51:52 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 051E06610C; Wed, 8 Jul 2026 17:51:52 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="TJGeYRsQ"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 10860660D1 for ; Wed, 8 Jul 2026 17:51:19 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 2B0743FBE; Wed, 8 Jul 2026 17:50:29 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525829; bh=OjEaSi7Zm2cxiy3p7wUQy7fR0Qkie3PU1PpBJczMegw=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=TJGeYRsQVxquJdkXxPS1lIhJXThhe3nz2CUS1+47QMoUgZRwsBY5Ww1uQjj2gzvEE wIHXT+UHm7UT2nw6pREljRnj3tK4QK7Pk+nJ2LyF+UeCLhD+qFNxokVcVgFQ/Bcu9C 72b5JuSr03w6sl95WnZw/W3Uahn0AaDlFaM5IkhA= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:51:04 +0200 Subject: [PATCH v5 22/36] ipa: libipa: lsc: Introduce lsc::FrameContext MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-22-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=1177; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=OjEaSi7Zm2cxiy3p7wUQy7fR0Qkie3PU1PpBJczMegw=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHowH8VtLm5D9S+ikeguA/Wz7OvXqczV6c64 vyQDEdHzseJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x6AAKCRByNAaPFqFW PEL1D/9XdZUJWRE8GgnJyVdaimy1Te4vzmHAppCit3heOHA7DhF25d4drVNiFw7BeXZwRMBWfWL lTy6sz0DRmYlsiH3UWOVEiJD47dmd75Km18VQ6Zhi2SJJgp1kSOe/3imda8vMaqo/A/Byn1NkS9 f5072i4q3W5/LyppDqC0wYRV89Qk8oIbCvcolCMxJjGlURseiA+FK+dPGH39mpuObIbzlHbLLZ6 x+HAIiMcJMAlNTaLJwQ/SwS+S9x8S3MDa5uQ/aNZOc9h7MiHq5+00IX8hlM1ceyVzx0hU/U+emJ TksLkxeaMs0BcKpjB5ugAXYzsDXJ1kBUXWrx2i/cgVAn0CfLKtyiUVef9weF/XTTNLPpiHWMGP0 Givuob9pgTvwO7QmPoTFCQbGmoXdWd7WsrH9BCklk9NPMHIiPV9g2TSzxiyzh+qmGvChF60n6sl RZJe5K54VzcenBsGTdlq6YVG7eDLMxHe8xOSj/1PIfyCzWsQqjQweW/dRGTMw9QhwjTAoCR/wrx PnskI8dXf0c/bxfH1fWFC85wDpETJo73Ur4B3aahG4anoe2zYc4Dut1Mqzf/3qZS4J0DtO4Zs58 ogdPbHUQfYelA50E/M93PaGOPQxZcAd8S1PejciIOABeFx/HvuqgmJHq2Y2yPRJEbnX7SVcHguQ 0wbxWDX3bmq4NUg== 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 algorithm frame context in lsc.h. Signed-off-by: Jacopo Mondi --- src/ipa/libipa/lsc.cpp | 11 +++++++++++ src/ipa/libipa/lsc.h | 5 +++++ 2 files changed, 16 insertions(+) diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp index 437666855354..382cafc04ab4 100644 --- a/src/ipa/libipa/lsc.cpp +++ b/src/ipa/libipa/lsc.cpp @@ -26,6 +26,17 @@ namespace lsc { * \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 */ } /* namespace ipa */ diff --git a/src/ipa/libipa/lsc.h b/src/ipa/libipa/lsc.h index 18c9fa25e53a..73519564feff 100644 --- a/src/ipa/libipa/lsc.h +++ b/src/ipa/libipa/lsc.h @@ -17,6 +17,11 @@ struct ActiveState { bool enabled; }; +struct FrameContext { + bool enabled; + bool update; +}; + } /* namespace lsc */ } /* namespace ipa */ From patchwork Wed Jul 8 15:51:05 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27330 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 CE81AC3329 for ; Wed, 8 Jul 2026 15:51:48 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 69F146611A; Wed, 8 Jul 2026 17:51:48 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="R2p4SYtZ"; 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 553BB660D9 for ; Wed, 8 Jul 2026 17:51:19 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7A4523FBF; Wed, 8 Jul 2026 17:50:29 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525829; bh=ufGN47Y+M2apUceF3o/l4FY6OxM8VSz3Mf75D2CYr2A=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=R2p4SYtZ5azUuaS0iZa6OXQKAGeBi6qmns1CakvzrubLpTPsoIRNXWkembBI86jqr c8EIWSaZTm6IHZ6hfm5RhKFeEeRCvYdsUYOw5Sp6urbt8rtDZlAievhpT2m/JTChDp PqHlZC6Zn7epzfHIs8rqwkFIdGXkbrtf2GA/ny3Y= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:51:05 +0200 Subject: [PATCH v5 23/36] ipa: libipa: lsc: Introduce LscAlgorithm MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-23-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=9923; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=ufGN47Y+M2apUceF3o/l4FY6OxM8VSz3Mf75D2CYr2A=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHpa9gOML3KL0ZDRXAsvm3+q1VkM1qhrhps4 XfUZwykdleJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x6QAKCRByNAaPFqFW PKlOD/4mWWYcJzyMqFMOX2kB2t5YpmGvZbpvUsCxl0BYHEEgbAOSjxCB68C29DfhYQ8qb4d4/7l evNhZMGV9/CAqQgzyHGRfaYeYS/uSIZu3IOcaMgkkq8OKGdWlJgZLySfvR4Cjoqnteuhp7ml1x8 0+2snlSkv3B/+qgNCYhZlRVFHzuTfQfzh4oWF2QU+eB7EcL1WHUh72TaCWEebDN03lQsrW4jcU5 tribMxFEvZFjrjY26hm/XlzfbqpqZLTtErgYJfnrZ8fO8sCgsxhKXD5I09cy+lb8PPb0W6QEml8 +4fPuHO39aXZEUdZahx7MHp0mYxE5tJ5KDVEWp8xcIKbFlPHAuDAhXP/+rhpg1j7ml0vO+w5sGW EuZucw8HyxacYRuGGVHRq0cXmbj8pxJa9Fm+we0i1IM4Gug6rixF+MaRl5Z3Kh1tn474p2WNvno aM0mJKmYVREuCdX8xgkHF6Ii0rC8CJEDI+HNsKoGQ+9/uwuHfABr874fUxeTCFGeNgfc/8jFq8U xCIWZCh+vr3Y8kfypCos+30+OOaLpg6V093xn8wdiSNnxg2iC7wq8jwk3YV4bYdKti+a5W4+DPm ccd5b52gVPeR7hwiIRIY2QJI0Hp6e/2QFh66QU26zZ/Re2GQ8JA0mX1E25yIWbCTYJMqAgDaxCB zbgf5fyV5Oy0AgQ== 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 LscAlgorithm class that implements the LSC algorithm for libipa. The class uses two backends (LscPolynomial and LscTable) to perform tuning file parsing and re-scaling of the LSC gains. Signed-off-by: Jacopo Mondi --- src/ipa/libipa/lsc.cpp | 209 +++++++++++++++++++++++++++++++++++++++++++++++++ src/ipa/libipa/lsc.h | 42 ++++++++++ 2 files changed, 251 insertions(+) diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp index 382cafc04ab4..cf748b766d1a 100644 --- a/src/ipa/libipa/lsc.cpp +++ b/src/ipa/libipa/lsc.cpp @@ -7,6 +7,13 @@ #include "lsc.h" +#include + +#include + +#include "lsc_polynomial.h" +#include "lsc_table.h" + /** * \file lsc.h * \brief libipa LSC algorithm @@ -14,6 +21,8 @@ namespace libcamera { +LOG_DEFINE_CATEGORY(Lsc) + namespace ipa { namespace lsc { @@ -39,6 +48,206 @@ namespace lsc { } /* 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. + * + * It provides support for parsing the tuning file content and generates tables + * of per-colour temperature gains that IPA algorithms can use to program their + * 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 organized per-colour temperature with per-colour components gain + * vectors 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 gains per-colour + * component 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 index 73519564feff..9a51d32c3464 100644 --- a/src/ipa/libipa/lsc.h +++ b/src/ipa/libipa/lsc.h @@ -7,6 +7,17 @@ #pragma once +#include +#include + +#include +#include + +#include "libcamera/internal/value_node.h" + +#include "interpolator.h" +#include "lsc_base.h" + namespace libcamera { namespace ipa { @@ -24,6 +35,37 @@ struct FrameContext { } /* 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 */ From patchwork Wed Jul 8 15:51:06 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27333 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 D42C0C3333 for ; Wed, 8 Jul 2026 15:51:51 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 67EFB660F6; Wed, 8 Jul 2026 17:51:51 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="C9fq82Fg"; 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 A0A3A660C5 for ; Wed, 8 Jul 2026 17:51:19 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id C39943FC2; Wed, 8 Jul 2026 17:50:29 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525829; bh=Q5YJZEdg9Su7OB5LPR0JNlstpE3TNjG8bKxHZqN+ZCk=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=C9fq82Fg/B4d3onRYP5/rVbI5WVxib1XGQBj5lweMc/ReuiXDNgUOUt1D/pbZs749 UJKn3XUonOUjT+c6ZNZ8JBgYLLgp7jIPDCQItpyBrOzkdkx8Hxwgx200LSQiTUq14/ UA4xGMMQM88og7gnp9tWb5E5C0LuThgQQUbOMNoA= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:51:06 +0200 Subject: [PATCH v5 24/36] ipa: libipa: lsc: Move sizesListToPositions() back to rkisp1 MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-24-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=9808; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=Q5YJZEdg9Su7OB5LPR0JNlstpE3TNjG8bKxHZqN+ZCk=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHpGjUSLGpN3G4KRaXH3O0/VliNZ4tyabPcK gvjZmIfQHuJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x6QAKCRByNAaPFqFW PE5rD/9OIadVZvCmkZLq59dIFq5ExovSHTcSuSOsOnY9WtC/AFMMTvA/dJR6t8AnKpCwBXa3dJA Jay+yOECtIvSgBvLYnhmObmrxn/n4c6rjYNriOJZmHFFJkxzjyr0P5Hh9UEFFYw5NcGU+nQ8fkK WGH5yQvOW9KDLRAG2S8Yl2AH+VEMcccO8FMuloVcutgmQ7sVm9gWOovlP8rnF8QY4dQoyGWDivY LYowq0/fS5KHFI1ICJHLZG6AAXk7GPuUx68nuVPi4NSL6vpDEp67/XU8kLONqWqipQ+ScbVI1eA Q5HhoQ6C+qHyKjyPIFIKH/LN7mR4A5k+Mmx/YEK+NIIEbEN1r35ry5/s8SvxxWvrQ02u5J95u/z MQ26mFsz+a8vCSjL6ms2XPZn1N26Ej9l/Q5uCVKxKh2LFDfBK1kU20FLTvnWpmonHaYCRuBcTvk l9j7BOibtl5EqsdUV1eQX4OM/UiTxevp8T2bS3J/5cjDGojRK3rrA0gnfgsQd4cYwb0Cx9zOgds XG0CBKFfUI1ISIxjIV7cUmWR8laQr9lJ9QeWbsiO6OSX6VXdMvB5Sm3OF50mUFBoldbA4l8rF9g YWUwYoc0Dcr6Ahsz9Gz9QIR2L3cQR4Z3iHFG+l+GGJmFZf4lSc28fpaAymYO0/mBcbiniASXvgz LkjsB/9xZWudKZg== 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 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 --- 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 | 32 +++++++++++++++++++++++++++++++- src/ipa/rkisp1/algorithms/lsc.h | 3 +++ 7 files changed, 45 insertions(+), 45 deletions(-) diff --git a/src/ipa/libipa/lsc_base.cpp b/src/ipa/libipa/lsc_base.cpp index 678ba8a9a808..cc691e17a694 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. 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 diff --git a/src/ipa/libipa/lsc_base.h b/src/ipa/libipa/lsc_base.h index 51cc35888209..8d1843a5d4fd 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); }; } /* 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 e03bf361c4b9..7010c97db22e 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -70,6 +70,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()) { @@ -138,6 +141,33 @@ std::vector LensShadingCorrection::parseSizes(const ValueNode &tuningDat 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 +LensShadingCorrection::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; +} + /** * \copydoc libcamera::ipa::Algorithm::configure */ @@ -171,7 +201,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 ee037353bbe4..06aa7a5f133c 100644 --- a/src/ipa/rkisp1/algorithms/lsc.h +++ b/src/ipa/rkisp1/algorithms/lsc.h @@ -41,9 +41,12 @@ private: void copyTable(rkisp1_cif_isp_lsc_config &config, const lsc::Components &set0); std::vector parseSizes(const ValueNode &tuningData, const char *prop); + std::vector sizesListToPositions(Span sizes); 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 Wed Jul 8 15:51:07 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27331 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 398FCC3332 for ; Wed, 8 Jul 2026 15:51:50 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id AF3A1660FE; Wed, 8 Jul 2026 17:51:49 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Z65170fe"; 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 EAA40660DC for ; Wed, 8 Jul 2026 17:51:19 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 1CE593FC5; Wed, 8 Jul 2026 17:50:30 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525830; bh=AbFShManA6ICgctzKMYWXIvYMk/Czw4c5Qve0xdr//4=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=Z65170fe2ow0NqxpKoQMtnS7CZXr2X1/9ngfEteBzp+XVG/f4Edw2BuHvkQKCVxQC Y6OhWdPm4zRtnjpwaMCQtNHxsUx3E8RSmiLsAZhHhglWoJ3imS9XjeHBHlJEuHQjmi QSmCJREsqH79cUYp/7hd1B/HAe+K6XYkfAWl+L/o= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:51:07 +0200 Subject: [PATCH v5 25/36] ipa: rkisp1: lsc: Port to use LscAlgorithm MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-25-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=5866; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=AbFShManA6ICgctzKMYWXIvYMk/Czw4c5Qve0xdr//4=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHq6HVKXq/YdUjBGDrzzh819yve0+33xrAcR BvnE2rsca2JAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x6gAKCRByNAaPFqFW PCPUD/4uPnV51npygEBE53u9zEjlnEP4UoA4WzvuKhIxepM+Zpr1Q2z7mKsutGyg+RfzMePkbGM DWMB+lHcKTQZklQdW1SNPIWgd/3cdOIyvc8kj1PvwQ+ErPJqChNXDUmvcO3IoI165iDvXQOdeCW p43uako6gbwKQtP4Q3CCo2D3k4RuLSjUqrSjLEZYFCja04+bUc3fqK1w2L1QdP+YI8S5EVMEqHV uXW6tUEhMKHJHggexZFV3XEU6444L7URh3zMZuSp6L75fCflfLhAn2utGu2d8EF1YaRjb6GsxGX LtvM208r8iUGtDjSP7bKoTVh/mwrLyqrcoSz7c28NQhzVLEbLUIR4PkL45EBUn0eoyFztWEeQ93 y21JjpLLvrUikLggFX30ohTOj56tmvlKf1jDNK3WAnemny3ZBxodUmAPEL5P9aEVtnXtGndzWxZ ZlnlfaqtqIH+tUV+FYE8TZp7Oko/AQG/nBPSs7wPAgjMx70Sbuk2m4nlVGPBTdlrrL//rIqMSsD /xOgs/4A17+ZrB5Aax2wlOcnrMkJGUd04bm0SizzFxJrKBHbQSn8NVLO/rG/A4usUnRty/+kocr Th4HFbHMeY+t9wjKQ+Ni+GD6SPGGcwQbuYhPhWFn2l+WhPVeNL29r/ImML0A1xoZq/VPOhWXnT5 KlZ8y5DlAzjC96Q== 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 --- 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 7010c97db22e..0d9f1b006107 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 */ @@ -73,42 +67,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); } std::vector LensShadingCorrection::parseSizes(const ValueNode &tuningData, @@ -199,13 +159,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) @@ -233,19 +188,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); } /** @@ -283,7 +227,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; @@ -303,7 +247,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 06aa7a5f133c..e7d3091a36c5 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" @@ -55,9 +58,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 Wed Jul 8 15:51:08 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27335 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 5E4E3C3334 for ; Wed, 8 Jul 2026 15:51:53 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C5D7566126; Wed, 8 Jul 2026 17:51:52 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="WeVCN+Fg"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 413D1660D3 for ; Wed, 8 Jul 2026 17:51:20 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 5ED773FC6; Wed, 8 Jul 2026 17:50:30 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525830; bh=R3IvaYZrno5rQgTfGoJQZSAc6LCKdMEhtihqPYYUwbg=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=WeVCN+FgpVu2xTOQrXgTk7tLsVpmooQ27B80Sr2YgH3QtK7bhHLn7eVNu5cazTTge 05Nt4kBS+3yqzhNDrq4JDIEq/uDPcpt3CoI9DXDddY8MXoAxojjqsTHxlsNrwm78De xaQ6BEFpKjU2QVtGsvXYKvGKgVrDNFXGGoGOJz7I= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:51:08 +0200 Subject: [PATCH v5 26/36] ipa: libipa: lsc: Make Components a map<> MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-26-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=17939; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=R3IvaYZrno5rQgTfGoJQZSAc6LCKdMEhtihqPYYUwbg=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHqEl/T2jA2s7HBrlZoojKjdKd4/8/qko7tm 2QLR1IMXdWJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x6gAKCRByNAaPFqFW PBoFD/97x868h2LO2rVbQ/2YzH7u7d26R5k/KKX6QdySl/MlAS9C4EI72T4GSZ1OwcHKliRK+In goqzBgEN0sVXmgF+9hxfRkB+ubcf688BcwVz6zNEV9iyLxHR9wn1MygekHcenarOlRP3NPooY20 L+Nqh7oBWad7WUkPh5UiTiRRE9SkG/pNnUoe+mWy82adeutVJyy6TaPlI9LZ3cwjyfVf3vQWmcz JA2b/PQieYws9C/huqqCNM+v4wEMA0FvvEPU70iWQnFZhR2C4Xm0dNV1Q7zOXrEEvxuUA/Pp31T F5+Z70X3m06jSE14XaDBxPokh/qmEm9RzQS7ml3f7LbywuF0RNN8UcB04itUnnyD6j2YvH+o1AZ PbSYdt/9HyTbXWPZulvVzjP42yPtjCI4px+30Qp+qCNwyyhvlGfihhJTWaV+Zv6i+xO0elt1gAy foMMwuR7REsri76QSCylZo6eaLNwDaXjDZotUd2zSsf5wrbM8NWJj2L+YD/jg7wohV54fL05WhW 3abxG+E/sDUtJZMqzhb+Vvr3d9XABGfFuqSncGFckP4sGsw6+2qsnImJ1yaHTVhMoT8ukKYPmNL 3iFDeuWVOPmrqDQnm5JU8kdYS/cz/qFj3kqC5C8OL2WCNoTa8D/ILaJMkwKULNuSlji7qkJINnM PqgTIZvfwLFNkqg== X-Developer-Key: i=jacopo.mondi@ideasonboard.com; a=openpgp; fpr=72392EDC88144A65C701EA9BA5826A2587AD026B X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" The current definition of lsc::Components assumes 4 gain vectors named after the colour components supported by the RkISP1 ISP (r, gr, gb and b). As different ISP support different colour channels for their Lsc tables make lsc::Components a map that associates the name of the colour channel to the vector of gains. The use of strings as index for the map comes from the requirement of parsing the tuning file using string identifiers. Pass the strings to use for tuning file parsing to the LscAlgorithm::init() function as part of a newly introduced LscDescriptor type and use the same strings for indexing the gain arrays. The newly introduced LscDescriptor type also provides to the LscAlgorithm class the expected size of the Lsc grid, allowing to remove RkISP1 specific values from the libipa generic implementation. Signed-off-by: Jacopo Mondi --- src/ipa/libipa/lsc.cpp | 10 +++--- src/ipa/libipa/lsc.h | 4 +-- src/ipa/libipa/lsc_base.cpp | 49 ++++++++++++++++++++-------- src/ipa/libipa/lsc_base.h | 18 ++++++----- src/ipa/libipa/lsc_polynomial.cpp | 65 +++++++++++++++++++------------------ src/ipa/libipa/lsc_polynomial.h | 10 ++---- src/ipa/libipa/lsc_table.cpp | 68 +++++++++++++++++++++++---------------- src/ipa/libipa/lsc_table.h | 9 ++++-- src/ipa/rkisp1/algorithms/lsc.cpp | 20 ++++++++---- 9 files changed, 150 insertions(+), 103 deletions(-) diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp index cf748b766d1a..796c75f6d729 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 cc691e17a694..919622c6b951 100644 --- a/src/ipa/libipa/lsc_base.cpp +++ b/src/ipa/libipa/lsc_base.cpp @@ -19,23 +19,20 @@ namespace ipa { namespace lsc { /** - * \struct Components + * \typedef Components * \brief Associate a colour components with a list of gains * * LSC tables are defined as a list of gain values associated to a colour * component. * - * \var Components::r - * \brief The list of gains for the Red colour component + * As different ISP support different colour components (usually 'r', 'gr', + * 'gb', 'b' or just 'r', 'g', 'b') this class associates a string + * identifier for the colour component to a list of gains. * - * \var Components::gr - * \brief The list of gains for the Green/Red colour component + * Each key name shall match an entry in the tuning file. * - * \var Components::gb - * \brief The list of gains for the Green/Blue colour component - * - * \var Components::b - * \brief The list of gains for the Blue colour component + * The list of keys is provided to the LscAlgorithm class using \a + * LscDescriptor::keys. */ /** @@ -57,13 +54,32 @@ void Interpolator:: lsc::Components &dest, double lambda) { - interpolateVector(a.r, b.r, dest.r, lambda); - interpolateVector(a.gr, b.gr, dest.gr, lambda); - interpolateVector(a.gb, b.gb, dest.gb, lambda); - interpolateVector(a.b, b.b, dest.b, lambda); + for (auto const &[k, v] : a) + interpolateVector(v, b.at(k), dest[k], lambda); } #endif +/** + * \struct LscDescriptor + * \brief Describe the ISP LSC engine + * + * \var LscDescriptor::keys + * \brief The list of colour components to which a list of gains is associated + * with in the tuning file. Used for parsing the tuning file + * + * \var LscDescriptor::numHCells + * \brief Number of horizontal cells of the ISP LSC grid. Used for validating + * the list of gains parsed from tuning file + * + * \var LscDescriptor::numVCells + * \brief Number of vertical cells of the ISP LSC grid. Used for validating + * the list of gains parsed from tuning file + * + * \var LscDescriptor::sensorSize + * \brief The physical sensor size. This is the largest frame size used to + * generate the LSC table. Only used by the polynomial LSC algorithm + */ + /** * \class LscImplementation * \brief Pure virtual base class for LSC algorithm implementations @@ -80,6 +96,11 @@ void Interpolator:: * \fn LscImplementation::parseLscData * \brief Parse \a tuningData * \param[in] tuningData The tuning data + * \param[in] descriptor The LSC engine descriptor + * + * Parse the tuning file using the \a descriptor to identify the colour + * components in the tuning data and validate the size of the loaded gains + * tables. * * \return 0 on success, a negative error number otherwise */ diff --git a/src/ipa/libipa/lsc_base.h b/src/ipa/libipa/lsc_base.h index 8d1843a5d4fd..48307455362a 100644 --- a/src/ipa/libipa/lsc_base.h +++ b/src/ipa/libipa/lsc_base.h @@ -26,13 +26,7 @@ namespace ipa { namespace lsc { -struct Components { - std::vector r; - std::vector gr; - std::vector gb; - std::vector b; -}; - +using Components = std::map>; using ComponentsMap = std::map; } /* namespace lsc */ @@ -56,12 +50,20 @@ void Interpolator:: double lambda); #endif /* __DOXYGEN__ */ +struct LscDescriptor { + std::vector keys; + unsigned int numHCells; + unsigned int numVCells; + Size sensorSize; +}; + class LscImplementation { public: virtual ~LscImplementation() {} - virtual int parseLscData(const ValueNode &tuningData) = 0; + virtual int parseLscData(const ValueNode &tuningData, + const LscDescriptor &descriptor) = 0; virtual lsc::ComponentsMap sampleForCrop(const Rectangle &cropRectangle, diff --git a/src/ipa/libipa/lsc_polynomial.cpp b/src/ipa/libipa/lsc_polynomial.cpp index 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..cdd2fea5ebf3 100644 --- a/src/ipa/libipa/lsc_table.cpp +++ b/src/ipa/libipa/lsc_table.cpp @@ -7,9 +7,6 @@ #include "lsc_table.h" -/* \todo Remove RkISP1 from libipa. */ -#include "linux/rkisp1-config.h" - namespace libcamera { LOG_DEFINE_CATEGORY(LscTable) @@ -29,42 +26,56 @@ namespace ipa { /** * \brief Parse tabular LSC data * \param[in] sets The tuning file content + * \param[in] descriptor The LSC engine descriptor * * Parse the LSC data in tabular form from the \a sets tuning data. * * \return 0 on success or a negative error number otherwise */ -int LscTable::parseLscData(const ValueNode &sets) +int LscTable::parseLscData(const ValueNode &sets, + const LscDescriptor &descriptor) { for (const auto &set : sets.asList()) { uint32_t ct = set["ct"].get(0); - if (lscData_.count(ct)) { - LOG(LscTable, Error) - << "Multiple sets found for color temperature " - << ct; - return -EINVAL; - } + int ret = parseLscComponent(set, ct, descriptor); + if (ret) + return ret; + } - lsc::Components components; - components.r = parseTable(set, "r"); - components.gr = parseTable(set, "gr"); - components.gb = parseTable(set, "gb"); - components.b = parseTable(set, "b"); + if (lscData_.empty()) { + LOG(LscTable, Error) << "Failed to load any sets"; + return -EINVAL; + } - if (components.r.empty() || components.gr.empty() || - components.gb.empty() || components.b.empty()) { + return 0; +} + +int LscTable::parseLscComponent(const ValueNode &yamlSet, + unsigned int ct, const LscDescriptor &descriptor) +{ + lsc::Components component; + for (auto &k : descriptor.keys) { + auto [it, inserted] = component.emplace( + std::piecewise_construct, + std::forward_as_tuple(k.c_str()), + std::forward_as_tuple(parseTable(yamlSet, + k.c_str(), + descriptor.numHCells, + descriptor.numVCells))); + if (!inserted || it->second.empty()) { LOG(LscTable, Error) - << "Set for color temperature " << ct - << " is missing tables"; + << "Set " << k << " for color temperature " + << ct << " is missing"; return -EINVAL; } - - lscData_.emplace(ct, std::move(components)); } - if (lscData_.empty()) { - LOG(LscTable, Error) << "Failed to load any sets"; + auto [it, inserted] = lscData_.emplace(ct, component); + if (!inserted) { + LOG(LscTable, Error) + << "Multiple sets found for color temperature " + << ct; return -EINVAL; } @@ -72,17 +83,18 @@ int LscTable::parseLscData(const ValueNode &sets) } std::vector LscTable::parseTable(const ValueNode &tuningData, - const char *prop) + const char *prop, + unsigned int numHCells, + unsigned int numVCells) { - static constexpr unsigned int kLscNumSamples = - RKISP1_CIF_ISP_LSC_SAMPLES_MAX * RKISP1_CIF_ISP_LSC_SAMPLES_MAX; + unsigned int lscNumSamples = numHCells * numVCells; std::vector table = tuningData[prop].get>().value_or(utils::defopt); - if (table.size() != kLscNumSamples) { + if (table.size() != lscNumSamples) { LOG(LscTable, Error) << "Invalid '" << prop << "' values: expected " - << kLscNumSamples + << lscNumSamples << " elements, got " << table.size(); return {}; } diff --git a/src/ipa/libipa/lsc_table.h b/src/ipa/libipa/lsc_table.h index 67c7464ea5b6..3fe52afbad7b 100644 --- a/src/ipa/libipa/lsc_table.h +++ b/src/ipa/libipa/lsc_table.h @@ -26,7 +26,8 @@ namespace ipa { class LscTable : public LscImplementation { public: - int parseLscData(const ValueNode &sets) override; + int parseLscData(const ValueNode &sets, + const LscDescriptor &descriptor) override; lsc::ComponentsMap sampleForCrop([[maybe_unused]] const Rectangle &cropRectangle, @@ -39,8 +40,12 @@ public: } private: + int parseLscComponent(const ValueNode &yamlSet, + unsigned int ct, const LscDescriptor &descriptor); std::vector parseTable(const ValueNode &tuningData, - const char *prop); + const char *prop, + unsigned int numHCells, + unsigned int numVCells); lsc::ComponentsMap lscData_; }; diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index 0d9f1b006107..9db67197ad64 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -67,8 +67,12 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, xPos_ = sizesListToPositions(xSize_); yPos_ = sizesListToPositions(ySize_); - return lscAlgo_.init(tuningData, context.sensorInfo.activeAreaSize, - context.ctrlMap); + return lscAlgo_.init(tuningData, context.ctrlMap, { + .keys = { "r", "gr", "gb", "b" }, + .numHCells = RKISP1_CIF_ISP_LSC_SAMPLES_MAX, + .numVCells = RKISP1_CIF_ISP_LSC_SAMPLES_MAX, + .sensorSize = context.sensorInfo.activeAreaSize + }); } std::vector LensShadingCorrection::parseSizes(const ValueNode &tuningData, @@ -174,10 +178,14 @@ void LensShadingCorrection::setParameters(rkisp1_cif_isp_lsc_config &config) void LensShadingCorrection::copyTable(rkisp1_cif_isp_lsc_config &config, const lsc::Components &set) { - std::copy(set.r.begin(), set.r.end(), &config.r_data_tbl[0][0]); - std::copy(set.gr.begin(), set.gr.end(), &config.gr_data_tbl[0][0]); - std::copy(set.gb.begin(), set.gb.end(), &config.gb_data_tbl[0][0]); - std::copy(set.b.begin(), set.b.end(), &config.b_data_tbl[0][0]); + const auto &r = set.at("r"); + std::copy(r.begin(), r.end(), &config.r_data_tbl[0][0]); + const auto &gr = set.at("gr"); + std::copy(gr.begin(), gr.end(), &config.gr_data_tbl[0][0]); + const auto &gb = set.at("gb"); + std::copy(gb.begin(), gb.end(), &config.gb_data_tbl[0][0]); + const auto &b = set.at("b"); + std::copy(b.begin(), b.end(), &config.b_data_tbl[0][0]); } /** From patchwork Wed Jul 8 15:51:09 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27332 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 10B48C3323 for ; Wed, 8 Jul 2026 15:51:51 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B966C660D3; Wed, 8 Jul 2026 17:51:50 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="nw1tQuyc"; 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 A3622660DF for ; Wed, 8 Jul 2026 17:51:20 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id A87CD3FD4; Wed, 8 Jul 2026 17:50:30 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525830; bh=rQpuQHXCyAXIKUP2x9f0JycAyoGrS4OUcp9j0GEys7U=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=nw1tQuycCcVAa3nkLQsCEdyJIDQpsdOAUkBsYl4fC/E58ZA22+y+a0hyfpZFROh+G 6+y5gtTo9UufgqaOdM7uzrGoPtHacvwLfoV5RL2CPVwZBrEnV3QNbdF4PAIU8o29X1 dVQ1p/zWSuBNHYjfh7YorUHsHmN4JFpEJF22e9wc= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:51:09 +0200 Subject: [PATCH v5 27/36] ipa: libipa: lsc: Document Components parsing MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-27-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=4242; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=rQpuQHXCyAXIKUP2x9f0JycAyoGrS4OUcp9j0GEys7U=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHqAg8RRpSj1C5BFMe+zjjZSJ/CgYvWUB1bQ o51hcTkiaOJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x6gAKCRByNAaPFqFW PFkHD/9GDt3khG8zqp08CfK2fXKbQxD/vdFEHKw1ikOwxONz2h35eb1T2vc9gVH3ilc0Hc4CnwY AmUOn8SIyFTdmtrGmyIVBsg6qHvYioxOp5fdsSh52VS4PERXvmGCeO8JUHHtkWao3uQ3t61dVoq Gi51P5SlD8t7cr29Am8jbbTqi2RO3jOJFU+JA7KHE5eOiVV1sMc+K+b6B3GpMpKGlnE1golbIOR 1pGTZI+fI1JEX8qivXQ29fD3U1sP3QOLjm/rEa+VyGyVMo7ulUDO0lTohzWITnZaT0UnSE1+yFe hm+vP3/P+LD7QfaeeTv7YRBUKrtjbfLsVNo7D6XGpvVjgq6iG0KDnCfkMYnw+nmEjazfyeFsoD2 KqKKkOZvd6hBwPuGIP3zvyGqNm3SB+BqyGiJGevYYopP/E6XKUFtS27q+gkAKCJ6wmMKvsV38rS ZWL+ebQMWb/81cqjPJCQjKKuC1ziv/5vXk8uvA9l44sCGLMjmuY20ZT6gB1o/h6ghK4v7fRyPBt lO/10opwqiTyAOXxj3h79hgAEFEZO2jrQosUhsOMaNGkCYfZ+eZ5h/FC20R1tfKIiqcj0IN5Jbo ML2xAcDjw+3DHug3n54/R/nY/9rKiS+yZRfjBXR+t66MNJqU9d9OZZxEBlTFHOUYS2BZ2V2rN9U 7SoL2Nf0cKINzdQ== 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 --- 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 796c75f6d729..26c3e9968072 100644 --- a/src/ipa/libipa/lsc.cpp +++ b/src/ipa/libipa/lsc.cpp @@ -68,6 +68,117 @@ namespace lsc { * tables are organized per-colour temperature with per-colour components gain * vectors 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 here omitted, but the expected number + * of entries has to be equal to + * LscDescriptor::numHCells * LscDescriptor::numVCells. + * + * \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 Wed Jul 8 15:51:10 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27338 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 B45FFC3336 for ; Wed, 8 Jul 2026 15:51:56 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 57ADA66113; Wed, 8 Jul 2026 17:51:56 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="cDqjP+aE"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 04942660B3 for ; Wed, 8 Jul 2026 17:51:22 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 1E3C43FE8; Wed, 8 Jul 2026 17:50:31 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525831; bh=ZE/FEhl1zjyzhA9H3UC3EUUq0qVCJRaGsJin9VMqjpU=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=cDqjP+aEP5h+wrPDDffg5zqs7xo/H6CYbzH6bpbySYLlPxPGFEeiBxGaT1InTtP8r /5BXl+rkzGcuKskbIpdVQ7BL11ASi0FbefK6j7tkz1fAoqEHaW1JJrBRHnM2qlzHyi Hen0X3r+gRnHMWna9ISzYFy+RZrHB4ffsGYmOfDk= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:51:10 +0200 Subject: [PATCH v5 28/36] ipa: libipa: lsc_polynomial: Remove sensorSize_ MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-28-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=3254; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=ZE/FEhl1zjyzhA9H3UC3EUUq0qVCJRaGsJin9VMqjpU=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHr3lrEDdOTGwpeHVw6LHcO+sg8GbCdb7icf 1SZPuMxP3SJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x6wAKCRByNAaPFqFW PB/1D/9hv/86Ay5Ex1w31EZeItbXw+oVJ+diVKAGmkONPOmB7GfDZD21beKcXUYQSDqoK7DUXsN oYEcavcHppbWbpEwWzbkeAFOut+j2e36VIBZ8d8aeFLJHnOufYS1EQnpoUyLP8rSsTNF8ovhpQf Q9W8WHfkCst/jKukXeg2v29SDPem9pEZvF6KAEDVrqMZPOxpWwNq8HyigBi/sSoKS7H2lECga4R NPnG8dIKtVJSB/wed0e8UkOLY6sdenVEoDO7AgAG+/02o8FP0Zo6VDXy28Jh+wX78bGjGVRKa8R pDWRZtyQZfYzdulXm77YDZqt67cPGseN9PNYtaOe4zvFs4igoQiVogjCHlzCm/+ximjJKm+DaaW VZ7tTzXKqpXqryM5lQ+f4hBG0l7M5usIyKhB5usUsLbbwgidQmAFguTqpIJ89CAER7CMrLO4RhO VssWFGxWVOJE+XBxL15uGY7pgyNKsFIvtk7ZiVX+J7UtwRXu/+Z60bw18IIRMqoM2cbolQRgFui FgDGxxKhhsTboPMsJX6YoSemFfOe6Q9A7qSVaUQhUglENnPPSLRaHHr2LuaC3TfYTulkyZ4xTIG Ao7ISxIJQmSWP0H15DpPlM9AjzVbp5ayZNEmINUqCQ+A1JJX1oZelWvhCDRT/JPOZkFYxVCDDqE 6AD3U+7+OOWDHXw== 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 --- 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 26c3e9968072..1d2f8eb1b41b 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 919622c6b951..adc09c15752a 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 Wed Jul 8 15:51:11 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27336 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 C1ECCC3330 for ; Wed, 8 Jul 2026 15:51:54 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 27FB5660F6; Wed, 8 Jul 2026 17:51:54 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="sf10ONKg"; 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 5865F660AB for ; Wed, 8 Jul 2026 17:51:21 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 6E8253FF5; Wed, 8 Jul 2026 17:50:31 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525831; bh=l3IWqtX/3k/0xTkpngmJmTTxrkKv+Uf3ZsubdNM+OxY=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=sf10ONKgHbe23OqF9Nc6EI6oL3phy8XBTYNgpf1AP9909y3mtqSb0SnKeU/0JqwsH gn2XY0s9indGJJPoQQ5xcUdT0XN2sVhIj6thd6c416zfudMFDArSCm1/A3JMLFdkUB Zv2WMIUL4QYp4U423tllqUtNL9WrSc7mQQjVUMbI= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:51:11 +0200 Subject: [PATCH v5 29/36] ipa: libipa: lsc: Document LscDescriptor::sensorSize usage MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-29-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=966; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=l3IWqtX/3k/0xTkpngmJmTTxrkKv+Uf3ZsubdNM+OxY=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHriNByfvEG9L+aX61mBJ4JYPPHXHVMKhCNa FeSDhJCITiJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x6wAKCRByNAaPFqFW PP4OD/9gZ6gK29OZ5QMSPG7jZ7pI8/OC/IENvR/f8WjI0R+LzvfPiSnxFYFzLgQbi7EWw66YBUq o1zYfJfMUYZuWpFJmb5Is5VU46XKoIq5oEqOI1bErAfPsRngoBckVUT6ohlwTNZ5G2F3TeIRGUZ ZR1wr85laohZtmBmNuK6/BCmYE/UiyqldibhRpwSV0WdJQ3zSKZFjv46QWnT9U386g3+BLIzrKQ JAPFb8K+oQx72P7+YdXN1UGJ/n3uWfnfQIaEgnzcnP4hAyubyjwBlV5jL9FSMlnPuih/QwWrbGi fhluHA8aN16lTwYhW1G670lxMuYmp0FrmymG7Xt2JXBJNm21LQzyLC8++WbKvUe4W2QdmaMSVPH qnOHFIdfd+l3EhaFGZX1xVazU4qv+qUILe6rhhAX/oSf5B4Gg9gB40TwReKZ9U9B8gqaDtdhisd 1+Rdnhw8RkxI/pgq+ohXKl4y3aVeyxKLQTTRlEUjlDNDRPC0V7DNAbaauNFURVpzWVGkcca5LHW aO+ZBTFXsa5VWGcV1lktP2DaKiR+QQyDYqTX40Ve0Kj61uED1w8g1JjoxtyiGVY5+g2g6tL9qnF AmSXFhW63pYBPcrdu1elw7CFtYJkaIPGexYOYQdObYnNLIO0SyqMDel/emNYTDC45D3UVZvVULS TwuamAuvvngyMfA== 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 --- 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 1d2f8eb1b41b..13b1b738106b 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 tables or the polynomial definition are generated at tuning time + * using an image of known resolution which 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 Wed Jul 8 15:51:12 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27337 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 2E2A8C331D for ; Wed, 8 Jul 2026 15:51:56 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B4B296608D; Wed, 8 Jul 2026 17:51:55 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="rBXaR7Xj"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id C6A48660DD for ; Wed, 8 Jul 2026 17:51:21 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id C3A233FFA; Wed, 8 Jul 2026 17:50:31 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525832; bh=Oh2vKjG1iL4s1uwcaEXnwfadMDO3PxWSDIU+oP4DRCA=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=rBXaR7Xj6oRDm4/NEAZ0sM27Hof6Wv0jeWsJ8gUmZmkflXlWv0+yJYqlKg6+nvfmg MPBbyd5dNUayF3emRdySj/Bb+UP1lg/46KxX1Y7E2hmd0yHDusKdgwVoHRnW5drg7B b3iWmQ2FTzdZuM4Bki4LrPE+sw3dQvn0yrJjW9i4= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:51:12 +0200 Subject: [PATCH v5 30/36] ipa: libipa: lsc: Template LscAlgorithm with register format MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-30-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=23339; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=Oh2vKjG1iL4s1uwcaEXnwfadMDO3PxWSDIU+oP4DRCA=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHsZqickUO/EehjXBXyT5rEgi5qRdQxz/R4j vMllN/6sIOJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x7AAKCRByNAaPFqFW PP0XEACWCirO9uaOYQFtD3Nqinxw71IoBXwXy1fJSIbVVx+bTmKGH3HnpfgNAfmT/BqT6qEGci7 rFSugX0S1txKHXLBhApE9HitVO3172xnLQfX0K+I1efTp5F4cqnrezg6KuKD64KqKqmi799rBJq XIsF05qV+UY7aOI1etSam+YnLk7incFne7Zc+boRatBzSQGSrdB1RVA5toPLFMyen3DFiVJDd5d i8G+y9L47AHPpgA5Ru/dE/bLoF1mpW8+EWBWgRi/+3v4jtm0CFyQbG0wBFn+PQFkdzul2CYKwRr byQ1Wy2Ya8yYHJ/PSViSYCik1Hg3UDzTAex2mVh2n/c3lWH1C76dH6RvR4+ae84bXo90BSCyghO 0OFRAzK6hPBBHh8uCAkpWMMUGgWaDHS9DLy3jyOZsBLhhq8xOKK8QEGyKTra8DyvxSDnUoeEx1Q DQN/2edYtd8fS+ZO0/EppU6JNLawk5Y3PkZHGj5QocdRIBliAM/e5T6zLMKLfRoVRJi7q5J/7ka Bs3VtZ04/iTMmZVztaFT8rffb8liByupnrzyLP3bz12Fr+R26YZPq0i0phdsi9ZbZNYNhZwcxA2 Nc9TYveIIrAGgsnIUYly+Ox/B2DkXvnj9PTlVlcOqz4ScaqOdBgOlQj5+3USoSunf6NNsjojVqY 5lw1eYtmVH5YyTA== 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::samplePolynomial function expands the radial polynomial assuming the RkISP1 Q<2, 10> register format: /* * 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); In order to remove all assumptions about the platform register format template the LscAlgorithm class with the fixed point format of the ISP Lsc engine registers. float sample = static_cast (poly.sampleAtNormalizedPixelPos(xp, yp)); samples.push_back(U(sample).quantized()); In order to template the LscPolynomial class, template the whole class hierarchy including the LscTable class that doesn't need (yet) the template argument but will use it once re-sampling LscTable will be implemented. Split all involved classes (LscAlgorithm, LscTable and LscPolynomial) in two classes: a Lsc*Base class for non-templated function implementations and a Lsc* class for templated functions implementations. Signed-off-by: Jacopo Mondi --- src/ipa/libipa/lsc.cpp | 151 +++++++++++++------------------------- src/ipa/libipa/lsc.h | 81 +++++++++++++++++--- src/ipa/libipa/lsc_base.cpp | 1 + src/ipa/libipa/lsc_base.h | 1 + src/ipa/libipa/lsc_polynomial.cpp | 90 +++++++---------------- src/ipa/libipa/lsc_polynomial.h | 67 +++++++++++++++-- src/ipa/libipa/lsc_table.cpp | 49 +++++++++---- src/ipa/libipa/lsc_table.h | 35 ++++++--- src/ipa/rkisp1/algorithms/lsc.h | 3 +- 9 files changed, 270 insertions(+), 208 deletions(-) diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp index 13b1b738106b..f5646937c60a 100644 --- a/src/ipa/libipa/lsc.cpp +++ b/src/ipa/libipa/lsc.cpp @@ -7,13 +7,6 @@ #include "lsc.h" -#include - -#include - -#include "lsc_polynomial.h" -#include "lsc_table.h" - /** * \file lsc.h * \brief libipa LSC algorithm @@ -48,9 +41,56 @@ namespace lsc { } /* namespace lsc */ +/** + * \class LscAlgorithmBase + * \brief Base class for LscAlgorithm + * + * Base class for LscAlgorithm for non-templated functions implementation + */ + +/** + * \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); +} + /** * \class LscAlgorithm * \brief libIPA LSC algorithm implementation + * \tparam U The fixedpoint LSC engine register format * * Due to the optical characteristics of the lens, the light intensity received * by the sensor is not uniform. The Lens Shading Correction algorithm applies @@ -200,6 +240,7 @@ namespace lsc { */ /** + * \fn LscAlgorithm::init() * \param[in] tuningData The tuning data * \param[in] controls The IPA list of supported controls * \param[in] descriptor The LSC engine descriptor @@ -209,42 +250,9 @@ namespace lsc { * * \return 0 on success, a negative error code otherwise */ -int LscAlgorithm::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 * \param[in] analogCrop The current sensor analog crop rectangle * \param[in] xPos List of horizontal positions of the LSC grid nodes @@ -265,67 +273,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 - * \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 diff --git a/src/ipa/libipa/lsc.h b/src/ipa/libipa/lsc.h index 9fe8ad67ea33..d48336b169f4 100644 --- a/src/ipa/libipa/lsc.h +++ b/src/ipa/libipa/lsc.h @@ -8,18 +8,25 @@ #pragma once #include +#include #include +#include + +#include #include #include #include "libcamera/internal/value_node.h" #include "interpolator.h" -#include "lsc_base.h" +#include "lsc_polynomial.h" +#include "lsc_table.h" namespace libcamera { +LOG_DECLARE_CATEGORY(Lsc) + namespace ipa { namespace lsc { @@ -35,19 +42,75 @@ struct FrameContext { } /* namespace lsc */ -class LscAlgorithm +class LscAlgorithmBase +{ +public: + void queueRequest(lsc::ActiveState &state, lsc::FrameContext &context, + const ControlList &controls); + void process(lsc::FrameContext &context, ControlList &metadata); +}; + +template +class LscAlgorithm : public LscAlgorithmBase { public: int init(const ValueNode &tuningData, ControlInfoMap::Map &controls, - const LscDescriptor &descriptor); + 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; + } 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 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; + } const lsc::Components interpolateComponents(unsigned int ct) { @@ -60,7 +123,7 @@ public: } private: - std::unique_ptr impl_; + std::unique_ptr> impl_; Interpolator sets_; lsc::ComponentsMap lscData_; bool polynomial_; diff --git a/src/ipa/libipa/lsc_base.cpp b/src/ipa/libipa/lsc_base.cpp index adc09c15752a..b91da4e5be9d 100644 --- a/src/ipa/libipa/lsc_base.cpp +++ b/src/ipa/libipa/lsc_base.cpp @@ -86,6 +86,7 @@ void Interpolator:: /** * \class LscImplementation * \brief Pure virtual base class for LSC algorithm implementations + * \tparam U The fixedpoint LSC engine register format * * Defines the interface for the LSC algorithm implementation. */ diff --git a/src/ipa/libipa/lsc_base.h b/src/ipa/libipa/lsc_base.h index 48307455362a..721fd137ef21 100644 --- a/src/ipa/libipa/lsc_base.h +++ b/src/ipa/libipa/lsc_base.h @@ -57,6 +57,7 @@ struct LscDescriptor { Size sensorSize; }; +template class LscImplementation { public: diff --git a/src/ipa/libipa/lsc_polynomial.cpp b/src/ipa/libipa/lsc_polynomial.cpp index c61c85fbf2b2..060fba9826a0 100644 --- a/src/ipa/libipa/lsc_polynomial.cpp +++ b/src/ipa/libipa/lsc_polynomial.cpp @@ -113,14 +113,10 @@ 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. + * \class LscPolynomialBase + * \brief Base class for LscPolynomial * - * \sa LscImplementation + * Base class for LscPolynomial for non-templated functions. */ /** @@ -132,8 +128,8 @@ void Polynomial::setReferenceImageSize(const Size &size) * * \return 0 on success or a negative error number otherwise */ -int LscPolynomial::parseLscData(const ValueNode &sets, - const LscDescriptor &descriptor) +int LscPolynomialBase::parseLscData(const ValueNode &sets, + const LscDescriptor &descriptor) { for (const auto &set : sets.asList()) { uint32_t ct = set["ct"].get(0); @@ -174,6 +170,27 @@ int LscPolynomial::parseLscData(const ValueNode &sets, } /** + * \var LscPolynomialBase::lscData_ + * \brief The polynomial LSC data + * + * Maps colour temperatures to per-colour radial polynomial definitions. + */ + +/** + * \class LscPolynomial + * \brief Radial Polynomial LSC algorithm implementation + * \tparam U The fixedpoint format used to convert gain values generated by + * polynomial expansion to the register format + * + * Polynomial-based LSC algorithm implementation. The LscPolynomial class + * implements LSC support using Polynomial to represent the shading artifacts + * map. + * + * \sa LscImplementation + */ + +/** + * \fn LscPolynomial::sampleForCrop() * \brief Re-sample the LSC components for \a cropRectangle * \param[in] cropRectangle The sensor analogue crop rectangle * \param[in] xPos List of horizontal positions of the LSC grid nodes @@ -195,62 +212,7 @@ int LscPolynomial::parseLscData(const ValueNode &sets, * [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, - std::vector xPos, std::vector yPos) -{ - - lsc::ComponentsMap components; - - 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; -} - -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; -} } /* namespace ipa */ diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h index 6d19e46a50b7..04d7465b3af0 100644 --- a/src/ipa/libipa/lsc_polynomial.h +++ b/src/ipa/libipa/lsc_polynomial.h @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -50,26 +51,80 @@ private: } /* namespace lsc */ -class LscPolynomial : public LscImplementation +class LscPolynomialBase { private: using PolynomialComponents = std::map; using PolynomialComponentsMap = std::map; +protected: + int parseLscData(const ValueNode &yamlSets, + const LscDescriptor &descriptor); + + PolynomialComponentsMap lscData_; +}; + +template +class LscPolynomial : public LscPolynomialBase, public LscImplementation +{ public: - int parseLscData(const ValueNode &sets, - const LscDescriptor &descriptor) override; + int parseLscData(const ValueNode &yamlSets, + const LscDescriptor &descriptor) override + { + return LscPolynomialBase::parseLscData(yamlSets, descriptor); + } lsc::ComponentsMap sampleForCrop(const Rectangle &cropRectangle, - std::vector xPos, std::vector yPos) override; + std::vector xPos, std::vector yPos) override + { + lsc::ComponentsMap components; + + 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; + } private: std::vector samplePolynomial(const lsc::Polynomial &poly, Span xPositions, Span yPositions, - const Rectangle &cropRectangle); - PolynomialComponentsMap lscData_; + 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; + + float sample = static_cast + (poly.sampleAtNormalizedPixelPos(xp, yp)); + + samples.push_back(U(sample).quantized()); + } + } + + return samples; + } }; } /* namespace ipa */ diff --git a/src/ipa/libipa/lsc_table.cpp b/src/ipa/libipa/lsc_table.cpp index cdd2fea5ebf3..0e8f144cb873 100644 --- a/src/ipa/libipa/lsc_table.cpp +++ b/src/ipa/libipa/lsc_table.cpp @@ -14,13 +14,10 @@ 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. + * \class LscTableBase + * \brief Base class for LscTable * - * \sa LscImplementation + * Base class for LscTable for non-templated functions. */ /** @@ -32,8 +29,8 @@ namespace ipa { * * \return 0 on success or a negative error number otherwise */ -int LscTable::parseLscData(const ValueNode &sets, - const LscDescriptor &descriptor) +int LscTableBase::parseLscData(const ValueNode &sets, + const LscDescriptor &descriptor) { for (const auto &set : sets.asList()) { uint32_t ct = set["ct"].get(0); @@ -51,8 +48,8 @@ int LscTable::parseLscData(const ValueNode &sets, return 0; } -int LscTable::parseLscComponent(const ValueNode &yamlSet, - unsigned int ct, const LscDescriptor &descriptor) +int LscTableBase::parseLscComponent(const ValueNode &yamlSet, + unsigned int ct, const LscDescriptor &descriptor) { lsc::Components component; for (auto &k : descriptor.keys) { @@ -82,10 +79,10 @@ int LscTable::parseLscComponent(const ValueNode &yamlSet, return 0; } -std::vector LscTable::parseTable(const ValueNode &tuningData, - const char *prop, - unsigned int numHCells, - unsigned int numVCells) +std::vector LscTableBase::parseTable(const ValueNode &tuningData, + const char *prop, + unsigned int numHCells, + unsigned int numVCells) { unsigned int lscNumSamples = numHCells * numVCells; @@ -102,6 +99,30 @@ std::vector LscTable::parseTable(const ValueNode &tuningData, return table; } +/** + * \var LscTableBase::lscData_ + * \brief The tabular LSC data + * + * Maps colour temperatures to per-colour channel vector of gains + */ + +/** + * \class LscTable + * \brief Table based LSC algorithm implementation + * \tparam U The fixedpoint LSC engine register format + * + * Table based LSC algorithm implementation. The LscTable class implements LSC + * support using tabular LSC data. + * + * \sa LscImplementation + */ + +/** + * \fn LscTable::parseLscData() + * + * \copydoc LscTableBase::parseLscData() + */ + } /* namespace ipa */ } /* namespace libcamera */ diff --git a/src/ipa/libipa/lsc_table.h b/src/ipa/libipa/lsc_table.h index 3fe52afbad7b..65e04b09c3ff 100644 --- a/src/ipa/libipa/lsc_table.h +++ b/src/ipa/libipa/lsc_table.h @@ -23,11 +23,32 @@ LOG_DECLARE_CATEGORY(LscTable) namespace ipa { -class LscTable : public LscImplementation +class LscTableBase +{ +protected: + int parseLscData(const ValueNode &sets, + const LscDescriptor &descriptor); + +private: + int parseLscComponent(const ValueNode &yamlSet, + unsigned int ct, const LscDescriptor &descriptor); + std::vector parseTable(const ValueNode &tuningData, + const char *prop, + unsigned int numHCells, + unsigned int numVCells); +protected: + lsc::ComponentsMap lscData_; +}; + +template +class LscTable : public LscTableBase, public LscImplementation { public: int parseLscData(const ValueNode &sets, - const LscDescriptor &descriptor) override; + const LscDescriptor &descriptor) override + { + return LscTableBase::parseLscData(sets, descriptor); + } lsc::ComponentsMap sampleForCrop([[maybe_unused]] const Rectangle &cropRectangle, @@ -38,16 +59,6 @@ public: << "Tabular LSC data doesn't support resampling"; return lscData_; } - -private: - int parseLscComponent(const ValueNode &yamlSet, - unsigned int ct, const LscDescriptor &descriptor); - std::vector parseTable(const ValueNode &tuningData, - const char *prop, - unsigned int numHCells, - unsigned int numVCells); - - lsc::ComponentsMap lscData_; }; } /* namespace ipa */ diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h index e7d3091a36c5..31156f56cd21 100644 --- a/src/ipa/rkisp1/algorithms/lsc.h +++ b/src/ipa/rkisp1/algorithms/lsc.h @@ -13,6 +13,7 @@ #include "libcamera/internal/value_node.h" +#include "libipa/fixedpoint.h" #include "libipa/lsc.h" #include "algorithm.h" @@ -58,7 +59,7 @@ private: unsigned int lastAppliedCt_; unsigned int lastAppliedQuantizedCt_; - LscAlgorithm lscAlgo_; + LscAlgorithm> lscAlgo_; }; } /* namespace ipa::rkisp1::algorithms */ From patchwork Wed Jul 8 15:51:13 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27339 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 75874C3261 for ; Wed, 8 Jul 2026 15:51:57 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 08ABF66122; Wed, 8 Jul 2026 17:51:57 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="S+VrK6z4"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 3D1CC660E1 for ; Wed, 8 Jul 2026 17:51:22 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 421631CFD; Wed, 8 Jul 2026 17:50:32 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525832; bh=2QvgZUzLDc2ja7R9EkinL3mS8x7xRAxO+EYGjrc7vbU=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=S+VrK6z4YVDphyIYq2CWnXaMiHTihyYEszSCyYsxOWamk4+IfYO3VLhGaw/mM119Z e7gkUjyrVLi19QKcbIc9YWpg9FGjP+4qF+e4CnBBt1TJNx5X9eNtowtoRhrqoDhuX4 Rx5umKxt88B4VXo9uKfxO7QRNcLkwiPeGwn0aTX8= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:51:13 +0200 Subject: [PATCH v5 31/36] ipa: libipa: lsc_base: Describe the function of lsc_base MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-31-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=1646; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=2QvgZUzLDc2ja7R9EkinL3mS8x7xRAxO+EYGjrc7vbU=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHsY+ooRauRg5kUkwAZcxTRbA5sBc5cQPShj ZdkGUdHeUqJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x7AAKCRByNAaPFqFW PLdFEACBfSt1warTMjInwgyM7KrOxIIyqlLYwFbKDUTKgQyB26bQsS6Cg49ylntEc3pSV18/c5t lFdl4NS04LrMf/lwkIV1ExNtmlz7bOcecaM7kUvo9diXaP5JPKr9vdyJKtuPdElIfTjkTj5GPWo bgTBKhlfMJ8wh41CchxNnvPfCgkAhvnSiU6ORTbZhGd8nJHAsdlSrpEKP73cYsHDHUNEplOJCtZ vVhO4i+lS2uq56qK3dlyeJMrWOE2g1iSlUuQkovX+nT67ZY67MQBT4BZpEOOb3tAva8BgBDgDVM gwuPqeRJHKOLpH2klWuhLtlTQl5T1tcz1vYCDfIrAWZApixYViTMA7mg7ymf32dd9EhbX1PqMnW vyc7iabwfQRtNSQTM9Etix/7FW2ax6cXxEHW/j8fPu+88DlcXGLQXIC/Ic95DgwjxsCOzNpP6As Uvb1bWC3/mlUpbCbtGn3Tyx3wPTxh85vV2HKZBHV+e/vgUS7rYcgAlkpeu3JhdgiJpqCGbNo87k UwGXgu9ycScVi9K7+/KnWCa2n9xqewfiqgyv0IKSE9i/R7g8N6MIwgTvGibfShkFe9CoZ+mecZC iI1eVVkV8R2AfzZPIsOpftfJr/xbX6e9qyj4b7UVT59x3xw1VmN2Owpp19zRrxxqPGgFrYxig1V S0u4L7AicB9LFkw== 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 the class hierarchy is in place, describe the function of the lsc_base header in the file comment. Signed-off-by: Jacopo Mondi --- src/ipa/libipa/lsc_base.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/ipa/libipa/lsc_base.cpp b/src/ipa/libipa/lsc_base.cpp index b91da4e5be9d..dd2e1128a546 100644 --- a/src/ipa/libipa/lsc_base.cpp +++ b/src/ipa/libipa/lsc_base.cpp @@ -10,6 +10,19 @@ /** * \file lsc_base.h * \brief Base types and definitions for LscImplementation class hierarchy + * + * Split the common classes and types definitions to a dedicated file to avoid + * circular inclusions. + * + * Both LscTable and LscPolynomial inherit from LscImplementation and they are + * instantiated from lsc.h as the LscAlgorithm::init() function is there inlined. + * + * For this reason lsc.h needs to include lsc_table.h and lsc_polynomial.h which + * need the LscImplementation class definition themselves. + * + * Split the LscImplementation interface definition to this file so that it can + * be included by both lsc_table.h and lsc_polynomial.h, which are then included + * by lsc.h. */ namespace libcamera { @@ -88,7 +101,8 @@ void Interpolator:: * \brief Pure virtual base class for LSC algorithm implementations * \tparam U The fixedpoint LSC engine register format * - * Defines the interface for the LSC algorithm implementation. + * Defines the interface for the LSC algorithm implementation. Currently + * implemented by LscTable and LscPolynomial. */ /** From patchwork Wed Jul 8 15:51:14 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27341 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 8C2ACC3339 for ; Wed, 8 Jul 2026 15:51:58 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 34D626612B; Wed, 8 Jul 2026 17:51:58 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="fzHUVSct"; 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 A89D06608D for ; Wed, 8 Jul 2026 17:51:22 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id ADF30401E; Wed, 8 Jul 2026 17:50:32 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525832; bh=GY5ogt0BidgoFAt5zKvDZ5pfXZ8jQRBTkReJfnAGbZ0=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=fzHUVSctbMQo3YugRs+tTPdzyryl6ksK6aKMNDVWmwBV9LvrgywHGIJZKSDSn58YG QqG6P3bajJBS+qucAZczNAWcutAT59uh9J/FBLLQ7mx2e+vU4x06zXj6jC4873ndNa 7lmLMeXZXvHMJ4zJ+Ym64XzgKb5jCb/o3OpSdqzU= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:51:14 +0200 Subject: [PATCH v5 32/36] ipa: libipa: lsc: Template lsc::Components MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-32-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=16122; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=GY5ogt0BidgoFAt5zKvDZ5pfXZ8jQRBTkReJfnAGbZ0=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHtSJLeR/DKR/NNx1oBBROrZ9PvPMfGleLfd Pq5Bk6koduJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x7QAKCRByNAaPFqFW PBxpD/9IqZQ7tIBqBbGRttX0qx1Qm+Mur7CEfi81atxZpCSSDkCAHbwmr7KZkDOKvS4ruoOA2i1 rUtjRwSApy9+xH+jh3/NqZqCe/yo433lPCXUREVvNr3tYZwhZJH3nlSHpYgg3MV54e81BySJJz/ uj9MNRmUQoIOTmyOvzdMNNR599dYvjpwQWipW32CjQbQmbtkSN2h7fo4uZFN1MrTXMxzI4bJ3Ql 3J4Nf/0Aa7tROZ0pI8OXX96RLW8M/ecqvGP4SM9C/iyqcjELCcYhhaSZ3ZTWl5ugSqZ3mgR4fSY /ve7dBoD+BUQPk6e/CNbUQM79QPnRFe3k3OzEHIhbF27ACZ8dZoUTWCzNYxu2KqJTRAB649KM1N lMGs24GwLPlbh4ClNM1DV7eySfJ3tJ5m0o3I2w4Yzv58pDICMtOSO69kR/IwnG5YvmZ0p6MQ2nk NcrdfzYKet7VeDjy6BqlVXQ3u/QbLJAeIH/Sb4DiZO6x12CXMyTBEAbgSZYy+8jWi6g9y9xgyNK fZ9u0GoIISWTcBhD3gmXL6iSWwb2OvVdfMqWYpCTEzTRIK18G+Hjjt7pkRoyDd9o6vpqbmF9iNp GPGY1PUpS0IGP3j6PN8m0k2VZIa1TjOjGNDc137Hptfu7+AGgibob9K9ClH2CtNvTInf/QknsMk /ul664JLaOdC/ZQ== 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 lsc::Component type maps a string that identifies a colour channel to a vector of gains expressed in register format. The current implementation assumes gains in u16 format which matches the RkISP1 register format. As lsc::Components is used both to store gains as loaded from tuning file and the result of the radial polynomial expansion, different ISP use different bit widths to represent the gain values. Template the lsc::Components and lsc::ComponentsMap with the type used to store the gain values. Use the Quantized::QuantizedType typedef which expresses the integer type required to store all bits of the fixed point number used to instantiate LscAlgorithm<> by the IPA module to template Components<>. The LscTable class now requires to inline all tuning file parsing helpers, so we can drop the just introduced LscTableBase. Specialize Interpolator> for uint16_t as this is the format used by RkISP1. Signed-off-by: Jacopo Mondi --- src/ipa/libipa/lsc.h | 13 +++-- src/ipa/libipa/lsc_base.cpp | 19 +++++--- src/ipa/libipa/lsc_base.h | 21 +++++--- src/ipa/libipa/lsc_polynomial.h | 19 +++++--- src/ipa/libipa/lsc_table.cpp | 100 +++----------------------------------- src/ipa/libipa/lsc_table.h | 90 ++++++++++++++++++++++++++-------- src/ipa/rkisp1/algorithms/lsc.cpp | 4 +- src/ipa/rkisp1/algorithms/lsc.h | 3 +- 8 files changed, 125 insertions(+), 144 deletions(-) diff --git a/src/ipa/libipa/lsc.h b/src/ipa/libipa/lsc.h index d48336b169f4..6dc76b6572b3 100644 --- a/src/ipa/libipa/lsc.h +++ b/src/ipa/libipa/lsc.h @@ -53,6 +53,9 @@ public: template class LscAlgorithm : public LscAlgorithmBase { +private: + using T = typename U::QuantizedType; + public: int init(const ValueNode &tuningData, ControlInfoMap::Map &controls, const LscDescriptor &descriptor) @@ -94,7 +97,7 @@ public: const std::vector &yPos) { LOG(Lsc, Debug) << "Sample Lsc data for " << analogCrop; - lsc::ComponentsMap lscData = + lsc::ComponentsMap lscData = impl_->sampleForCrop(analogCrop, xPos, yPos); /* @@ -112,20 +115,20 @@ public: return 0; } - const lsc::Components interpolateComponents(unsigned int ct) + const lsc::Components interpolateComponents(unsigned int ct) { return sets_.getInterpolated(ct); } - const lsc::ComponentsMap getComponents() + const lsc::ComponentsMap getComponents() { return lscData_; } private: std::unique_ptr> impl_; - Interpolator sets_; - lsc::ComponentsMap lscData_; + Interpolator> sets_; + lsc::ComponentsMap lscData_; bool polynomial_; }; diff --git a/src/ipa/libipa/lsc_base.cpp b/src/ipa/libipa/lsc_base.cpp index dd2e1128a546..235f11e8c53b 100644 --- a/src/ipa/libipa/lsc_base.cpp +++ b/src/ipa/libipa/lsc_base.cpp @@ -32,25 +32,28 @@ namespace ipa { namespace lsc { /** - * \typedef Components + * \class Components * \brief Associate a colour components with a list of gains + * \tparam T The type used to store the gain values when loaded from tuning file * * LSC tables are defined as a list of gain values associated to a 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. + * identifier for the colour component to a list of gains of type \a T. * - * Each key name shall match an entry in the tuning file. + * Each key name shall match an entry in the tuning file and the type \a T + * shall match the size of the registers where gains are stored. * * The list of keys is provided to the LscAlgorithm class using \a * LscDescriptor::keys. */ /** - * \typedef ComponentsMap + * \class ComponentsMap * \brief Associate a colour temperature to a LSC table + * \tparam T The type used to store the gain values when loaded from tuning file * * An LSC table is generated during the tuning phase for a specific light * temperature, and a tuning file usually contains LSC tables generated for @@ -61,10 +64,10 @@ namespace lsc { #ifndef __DOXYGEN__ template<> -void Interpolator:: - interpolate(const lsc::Components &a, - const lsc::Components &b, - lsc::Components &dest, +void Interpolator>:: + interpolate(const lsc::Components &a, + const lsc::Components &b, + lsc::Components &dest, double lambda) { for (auto const &[k, v] : a) diff --git a/src/ipa/libipa/lsc_base.h b/src/ipa/libipa/lsc_base.h index 721fd137ef21..4edcb2637d1b 100644 --- a/src/ipa/libipa/lsc_base.h +++ b/src/ipa/libipa/lsc_base.h @@ -26,8 +26,15 @@ namespace ipa { namespace lsc { -using Components = std::map>; -using ComponentsMap = std::map; +template +class Components : public std::map> +{ +}; + +template +class ComponentsMap : public std::map> +{ +}; } /* namespace lsc */ @@ -43,10 +50,10 @@ void interpolateVector(const std::vector &a, const std::vector &b, } template<> -void Interpolator:: - interpolate(const lsc::Components &a, - const lsc::Components &b, - lsc::Components &dest, +void Interpolator>:: + interpolate(const lsc::Components &a, + const lsc::Components &b, + lsc::Components &dest, double lambda); #endif /* __DOXYGEN__ */ @@ -66,7 +73,7 @@ public: virtual int parseLscData(const ValueNode &tuningData, const LscDescriptor &descriptor) = 0; - virtual lsc::ComponentsMap + virtual lsc::ComponentsMap sampleForCrop(const Rectangle &cropRectangle, std::vector xPos, std::vector yPos); }; diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h index 04d7465b3af0..a84a736734e1 100644 --- a/src/ipa/libipa/lsc_polynomial.h +++ b/src/ipa/libipa/lsc_polynomial.h @@ -67,6 +67,9 @@ protected: template class LscPolynomial : public LscPolynomialBase, public LscImplementation { +private: + using T = typename U::QuantizedType; + public: int parseLscData(const ValueNode &yamlSets, const LscDescriptor &descriptor) override @@ -74,14 +77,14 @@ public: return LscPolynomialBase::parseLscData(yamlSets, descriptor); } - lsc::ComponentsMap + lsc::ComponentsMap sampleForCrop(const Rectangle &cropRectangle, std::vector xPos, std::vector yPos) override { - lsc::ComponentsMap components; + lsc::ComponentsMap components; for (const auto &[t, c] : lscData_) { - lsc::Components comp; + lsc::Components comp; for (const auto &[k, p] : c) { comp.emplace(std::piecewise_construct, @@ -97,17 +100,17 @@ public: } 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) { 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; + std::vector samples; samples.reserve(xPositions.size() * yPositions.size()); diff --git a/src/ipa/libipa/lsc_table.cpp b/src/ipa/libipa/lsc_table.cpp index 0e8f144cb873..4c9ab587d014 100644 --- a/src/ipa/libipa/lsc_table.cpp +++ b/src/ipa/libipa/lsc_table.cpp @@ -13,99 +13,6 @@ LOG_DEFINE_CATEGORY(LscTable) namespace ipa { -/** - * \class LscTableBase - * \brief Base class for LscTable - * - * Base class for LscTable for non-templated functions. - */ - -/** - * \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 LscTableBase::parseLscData(const ValueNode &sets, - const LscDescriptor &descriptor) -{ - for (const auto &set : sets.asList()) { - uint32_t ct = set["ct"].get(0); - - int ret = parseLscComponent(set, ct, descriptor); - if (ret) - return ret; - } - - if (lscData_.empty()) { - LOG(LscTable, Error) << "Failed to load any sets"; - return -EINVAL; - } - - return 0; -} - -int LscTableBase::parseLscComponent(const ValueNode &yamlSet, - unsigned int ct, const LscDescriptor &descriptor) -{ - lsc::Components component; - for (auto &k : descriptor.keys) { - auto [it, inserted] = component.emplace( - std::piecewise_construct, - std::forward_as_tuple(k.c_str()), - std::forward_as_tuple(parseTable(yamlSet, - k.c_str(), - descriptor.numHCells, - descriptor.numVCells))); - if (!inserted || it->second.empty()) { - LOG(LscTable, Error) - << "Set " << k << " for color temperature " - << ct << " is missing"; - return -EINVAL; - } - } - - auto [it, inserted] = lscData_.emplace(ct, component); - if (!inserted) { - LOG(LscTable, Error) - << "Multiple sets found for color temperature " - << ct; - return -EINVAL; - } - - return 0; -} - -std::vector LscTableBase::parseTable(const ValueNode &tuningData, - const char *prop, - unsigned int numHCells, - unsigned int numVCells) -{ - unsigned int lscNumSamples = numHCells * numVCells; - - std::vector table = - tuningData[prop].get>().value_or(utils::defopt); - if (table.size() != lscNumSamples) { - LOG(LscTable, Error) - << "Invalid '" << prop << "' values: expected " - << lscNumSamples - << " elements, got " << table.size(); - return {}; - } - - return table; -} - -/** - * \var LscTableBase::lscData_ - * \brief The tabular LSC data - * - * Maps colour temperatures to per-colour channel vector of gains - */ - /** * \class LscTable * \brief Table based LSC algorithm implementation @@ -119,8 +26,13 @@ std::vector LscTableBase::parseTable(const ValueNode &tuningData, /** * \fn LscTable::parseLscData() + * \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. * - * \copydoc LscTableBase::parseLscData() + * \return 0 on success or a negative error number otherwise */ } /* namespace ipa */ diff --git a/src/ipa/libipa/lsc_table.h b/src/ipa/libipa/lsc_table.h index 65e04b09c3ff..fe4e133060a6 100644 --- a/src/ipa/libipa/lsc_table.h +++ b/src/ipa/libipa/lsc_table.h @@ -23,34 +23,33 @@ LOG_DECLARE_CATEGORY(LscTable) namespace ipa { -class LscTableBase +template +class LscTable : public LscImplementation { -protected: - int parseLscData(const ValueNode &sets, - const LscDescriptor &descriptor); - private: - int parseLscComponent(const ValueNode &yamlSet, - unsigned int ct, const LscDescriptor &descriptor); - std::vector parseTable(const ValueNode &tuningData, - const char *prop, - unsigned int numHCells, - unsigned int numVCells); -protected: - lsc::ComponentsMap lscData_; -}; + using T = typename U::QuantizedType; -template -class LscTable : public LscTableBase, public LscImplementation -{ public: int parseLscData(const ValueNode &sets, const LscDescriptor &descriptor) override { - return LscTableBase::parseLscData(sets, descriptor); + for (const auto &set : sets.asList()) { + uint32_t ct = set["ct"].get(0); + + int ret = parseLscComponent(set, ct, descriptor); + if (ret) + return ret; + } + + if (lscData_.empty()) { + LOG(LscTable, Error) << "Failed to load any sets"; + return -EINVAL; + } + + return 0; } - lsc::ComponentsMap + lsc::ComponentsMap sampleForCrop([[maybe_unused]] const Rectangle &cropRectangle, [[maybe_unused]] std::vector xPos, [[maybe_unused]] std::vector yPos) override @@ -59,6 +58,59 @@ public: << "Tabular LSC data doesn't support resampling"; return lscData_; } + +private: + int parseLscComponent(const ValueNode &yamlSet, + unsigned int ct, const LscDescriptor &descriptor) + { + lsc::Components component; + for (auto &k : descriptor.keys) { + auto [it, inserted] = component.emplace( + std::piecewise_construct, + std::forward_as_tuple(k.c_str()), + std::forward_as_tuple(parseTable(yamlSet, + k.c_str(), + descriptor.numHCells, + descriptor.numVCells))); + if (!inserted || it->second.empty()) { + LOG(LscTable, Error) + << "Set " << k << " for color temperature " + << ct << " is missing"; + return -EINVAL; + } + } + + auto [it, inserted] = lscData_.emplace(ct, component); + if (!inserted) { + LOG(LscTable, Error) + << "Multiple sets found for color temperature " + << ct; + return -EINVAL; + } + + return 0; + } + + std::vector parseTable(const ValueNode &tuningData, + const char *prop, unsigned int numHCells, + unsigned int numVCells) + { + unsigned int kLscNumSamples = numHCells * numVCells; + + 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; + } + + lsc::ComponentsMap lscData_; }; } /* namespace ipa */ diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index 9db67197ad64..7fa495acf10a 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -176,7 +176,7 @@ void LensShadingCorrection::setParameters(rkisp1_cif_isp_lsc_config &config) } void LensShadingCorrection::copyTable(rkisp1_cif_isp_lsc_config &config, - const lsc::Components &set) + const lsc::Components &set) { const auto &r = set.at("r"); std::copy(r.begin(), r.end(), &config.r_data_tbl[0][0]); @@ -235,7 +235,7 @@ void LensShadingCorrection::prepare([[maybe_unused]] IPAContext &context, setParameters(*config); - const lsc::Components &set = lscAlgo_.interpolateComponents(quantizedCt); + const lsc::Components &set = lscAlgo_.interpolateComponents(quantizedCt); copyTable(*config, set); lastAppliedCt_ = ct; diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h index 31156f56cd21..eb32997d64bc 100644 --- a/src/ipa/rkisp1/algorithms/lsc.h +++ b/src/ipa/rkisp1/algorithms/lsc.h @@ -42,7 +42,8 @@ public: 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 lsc::Components &set0); std::vector parseSizes(const ValueNode &tuningData, const char *prop); std::vector sizesListToPositions(Span sizes); From patchwork Wed Jul 8 15:51:15 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27342 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 31F6FC3329 for ; Wed, 8 Jul 2026 15:51:59 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D869D6610F; Wed, 8 Jul 2026 17:51:58 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="nFRDx2dN"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0ECF8660B8 for ; Wed, 8 Jul 2026 17:51:23 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 210624045; Wed, 8 Jul 2026 17:50:33 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525833; bh=Gv1Zzsx5m30sFYdnWZws32iqLMjIF1ZPRqK2SkY9WcM=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=nFRDx2dNu6j7oSAUoExcjabZulTjdw7UEiCJpLTv3JDJ2ridjXZLZ2AuEHQm5FuFp PjVthufwRp/TykOUyaoYngYl197JNfLHn6flpafFa8kV4kDu/LcnuSUvBRqBjg5f/i a8P23gd0gTz2hs4J8EMSHhT4rrceN/VeFRs/gmVU= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:51:15 +0200 Subject: [PATCH v5 33/36] ipa: libipa: lsc: Rename sampleForCrop to resampleLscData MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-33-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=4426; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=Gv1Zzsx5m30sFYdnWZws32iqLMjIF1ZPRqK2SkY9WcM=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHtZ3d7O05pivQ2a/50JqUkhALCtBDb6kLl5 vPCvsvSsMiJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x7QAKCRByNAaPFqFW PNd4D/kBhhI9X8fn6d47yDKYdNTEIu9QO7GVzQtUwok7JbnHjEgPaO1xt6yTyKl8iFDLz7dA4pP JWwsoI2v3OD7Jw/fIX11SbjygNkaLiEpvreeCSOqWlHkguu1NWVqv+bk7wUz1FIKag4ZVFVw98X qbcFTvmBjjVokkk5UzGYG4UClG8JHEgiYWELVFxUkNYWzbJmzKJ+3eIgb4Wd/+SvK3mMCDCn6wK hGyZuWYTv9aynvjf0e2DqFHXJZmOYxrAHXXh5MdiVxXq8I5nQV7MrdOuA01hcKq9d/mHAhYh4wm ZXUGn9fRmx2TcCDBPxc00lKi3nh6dcY6ynTvfKmnATgKoMNyuC7KbHfp8y7zChs2Bezg3TnctUI Opea2AaYi7AZ19qGbT587Now9mKHDGRnAmUwDcFc7HL51JjMQiAE0BKIl/Gt9Zec7w5vhqNV9yu EbVVJqoOzRnqbvVH7t2GMbY4bAh9UNMo8qKPuHj1eN1r23Q3RKqKzexkt0SsoP5rXL+SRxCAosR pmCutH2StRqKp8GGEAAbrHnTtm/VBUXU3uLuEmrTTBlJOb3FAuDkCWlIMqThWJ/0kazjl4eNyQF qvptcytfF6JtcA/V5LZjy00tXD2IksqCZIwwNXKmXQp+jnamenUVw5GvvVqXeKcpJHsnpovndhm XYhVYNPOPg8QsCA== 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" Rename the function to a more precise name. Signed-off-by: Jacopo Mondi --- src/ipa/libipa/lsc.cpp | 2 +- src/ipa/libipa/lsc.h | 2 +- src/ipa/libipa/lsc_base.cpp | 2 +- src/ipa/libipa/lsc_base.h | 4 ++-- src/ipa/libipa/lsc_polynomial.cpp | 2 +- src/ipa/libipa/lsc_polynomial.h | 4 ++-- src/ipa/libipa/lsc_table.h | 6 +++--- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp index f5646937c60a..0ce40c09caed 100644 --- a/src/ipa/libipa/lsc.cpp +++ b/src/ipa/libipa/lsc.cpp @@ -269,7 +269,7 @@ void LscAlgorithmBase::process(lsc::FrameContext &context, ControlList &metadata * 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 + * \sa LscImplementation::resampleLscData * * \return 0 on success, a negative error code otherwise */ diff --git a/src/ipa/libipa/lsc.h b/src/ipa/libipa/lsc.h index 6dc76b6572b3..1f46555f9bf0 100644 --- a/src/ipa/libipa/lsc.h +++ b/src/ipa/libipa/lsc.h @@ -98,7 +98,7 @@ public: { LOG(Lsc, Debug) << "Sample Lsc data for " << analogCrop; lsc::ComponentsMap lscData = - impl_->sampleForCrop(analogCrop, xPos, yPos); + impl_->resampleLscData(analogCrop, xPos, yPos); /* * Retain a copy of the components table. diff --git a/src/ipa/libipa/lsc_base.cpp b/src/ipa/libipa/lsc_base.cpp index 235f11e8c53b..c87e2680d973 100644 --- a/src/ipa/libipa/lsc_base.cpp +++ b/src/ipa/libipa/lsc_base.cpp @@ -127,7 +127,7 @@ void Interpolator>:: */ /** - * \fn LscImplementation::sampleForCrop + * \fn LscImplementation::resampleLscData * \brief Re-sample the LSC components for \a cropRectangle * \param[in] cropRectangle The sensor analogue crop rectangle * \param[in] xPos List of horizontal positions of the LSC grid nodes diff --git a/src/ipa/libipa/lsc_base.h b/src/ipa/libipa/lsc_base.h index 4edcb2637d1b..a25d951e8ddb 100644 --- a/src/ipa/libipa/lsc_base.h +++ b/src/ipa/libipa/lsc_base.h @@ -74,8 +74,8 @@ public: const LscDescriptor &descriptor) = 0; virtual lsc::ComponentsMap - sampleForCrop(const Rectangle &cropRectangle, - std::vector xPos, std::vector yPos); + resampleLscData(const Rectangle &cropRectangle, + 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 060fba9826a0..aab45ab4f15c 100644 --- a/src/ipa/libipa/lsc_polynomial.cpp +++ b/src/ipa/libipa/lsc_polynomial.cpp @@ -190,7 +190,7 @@ int LscPolynomialBase::parseLscData(const ValueNode &sets, */ /** - * \fn LscPolynomial::sampleForCrop() + * \fn LscPolynomial::resampleLscData() * \brief Re-sample the LSC components for \a cropRectangle * \param[in] cropRectangle The sensor analogue crop rectangle * \param[in] xPos List of horizontal positions of the LSC grid nodes diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h index a84a736734e1..00bbd068d537 100644 --- a/src/ipa/libipa/lsc_polynomial.h +++ b/src/ipa/libipa/lsc_polynomial.h @@ -78,8 +78,8 @@ public: } lsc::ComponentsMap - sampleForCrop(const Rectangle &cropRectangle, - std::vector xPos, std::vector yPos) override + resampleLscData(const Rectangle &cropRectangle, + std::vector xPos, std::vector yPos) override { lsc::ComponentsMap components; diff --git a/src/ipa/libipa/lsc_table.h b/src/ipa/libipa/lsc_table.h index fe4e133060a6..c3d6c6b95dcd 100644 --- a/src/ipa/libipa/lsc_table.h +++ b/src/ipa/libipa/lsc_table.h @@ -50,9 +50,9 @@ public: } lsc::ComponentsMap - sampleForCrop([[maybe_unused]] const Rectangle &cropRectangle, - [[maybe_unused]] std::vector xPos, - [[maybe_unused]] std::vector yPos) override + resampleLscData([[maybe_unused]] const Rectangle &cropRectangle, + [[maybe_unused]] std::vector xPos, + [[maybe_unused]] std::vector yPos) override { LOG(LscTable, Warning) << "Tabular LSC data doesn't support resampling"; From patchwork Wed Jul 8 15:51:16 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27340 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 0890CC3338 for ; Wed, 8 Jul 2026 15:51:58 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A3C8766111; Wed, 8 Jul 2026 17:51:57 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="gw1M6ND6"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 5214C660D7 for ; Wed, 8 Jul 2026 17:51:23 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7309A4059; Wed, 8 Jul 2026 17:50:33 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525833; bh=pKxxZ9cajwGg/4XFTEBKJ35/7Bmlg5h/GcltukR9A5Y=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=gw1M6ND6YIZtKzRMAqp7875ugVhah/zvvxb7lvadNtlXDub+XL0JQAWOe4rEFe3w1 nufMYXkhJapRlmK/mcra+RQFZGK5jCs+QMRoSOHYk+DL4cEHc2ML6TvqdluO5Uniax 3Swab3eNCbwrtAEFAKpBFxUxBPgQ9asIWbNyyf/A= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:51:16 +0200 Subject: [PATCH v5 34/36] ipa: libipa: lsc: Specialize Interpolator for uint8_t MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-34-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=1736; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=pKxxZ9cajwGg/4XFTEBKJ35/7Bmlg5h/GcltukR9A5Y=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHtJT9XBhcWqONPcXhgf4AAu/oHcL8TpuY5G qSQwB1qC0iJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x7QAKCRByNAaPFqFW PA/BEACbet4XP/rkUgrxopirdwDFiyhdtdhN4pN06m8DAV19t9qjfx7QHx39W604X9bPUG6ufsk taf7jjGNOLYonk8I1UwprQl1AiOjXhx0c0swPNGlQuJtVT4DB88MCt2IHLISGOuBReJpffMLgfg pU9zJmUy44jgXjNu+768EEvzx1bHqV1p7sOFchbuXkdYzIsjXEeT63FfBKgfKi+SBxwjLBTj3/k 0GkMbJqp2PNqyRKV1vu/gTnmEiRhMtuU+Q8i31HOIx99gxXw2/H9s8ysS4wHUq4qGY0LJ3qxzB6 iIWTwRzwyslfV6r96XdZ1DS0v/u535enjPFIZ90CzaDbgUMtQAX7HZ4JPJstfIw7xSLWVMlA9lk i1tvAgFgIpYA64Y8mpxlGG/ZbC+ZptxQK72exo+r+iFyIpJ6swmuddaKRDa90WEHhZoqntV09X5 rNje8oiXnlnO4FSK2kUyf1g1c5cisVWPtV+V5QPjL18RNffCBRrFKBwi6vNPS3TnlVLkogUnWB0 xZCDLrBsofhWJ677egM0HVF/k+5iYlNMLK2U4Uzscvs3PSsgNup+Typqqdrp3snXtCvcGpmF6cp 0MBilIIK4/wltPhJSG372+SSFtl7hwss8qy9dTtg8trb20Rvev26ujYVcw49l9VzKJiZ5V4Ctax tGVi1nBdvonW8fg== 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 Mali-C55 LSC algorithm uses 8-bit gain values. Specialize Interpolator> for uint8_t to prevent the uint16_t specialization from being selected. Signed-off-by: Jacopo Mondi --- src/ipa/libipa/lsc_base.cpp | 11 +++++++++++ src/ipa/libipa/lsc_base.h | 7 +++++++ 2 files changed, 18 insertions(+) diff --git a/src/ipa/libipa/lsc_base.cpp b/src/ipa/libipa/lsc_base.cpp index c87e2680d973..a0b78ac5265b 100644 --- a/src/ipa/libipa/lsc_base.cpp +++ b/src/ipa/libipa/lsc_base.cpp @@ -63,6 +63,17 @@ 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); +} + template<> void Interpolator>:: interpolate(const lsc::Components &a, diff --git a/src/ipa/libipa/lsc_base.h b/src/ipa/libipa/lsc_base.h index a25d951e8ddb..51f3dd91f547 100644 --- a/src/ipa/libipa/lsc_base.h +++ b/src/ipa/libipa/lsc_base.h @@ -49,6 +49,13 @@ void interpolateVector(const std::vector &a, const std::vector &b, 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); + template<> void Interpolator>:: interpolate(const lsc::Components &a, From patchwork Wed Jul 8 15:51:17 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27343 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 EDB75C333A for ; Wed, 8 Jul 2026 15:51:59 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7FBDB6611D; Wed, 8 Jul 2026 17:51:59 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ktUhO40t"; 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 A9A9A660CE for ; Wed, 8 Jul 2026 17:51:23 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id BDD9F4061; Wed, 8 Jul 2026 17:50:33 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525833; bh=ax8BgGImoAq8vCACjcTubXq3ibzUonWpyk48NOKhiQI=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=ktUhO40tpLxQHxPo5lebzyox+dO08xAvezFaHSKOJMSWXcyn1Y6MKmFoYc4bzrcOG qCmskuwaZissoZc7TpmYfkdJyPwtfEPZIoZN2xUvRZR2hz4u22V9sPwVZsS1uEj00D 7To06BMZ5lxohAo8FXm1VmfnQa2OQJXqiuBade2U= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:51:17 +0200 Subject: [PATCH v5 35/36] ipa: mali-c55: Add sensorInfo to IPAContext MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-35-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug 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/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHuzm9Hqt41m4+RsSUWSOjvfNwuBCVYmhZTe Oc2fu75uPuJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x7gAKCRByNAaPFqFW PL6UD/oDvBEXdqluEFNmYqlVRrLUHqQgqWPRwujtUyJyrsPw3GlQomDgQvN9XFFIFqaD8oZnomC XYkyrOm3Z8OC6dcnz5UZbpj/HPJn1TQiUH7hgYaXWo2dMtIxmkw4PelSz3LHs1M7y6MikC2jegI zs036PWt7w9erF2pOI/no2L+5yQW7oDWU8zHxqg09t/kzFsyQduwStyZVGSfC7CAs8Ds8KDeeti hqsTEUWC/j9zVNyTSohTKhsTRgpoUkT59ucrwflseZ9ed2mhMOTPgB2EgNGz9JHd2H069yQXIdG E0L+pCh2FbcGzimnPpPzFit2c3vnL1nWUNgxyO4K/FsGzN/qNJXCy1WoklLhSL5CCbqS5E1pvYS Gwha5hDm696zcJL1I+YeWSeoqd49NlY8pJZNQ+5wae/dAwZYxIUGEjCyMeK8Mn3OpdRTZ9LAAop xifz8YJchHinqL+3v8FLGh5Cd40xCIPRsr53p0mJNf6RNPN5L+AFzqd988V9Wz/PYgXzhXO1OTt v/xUmg89x7b+VeK3gAdliPIqlKsNj67GEyUWecMBZ9Wsx5DCXyN4fSEtrbCiUOufVL4pZP07VmP B6exaWM4WwamWIb/CfmKPpIUP67+FjPZPeG3hLd30uosUXLMF9akfZ1I3E5OGzvdq+tK5oCuEqt yk90V/meTHn3rMQ== 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 Wed Jul 8 15:51:18 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27344 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 7BB34C333B for ; Wed, 8 Jul 2026 15:52:00 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 22E2C6611C; Wed, 8 Jul 2026 17:52:00 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="J4xgvpuM"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 1489F660E5 for ; Wed, 8 Jul 2026 17:51:24 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 244D640AD; Wed, 8 Jul 2026 17:50:34 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783525834; bh=gBpwl50taYAIJtQ3lC8VXTG531PwJCLIUQHX6s44Emo=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=J4xgvpuMiFFfzI9p+VEipFNDf88Yu42IDB6sn9Knam623N+Wcid2+PyAGvfustcNo 8fqPdrQkIvuJZAGSViRIvi9Gi/+Fv5epzp11wSITXn46bpqUCDai7+zykpzBfEnPGe 6nbrKR+deGva/evh5ZsjMOA7Ylbf/3bDDrfV0q5Q= From: Jacopo Mondi Date: Wed, 08 Jul 2026 17:51:18 +0200 Subject: [PATCH v5 36/36] ipa: mali-c55: Port to use LscAlgorithm MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v5-36-0759d0359f52@ideasonboard.com> References: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi , Kieran Bingham , Daniel Scally X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=10843; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=gBpwl50taYAIJtQ3lC8VXTG531PwJCLIUQHX6s44Emo=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHuZjwBjze/PMt2K9ClMCoc3ATVePPr8WK7e Lz9kyFSbBaJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x7gAKCRByNAaPFqFW PN4jD/9oOBmaeifR+1Q/Pjpgvn2sagY7xENT8NoocBQa2qt0b0+AWkyLfMClv1n1K2RQ74ifCUn W30GPMfELdAXBLQJfc+eizdG33viaE4Rn7qWBdWOD/qviJBxlDIB+l64ufGfOIlORe5wmV3plLJ XsSF+qNgorxXdRkClkWPzGNrvc0QCfTrtOzoguwAelNAyz/XeAQy7yWVNk9BZFL7kIOj3HvrVVw 4+ojFaz4VLUnt7K6NZj62Wj2FAVi1+Xej2VT20lsUBP6mFfsxwdJqJM7iY5/zkNZebZrXpftRfd cxgRF/VZ02wMl6dYpo6qHxBI1ix+JywSRzZpRZkoCpF+nD7tVmfa7zuWlHhm4yFb/5+BCALV2if kjAodjCAgOnT9ZtmjHqVZLaWSApOlm/V7gjWyr9zLAD6CW6UB2ROqg4hAug9gx7QTHKiJUZXPxt euW11CYUwQv+fORSM84E12ZWjwGw0Qternl0FXlxgXf5N8vb4P9C3S88WP5vI650Jbz5oe5HH0Z AoQkiHG07HXtsTqaRfkGKHYo+VCX/6VB6mPlqghXXTnc2VV4hxcYFtMx1xcm9az4Npsh7u7iWPP rtLwTC0oSyz4xH+xBhw8TZK/zyn7UV/CbfiVc7DhfOkftLHtc9FL3uoSsRgsrcfYD19NMUCqpoJ 2gDukHF5yYLPaEA== 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..36b422939e43 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" }, + .numHCells = kMeshSize, + .numVCells = 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. + */ + const 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 {