From patchwork Wed Jul 8 09:25: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: 27287 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 1A857C3318 for ; Wed, 8 Jul 2026 09:25:38 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id DC082660C7; Wed, 8 Jul 2026 11:25:35 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="aUfTV8+/"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id DF6346609A for ; Wed, 8 Jul 2026 11:25:11 +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 4F6B03FBB; Wed, 8 Jul 2026 11:24:22 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502662; bh=ufGN47Y+M2apUceF3o/l4FY6OxM8VSz3Mf75D2CYr2A=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=aUfTV8+/AEwblF3JJ65SXTFkTdJpsIdk/+9YKoh6+Q54A6KrTl5BapeLeeaTcpeSO gzfKlOkc/vwtj25amMjnsA7KtfqSugx6ey+UfVO398BmgzDJe+1uxWRBzo2fN8onof zdi1HXhhYtD7i3+Jtwy5vlJKumWcsxCsBADBN2VE= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:25:09 +0200 Subject: [PATCH v4 22/35] ipa: libipa: lsc: Introduce LscAlgorithm MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-22-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@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/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThdudRhbcyWqR+U9zdnDdHB8Emni0MHXOgd4K ucHOJzQwHyJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XbgAKCRByNAaPFqFW PNGnD/9nn3D+4WLLRqMoEC4wMkS3gwXsv9IrH9BkylSYYARwVIBryXMgGs7xpjpW/9TL4lolOra Xi6BSOZXgVhfvDEfmZo7AUFrbnDNUPDWLRuymqbrUzi2Pnca+8ndCj3BO8oLh16Jo3NGOVg4swc Y2jKc2WkfX1Q7gWxbt2sB8HbeOx+/3prz00Z3M5OS2eIUPSoqftry8InhplVDnZiO/mRR721oEB +SvG6CGM7eesnAsETjctjrb08Z2i/H0fR1TnOWMMp3PUVSSkpkmTAzWyfE+oP3LiBDuGV1gmNBx dNWXjJ6eZyI9HXQSuh+jq8lvZuCVyud7bcdrw8VeMeqFeZN0WnvavQflYcsPlW8stI+RZQ/EPgm tThf3SYohQ/sGPs5wp3J6e0v09fHsOgLSvIkBzVhEO8RqcPFC7a+T2NTNP/38mtNVl5uuGcy+gx ptmhfTSQPIoJ7Di1RVXibgFfN82eVMQM1S6KD34MF+xsUY0oSI5JLWnFJU+BBQaFXo9sbuRfIfh iwOr+QPAJKUX58tAjOYilG2WiN4rPy+rK4wKUgBlvYSKXjnAirVWSuNOL6Op2Iv4E25m8gIkgQG 7pLQ08oehxDEhvUh8dc8qMgG9hXUqFq6+1Mr/TWMpp5m0MLv8RV49Ysetu77EAzR0e4MYkQXp4I xL7kmvtpPHkGD6Q== 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 */