From patchwork Mon Jul 20 14:59:38 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27412 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 1F579C3303 for ; Mon, 20 Jul 2026 15:00:13 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A789E67E97; Mon, 20 Jul 2026 17:00:12 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Tf1uhYpX"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 73E6167E44 for ; Mon, 20 Jul 2026 16:59:52 +0200 (CEST) Received: from [192.168.1.106] (mob-5-90-63-152.net.vodafone.it [5.90.63.152]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8738529D1; Mon, 20 Jul 2026 16:58:53 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559533; bh=yBgKpKiqgzf/n4L5tBrq7FuAKFUf5UkNAiwow3alboE=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=Tf1uhYpXp/Cg+QG/+oPkYYQJxBn/TDiyktDsfHbZic9cg4dQRpXUYSQhrGJGp2NVx y3hIYSNI+3tBKiwn0Y+RoDRpENHdUKaLN0tmepSrNyF+sdyMsIEFTZOth822kCPt1u VIklPG4KC7UYhNadHLrujyTIsutdxLEXwxhxlq+c= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:38 +0200 Subject: [PATCH v6 20/31] ipa: libipa: lsc: Introduce LscAlgorithm MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-20-ececb73f97cb@ideasonboard.com> References: <20260720-libipa-algorithms-v6-0-ececb73f97cb@ideasonboard.com> In-Reply-To: <20260720-libipa-algorithms-v6-0-ececb73f97cb@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=11537; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=yBgKpKiqgzf/n4L5tBrq7FuAKFUf5UkNAiwow3alboE=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfY3GC5Pf3Z0buBh9WwCehA2W9zixETasyY1 Nf0dbe3O3iJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal432AAKCRByNAaPFqFW PLCXEADAeUQrA3OXVOMKzC89EYHqx61a+qg9WHudHeRuJMiWj6nYtZfsrOZvEvwX2/VShOcDY6G B7x0yj39zVojUigUxnVK2i4e8xIdi5WR8l1/j5LRFZmox2sV8Y0+MZM0oV/YoW4/j8GBM8bBqi1 r31x/7N6Cl1MsFIL97rdKDcEThszFYx8yOMEwCPkzJNoNk1fsMtDZBHl4KQrXILrbRsdzm8WeAd s76Ow79zTgSuC+PDa/YxWLBE6ZzvUPUuaMgrlD43wYB2yt/1zkpXXonmuoCbivWDzpwMYBbF2bQ tAcrJLFHxvrBYX8eZQNPDtI4U4yFUZZSLhGKwcrDtji4s75jDzIzYVPpOQyQ8o+8vWiZ8AwqE6Z zTj0kCwUZy52DRStNNTBMsMHsvX08sa60mnJRQWln+LrnDImQjIZNlul812QwMJqeF6q+OQxguL S0qmmHEDrxOv6mV7yEyWc9FltOe5uZfRwJskGKt5NZTMee8jHJwsjDvFFkMf7uFRHpOhdRi7PVw iVIb0h1AlxzlWFi1dkX616HmOTOqgz9+5wIWdnZV0NwUblAwiQs/62ySenSpmmWD5OLY4R2fST4 73R/XWX8kUtN9ZX4MLnuhzQFgQuZITr4nSMqx8RRj205+3vdSZogCzV6EC+CfIx2/I0lVS+ia1P ya9kNimqD2+6vOg== X-Developer-Key: i=jacopo.mondi@ideasonboard.com; a=openpgp; fpr=72392EDC88144A65C701EA9BA5826A2587AD026B X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Introduce the lsc.h and lsc.cpp files with the definition of the Lsc algorithm active state, frame context and the LscAlgorithm class. The LscAlgorithm class uses two backends (LscPolynomial and LscTable) to perform tuning file parsing and re-scaling of the LSC gains. Signed-off-by: Jacopo Mondi Reviewed-by: Stefan Klug Reviewed-by: Kieran Bingham --- src/ipa/libipa/lsc.cpp | 253 +++++++++++++++++++++++++++++++++++++++++++++ src/ipa/libipa/lsc.h | 71 +++++++++++++ src/ipa/libipa/meson.build | 2 + 3 files changed, 326 insertions(+) diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp new file mode 100644 index 000000000000..dd69c7a80b17 --- /dev/null +++ b/src/ipa/libipa/lsc.cpp @@ -0,0 +1,253 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + * + * libIPA Lsc algorithms + */ + +#include "lsc.h" + +#include + +#include + +#include "lsc_polynomial.h" +#include "lsc_table.h" + +/** + * \file lsc.h + * \brief libipa LSC algorithm + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(Lsc) + +namespace ipa { + +namespace lsc { + +/** + * \struct ActiveState + * \brief The LSC active state + * + * \var ActiveState::enabled + * \brief Boolean flag for the LscAlgorithm enable status + */ + +/** + * \struct FrameContext + * \brief The LSC frame context + * + * \var FrameContext::enabled + * \brief Boolean flag for the LscAlgorithm enable status + * + * \var FrameContext::update + * \brief Boolean flag for the LscAlgorithm updated status + */ + +} /* namespace lsc */ + +/** + * \class LscAlgorithm + * \brief libIPA LSC algorithm implementation + * + * Due to the optical characteristics of the lens, the light intensity received + * by the sensor is not uniform. The Lens Shading Correction algorithm applies + * multipliers to all pixels to compensate for the lens shading effect. + * + * The LscAlgorithm implements the libipa Lens Shading Correction algorithm + * using an implementation of the LscImplementation interface. + * + * This class provides support for parsing the tuning file content and + * generates tables indexed by colour temperature to store per-colour-channel + * gains for the IPA algorithm to be able to program the LSC engine. + * + * The init() function parses the tuning file and loads the gain tables either + * in tabular form (LscTable) or as radial polynomials (LscPolynomial). The gain + * tables are indexed by colour temperature with per-colour-components vectors + * of gain values or polynomial coefficients. + * + * At LscAlgorithm::configure() time the LSC tables are re-sampled on the + * sensor's crop rectangle in use to adapt them to the configuration in use for + * a streaming session. Polynomial LSC tables support re-sampling and can be + * applied to any sensor configuration. Grid-based LSC tables cannot currently + * be re-sampled and the configuration as parsed from the tuning file is used + * for all sensor configurations providing best-effort results. + * + * \todo Implement grid based re-sampling + * + * When the IPA algorithms wants to get access to the (re-sampled) tables to + * program its LSC engine, it uses LscAlgorithm::interpolateComponents() to get + * an LSC table interpolated by the LscAlgorithm class for the specified colour + * temperature. If the algorithm wants to access the non-interpolated tables it + * can retrieve them using LscAlgorithm::getComponents(). + */ + +/** + * \param[in] tuningData The tuning data + * \param[in] sensorSize The physical sensor size + * \param[in] controls The IPA list of supported controls + * + * Parse \a tuningData according to the settings specified in \a descriptor to + * populate the LSC data and registers LSC controls in \a controls. + * + * \return 0 on success, a negative error code otherwise + */ +int LscAlgorithm::init(const ValueNode &tuningData, const Size &sensorSize, + ControlInfoMap::Map &controls) +{ + polynomial_ = false; + + std::string type = tuningData["type"].get("table"); + if (type == "table") { + impl_ = std::make_unique(); + LOG(Lsc, Debug) << "Using table-based Lsc"; + } else if (type == "polynomial") { + /* + * \todo Most likely the reference frame should be native_size. + * Let's wait how the internal discussions progress. + */ + impl_ = std::make_unique(sensorSize); + polynomial_ = true; + LOG(Lsc, Debug) << "Using polynomial Lsc"; + } else { + LOG(Lsc, Error) << "Unsupported Lsc algorithm '" + << type << "'"; + return -EINVAL; + } + + const ValueNode &yamlSets = tuningData["sets"]; + if (!yamlSets.isList()) { + LOG(Lsc, Error) << "'sets' parameter not found in tuning file"; + return -EINVAL; + } + + int ret = impl_->parseLscData(yamlSets); + if (ret) + return ret; + + controls[&controls::LensShadingCorrectionEnable] = + ControlInfo(false, true, true); + + return 0; +} + +/** + * \param[in] state The LSC active state + * \param[in] analogCrop The current sensor analog crop rectangle + * \param[in] xPos List of horizontal positions of the LSC grid nodes + * \param[in] yPos List of vertical positions of the LSC grid nodes + * + * Re-sample the LSC data for an \a analogCrop. + * + * LSC tables are generated at tuning time using a known sensor configuration. + * When a new streaming session is started, it might use a different sensor + * configuration for which the LSC tables need to be adjusted to. + * + * This function re-generates the LSC tables to adapt them to a new sensor + * configuration, specifically it re-samples the LSC data for a new \a + * analogCrop on a grid specified by \a xPos and \a yPos. Re-sampling of + * LSC data is currently supported by polynomial-based LSC tables. + * + * \sa LscImplementation::sampleForCrop + * + * \return 0 on success, a negative error code otherwise + */ +int LscAlgorithm::configure(lsc::ActiveState &state, const Rectangle &analogCrop, + const std::vector &xPos, + const std::vector &yPos) +{ + LOG(Lsc, Debug) << "Sample Lsc data for " << analogCrop; + lsc::ComponentsMap lscData = + impl_->sampleForCrop(analogCrop, xPos, yPos); + + /* + * Retain a copy of the components table. + * + * We could avoid a copy here if getComponents() could + * return sets_.data() but I wasn't able to work around the + * compiler refusing it. + */ + lscData_ = lscData; + + sets_.setData(std::move(lscData)); + state.enabled = true; + + return 0; +} + +/** + * \brief Queue a request to the lsc algorithm + * \param[in] state The lsc active state + * \param[in] context The lsc frame context + * \param[in] controls The list of controls associated with a Request + * + * Queue a new list of \a controls to the lsc algorithm. + * The only supported control is controls::LensShadingCorrectionEnable. + */ +void LscAlgorithm::queueRequest(lsc::ActiveState &state, + lsc::FrameContext &context, + const ControlList &controls) +{ + const auto &lscEnable = controls.get(controls::LensShadingCorrectionEnable); + if (lscEnable && *lscEnable != state.enabled) { + state.enabled = *lscEnable; + + LOG(Lsc, Debug) + << (state.enabled ? "Enabling" : "Disabling") << " Lsc"; + + context.update = true; + } + + context.enabled = state.enabled; +} + +/** + * \brief Populate the list of lsc metadata + * \param[in] context The lsc frame context + * \param[in] metadata The list of metadata + * + * Populates the list of \a metadata with controls handled by the LscAlgorithm + * class. The only supported metadata is controls::LensShadingCorrectionEnable. + */ +void LscAlgorithm::process(lsc::FrameContext &context, ControlList &metadata) +{ + metadata.set(controls::LensShadingCorrectionEnable, context.enabled); +} + +/** + * \fn LscAlgorithm::interpolateComponents + * \brief Interpolate the LSC tables for a given colour temperature + * \param[in] ct The colour temperature + * + * LSC tables are generated using different colour temperatures during the + * tuning phase. + * + * This function returns the interpolated LSC data for a given \a ct + * colour temperature. + * + * IPA algorithm can use this function to obtain a list of per-colour-component + * gains to program their LSC engines with every time a significant enough + * change in colour temperature is detected. + * + * Calling this function is only valid after LscAlgorithm::configure() has been + * called. An empty components list is returned otherwise. + * + * \return The LSC gains table interpolated for temperature \a ct + */ + +/** + * \fn LscAlgorithm::getComponents + * + * Return the map of LSC data per-colour-temperature. + * + * Calling this function is only valid after LscAlgorithm::configure() has been + * called. An empty components list is returned otherwise. + * + * \return The map of LSC gains tables per-colour-temperature + */ + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/lsc.h b/src/ipa/libipa/lsc.h new file mode 100644 index 000000000000..9a51d32c3464 --- /dev/null +++ b/src/ipa/libipa/lsc.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + * + * libIPA Lsc algorithm + */ + +#pragma once + +#include +#include + +#include +#include + +#include "libcamera/internal/value_node.h" + +#include "interpolator.h" +#include "lsc_base.h" + +namespace libcamera { + +namespace ipa { + +namespace lsc { + +struct ActiveState { + bool enabled; +}; + +struct FrameContext { + bool enabled; + bool update; +}; + +} /* namespace lsc */ + +class LscAlgorithm +{ +public: + int init(const ValueNode &tuningData, const Size &sensorSize, + ControlInfoMap::Map &controls); + + int configure(lsc::ActiveState &state, const Rectangle &analogCrop, + const std::vector &xPos, + const std::vector &yPos); + + void queueRequest(lsc::ActiveState &state, lsc::FrameContext &context, + const ControlList &controls); + void process(lsc::FrameContext &context, ControlList &metadata); + + const lsc::Components interpolateComponents(unsigned int ct) + { + return sets_.getInterpolated(ct); + } + + const lsc::ComponentsMap getComponents() + { + return lscData_; + } + +private: + std::unique_ptr impl_; + Interpolator sets_; + lsc::ComponentsMap lscData_; + bool polynomial_; +}; + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build index 95d398d99fd6..42f89d9820fa 100644 --- a/src/ipa/libipa/meson.build +++ b/src/ipa/libipa/meson.build @@ -14,6 +14,7 @@ libipa_headers = files([ 'fixedpoint.h', 'histogram.h', 'interpolator.h', + 'lsc.h', 'lsc_base.h', 'lsc_polynomial.h', 'lsc_table.h', @@ -38,6 +39,7 @@ libipa_sources = files([ 'fixedpoint.cpp', 'histogram.cpp', 'interpolator.cpp', + 'lsc.cpp', 'lsc_base.cpp', 'lsc_polynomial.cpp', 'lsc_table.cpp',