From patchwork Mon Jun 15 14:05:34 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 26886 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 9928EC3301 for ; Mon, 15 Jun 2026 14:06:06 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D5EB7623FA; Mon, 15 Jun 2026 16:06:02 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="PE92C4UM"; 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 65DF4623EC for ; Mon, 15 Jun 2026 16:05:49 +0200 (CEST) Received: from [192.168.1.104] (net-93-65-100-155.cust.vodafonedsl.it [93.65.100.155]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 92D60241; Mon, 15 Jun 2026 16:05:16 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1781532316; bh=il6dW1yhdjqzh7pzp9AACFqt7c/EgOicybB1lNxWM78=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=PE92C4UMpQ4vlJfJfj4Jz+jVxJnxDbkuIezpyvcX9SD3kphOiWPrEYEvSneEhD8H1 HyICdpCeVthVXIxqeUbaC+uYJaf59XNTUTaFO8nd0baBp1cN6aJFO0KBcHpi4dRcC6 +NdNm/nAdAEH4Xk4H9SVtC+jr5dLjKZFslTAnwig= From: Jacopo Mondi Date: Mon, 15 Jun 2026 16:05:34 +0200 Subject: [PATCH 09/11] ipa: libipa: Introduce LscAlgorithm MIME-Version: 1.0 Message-Id: <20260615-libipa-algorithms-v1-9-e949c937422e@ideasonboard.com> References: <20260615-libipa-algorithms-v1-0-e949c937422e@ideasonboard.com> In-Reply-To: <20260615-libipa-algorithms-v1-0-e949c937422e@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=58176; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=il6dW1yhdjqzh7pzp9AACFqt7c/EgOicybB1lNxWM78=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqMAa5vp8qTM83epZioQu7EdJVoGDXEUalj+W3k DcHd68U2HaJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCajAGuQAKCRByNAaPFqFW PDwJD/kBwzJ5KLtK/UtgWf4FD9/1ubii5IFd2pugmaLFoVRC8HSCb1V3gLR/xYED7odZVVrc+Nv zLtKtR9M9nnwN448By7xGAGY0AmPiRcwS1kgd/afyib9QZiIGtct6VmWZJ2QOI5aJF3F3rrfx5w lOgj6erZLGQoWtkmtyIolOiTgwb6bUua3IVSueU5LuhnXtS0IWV6M0xLnQbN752sQm1+FqxqpGS hWmkUlJzjrA6UStXeCPQbrxyIUoWfqo8Bi+m9QlAPR1P+Y4X0InB1n/S96USzxIBYS4ATGIRmuG H2/5R8daXs9guSjk+IVNIvqreBfy7xvQ9ljoFhokISU2NTLhU/xLrTHOG+glFInJtdnijZmyNT0 bLqecvE96/GNjf5jul/0V9fKyyr97mrrnD5UZFshy2rCMoUNZLcDUn38WD0azpv/7Vr1FB63msJ O8Y23R27KKelHPjxC5ms9W+YoVgBclVnEn3URJL/LUh28EUcxHjwhU3/3ynRPLG0vv8dX7L2Qtp 0C4Ne5Qlx3oVZp/3oiWx0qqQdaRsFeKsYiA8pDv6D+m4+G99DMuoGSAAiAmbWTo462iJA/AwhyZ UrvCN1Bi6pe/rAe8VefC0PuYAR3//+/2L60LcxRpJB316YgILdzfoUQQ9zw8mPENW0KeQemJn3j w0CKJD+18NdWGtg== 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 an Lsc algorithm implementation in libIPA. The LscAlgorithm class supports two algorithms: grid-based lsc and polynomial-based lsc. Both implementations have been generalized from the existing ones in the RkISP1 IPA. Signed-off-by: Jacopo Mondi --- src/ipa/libipa/lsc.cpp | 322 +++++++++++++++++++++++++++++ src/ipa/libipa/lsc.h | 145 +++++++++++++ src/ipa/libipa/lsc_base.cpp | 152 ++++++++++++++ src/ipa/libipa/lsc_base.h | 59 ++++++ src/ipa/libipa/lsc_grid.cpp | 33 +++ src/ipa/libipa/lsc_grid.h | 113 ++++++++++ src/ipa/libipa/lsc_polynomial.cpp | 133 +++++++++++- src/ipa/libipa/lsc_polynomial.h | 135 ++++++++---- src/ipa/libipa/meson.build | 5 + src/ipa/rkisp1/algorithms/lsc.cpp | 424 +++++--------------------------------- src/ipa/rkisp1/algorithms/lsc.h | 45 ++-- src/ipa/rkisp1/ipa_context.h | 10 +- 12 files changed, 1126 insertions(+), 450 deletions(-) diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp new file mode 100644 index 000000000000..ae713a776778 --- /dev/null +++ b/src/ipa/libipa/lsc.cpp @@ -0,0 +1,322 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + * + * libIPA Lsc algorithms + */ + +#include "lsc.h" + +#include + +/** + * \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 */ + +#ifndef __DOXYGEN__ +template<> +void Interpolator>:: + interpolate(const lsc::Components &a, + const lsc::Components &b, + lsc::Components &dest, + double lambda) +{ + for (auto const &[k, v] : a) + interpolateVector(v, b.at(k), dest[k], lambda); +} +#endif + +/** + * \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 T The type used to store the gain values when loaded from tuning file + * \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 + * multipliers to all pixels to compensate for the lens shading effect. + * + * The LscAlgorithm implements the libipa Lens Shading Correction algorithm. + * It provides support for parsing the tuning file content and generate 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 (LscGrid) or as radial polynomial (LscPolynomial). The gain + * 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 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 + * + * 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 + * applied to any sensor configuration. Grid-based lsc tables cannot be + * re-sampled and the configuration as parsed from the tuning file is used for + * all sensor configurations providing best-effort results. + * + * When the IPA algorithms wants to get access to the (resampled) 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(). + */ + +/** + * \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 + * + * 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 + */ + +/** + * \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 + * \param[in] yPos List of vertical positions of the LSC grid nodes + * + * Re-sample the lsc data for an \a analogCrop. + * + * Lsc data 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 only supported by polynomial-based lsc tables. + * + * \sa LscImplementation::resampleLscData + * + * \return 0 on success, a negative error code otherwise + */ + +/** + * \fn LscAlgorithm::interpolateComponents + * \brief Interpolate the lsc tables for a given colour temperature + * \param[in] ct The colour temperature + * + * Lsc data 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 new file mode 100644 index 000000000000..8bfa3a7c4ecf --- /dev/null +++ b/src/ipa/libipa/lsc.h @@ -0,0 +1,145 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + * + * libIPA Lsc algorithms + */ + +#pragma once + +#include + +#include + +#include "interpolator.h" +#include "lsc_grid.h" +#include "lsc_polynomial.h" + +namespace libcamera { + +LOG_DECLARE_CATEGORY(Lsc) + +namespace ipa { + +namespace lsc { + +struct ActiveState { + bool enabled; +}; + +struct FrameContext { + bool enabled; + bool update; +}; + +} /* 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 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, + LscDescriptor descriptor) + { + polynomial_ = false; + + std::string type = tuningData["type"].get("table"); + if (type == "table") { + impl_ = std::make_unique>(); + LOG(Lsc, Debug) << "Using grid-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) + { + LOG(Lsc, Debug) << "Sample Lsc data for " << analogCrop; + lsc::ComponentsMap lscData = + impl_->resampleLscData(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) + { + 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/lsc_base.cpp b/src/ipa/libipa/lsc_base.cpp new file mode 100644 index 000000000000..fba8d2f00d54 --- /dev/null +++ b/src/ipa/libipa/lsc_base.cpp @@ -0,0 +1,152 @@ +/* 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 + * + * Split the common classes and types definitions to a dedicated file to avoid + * circular inclusions. + * + * Both LscGrid 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_grid.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_grid.h and lsc_polynomial.h, which are then included + * by lsc.h. + */ + +namespace libcamera { + +namespace ipa { + +namespace lsc { + +/** + * \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 of type \a T. + * + * 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. + */ + +/** + * \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 + * several different colour temperatures. + * + * This class associates an lsc table to the colour temperature used when the + * table has been generated. + */ + +} /* namespace lsc */ + +/** + * \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 + * \tparam T The type used to store the gain values when loaded from tuning file + * \tparam U The fixedpoint format used to convert gain values generated by + * polynomial expansion to the register format + * + * Defines the interface for the lsc algorithm implementation. Currently + * implemented by LscGrid and LscPolynomial. + */ + +/** + * \fn LscImplementation::~LscImplementation + * \brief Virtual class destructor + */ + +/** + * \fn LscImplementation::parseLscData + * \brief Parse \a tuningData using \a descriptor + * \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 + */ + +/** + * \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 + * \param[in] yPos List of vertical positions of the lsc grid nodes + * + * Lsc tables are expressed in two formats: + * - A list of gain values (LscGrid) + * - A radial polynomial (LscPolynomial) + * + * Grid-based lsc data 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. + * + * Polynomial are more flexible and can be re-sampled for a given resolution + * using a list of horizontal and vertical nodes that define the lsc grid. + * Polynomial lsc tables have to be re-sampled every time a new configuration is + * applied, as each streaming session might use a different sensor crop + * rectangle. + * + * \a cropRectangle represents the size of the frame on which the polynomial Lsc + * has to be re-sampled on. + * + * \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 + * 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..aecae305aa9c --- /dev/null +++ b/src/ipa/libipa/lsc_base.h @@ -0,0 +1,59 @@ +/* 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 "libcamera/internal/value_node.h" + +namespace libcamera { + +namespace ipa { + +namespace lsc { + +template +class Components : public std::map> +{ +}; + +template +class ComponentsMap : public std::map> +{ +}; + +} /* namespace lsc */ + +struct LscDescriptor { + std::vector keys; + unsigned int numHCells; + unsigned int numVCells; + Size sensorSize; +}; + +template +class LscImplementation +{ +public: + virtual ~LscImplementation() = default; + + virtual int parseLscData(const ValueNode &tuningData, + const LscDescriptor &descriptor) = 0; + + virtual lsc::ComponentsMap resampleLscData(const Rectangle &cropRectangle, + const std::vector &xPos, + const std::vector &yPos) = 0; +}; + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/lsc_grid.cpp b/src/ipa/libipa/lsc_grid.cpp new file mode 100644 index 000000000000..878994acc8c9 --- /dev/null +++ b/src/ipa/libipa/lsc_grid.cpp @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * Grid based lens shading correction + */ + +#include "lsc_grid.h" + +/** + * \file lsc_grid.h + * \brief LscGrid class + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(LscGrid) + +namespace ipa { + +/** + * \class LscGrid + * \brief Grid based lsc algorithm implementation + * + * Grid based lsc algorithm implementation. The LscGrid class implements lsc + * support using tabular lsc data. + * + * \sa LscImplementation + * \sa LscAlgorithm + */ + +} /* namespace ipa */ +} /* namespace libcamera */ diff --git a/src/ipa/libipa/lsc_grid.h b/src/ipa/libipa/lsc_grid.h new file mode 100644 index 000000000000..e92cde95d18d --- /dev/null +++ b/src/ipa/libipa/lsc_grid.h @@ -0,0 +1,113 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * Grid based lens shading correction + */ + +#pragma once + +#include + +#include + +#include "lsc_base.h" + +namespace libcamera { + +LOG_DECLARE_CATEGORY(LscGrid) + +namespace ipa { + +template +class LscGrid : public LscImplementation +{ +public: + ~LscGrid() {} + + int parseLscData(const ValueNode &yamlSets, + const LscDescriptor &descriptor) override + { + const auto &sets = yamlSets.asList(); + + for (const auto &yamlSet : sets) { + uint32_t ct = yamlSet["ct"].get(0); + + int ret = parseLscComponent(yamlSet, ct, descriptor); + if (ret) + return ret; + } + + if (lscData_.empty()) { + LOG(LscGrid, Error) << "Failed to load any sets"; + return -EINVAL; + } + + return 0; + } + + lsc::ComponentsMap resampleLscData([[maybe_unused]] const Rectangle &cropRectangle, + [[maybe_unused]] const std::vector &xPos, + [[maybe_unused]] const std::vector &yPos) override + { + /* No resampling for grid-based LSC algorithm. */ + 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(LscGrid, Error) + << "Set " << k << " for color temperature " + << ct << " is missing"; + return -EINVAL; + } + } + + auto [it, inserted] = lscData_.emplace(ct, component); + if (!inserted) { + LOG(LscGrid, 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(LscGrid, Error) + << "Invalid '" << prop << "' values: expected " + << kLscNumSamples + << " elements, got " << table.size(); + return {}; + } + + return table; + } + + lsc::ComponentsMap lscData_; +}; + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/lsc_polynomial.cpp b/src/ipa/libipa/lsc_polynomial.cpp index f607d86c54c3..8d3f1883d6a5 100644 --- a/src/ipa/libipa/lsc_polynomial.cpp +++ b/src/ipa/libipa/lsc_polynomial.cpp @@ -1,12 +1,14 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ /* - * Copyright (C) 2024, Ideas On Board + * Copyright (C) 2026, Ideas On Board * - * Polynomial class to represent lens shading correction + * Polynomial based lens shading correction */ #include "lsc_polynomial.h" +#include + #include /** @@ -21,7 +23,7 @@ LOG_DEFINE_CATEGORY(LscPolynomial) namespace ipa { /** - * \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,13 @@ namespace ipa { */ /** - * \fn LscPolynomial::sampleAtNormalizedPixelPos(double x, double y) + * \fn Polynomial::Polynomial(const Polynomial &other) + * \brief Construct a Polynomial by copy + * \param[in] other The Polynomial to copy construct from + */ + +/** + * \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 @@ -55,9 +63,21 @@ namespace ipa { * \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 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 @@ -66,9 +86,18 @@ namespace ipa { * * \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 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 @@ -76,6 +105,92 @@ namespace ipa { * * \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; +} + +/** + * \class LscPolynomialBase + * \brief Base class for LscPolynomial + * + * Base class for LscPolynomial for non-templated functions. + */ + +/** + * \brief Parse polynomial lsc data + * \param[in] yamlSets The tuning file content + * \param[in] descriptor The lsc engine descriptor + * + * Parse the lsc data in polyomial form from the \a yamlSet tuning data. + */ +int LscPolynomialBase::parseLscData(const ValueNode &yamlSets, + const LscDescriptor &descriptor) +{ + const auto &sets = yamlSets.asList(); + for (const auto &yamlSet : sets) { + uint32_t ct = yamlSet["ct"].get(0); + + Components components; + for (auto &k : descriptor.keys) { + auto polynomial = yamlSet[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); + } + + auto [it, inserted] = lscData_.emplace(ct, components); + if (!inserted) { + LOG(LscPolynomial, Error) + << "Multiple sets found for " + << "color temperature " << ct; + return -EINVAL; + } + } + + if (lscData_.empty()) { + LOG(LscPolynomial, Error) << "Failed to load any sets"; + return -EINVAL; + } + + return 0; +} + +/** + * \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 + * + * Polynomial-based lsc algorithm implementation. The LscPolynomial class + * implements lsc support using Polynomial to represent the shading artifacts + * map. + * + * \sa LscImplementation + * \sa LscAlgorithm + * + */ -} // namespace ipa -} // namespace libcamera +} /* namespace ipa */ +} /* namespace libcamera */ diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h index d7d9ae42e360..72cca591f260 100644 --- a/src/ipa/libipa/lsc_polynomial.h +++ b/src/ipa/libipa/lsc_polynomial.h @@ -1,15 +1,17 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ /* - * Copyright (C) 2024, Ideas On Board + * Copyright (C) 2026, 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 @@ -18,64 +20,117 @@ #include "libcamera/internal/value_node.h" +#include "lsc_base.h" + namespace libcamera { LOG_DECLARE_CATEGORY(LscPolynomial) namespace ipa { -class LscPolynomial +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 }) { } - double sampleAtNormalizedPixelPos(double x, double y) const + Polynomial(const Polynomial &other) = default; + + double sampleAtNormalizedPixelPos(double x, double y) const; + double getM() const; + void setReferenceImageSize(const Size &size); + +private: + double cx_; + double cy_; + double cnx_; + double cny_; + std::array coefficients_; + Size imageSize_; +}; + +class LscPolynomialBase +{ +private: + using Components = std::map; + using ComponentsMap = std::map; + +public: + int parseLscData(const ValueNode &yamlSets, + const LscDescriptor &descriptor); + +protected: + ComponentsMap lscData_; +}; + +template +class LscPolynomial : public LscPolynomialBase, public LscImplementation +{ +public: + ~LscPolynomial() {} + + int parseLscData(const ValueNode &yamlSets, + const LscDescriptor &descriptor) override { - 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; + return LscPolynomialBase::parseLscData(yamlSets, descriptor); } - double getM() const + lsc::ComponentsMap resampleLscData(const Rectangle &cropRectangle, + const std::vector &xPos, + const std::vector &yPos) override { - 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)); + lsc::ComponentsMap components; - return sqrt(mx * mx + my * my); - } + for (auto &[t, c] : lscData_) { + lsc::Components comp; - void setReferenceImageSize(const Size &size) - { - assert(!size.isNull()); - imageSize_ = size; + for (auto &[k, p] : c) { + comp.emplace(std::piecewise_construct, + std::forward_as_tuple(k), + std::forward_as_tuple(samplePolynomial(p, xPos, yPos, + cropRectangle))); + } - /* Calculate normalized centers */ - double m = getM(); - cnx_ = (size.width * cx_) / m; - cny_ = (size.height * cy_) / m; + components[t] = comp; + } + + return components; } private: - double cx_; - double cy_; - double cnx_; - double cny_; - std::array coefficients_; + std::vector samplePolynomial(const Polynomial &poly, + Span xPositions, + Span yPositions, + const Rectangle &cropRectangle) - Size imageSize_; + { + 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 */ @@ -83,8 +138,8 @@ private: #ifndef __DOXYGEN__ template<> -struct ValueNode::Accessor { - std::optional get(const ValueNode &obj) const +struct ValueNode::Accessor { + std::optional get(const ValueNode &obj) const { std::optional cx = obj["cx"].get(); std::optional cy = obj["cy"].get(); @@ -98,7 +153,7 @@ struct ValueNode::Accessor { LOG(LscPolynomial, Error) << "Polynomial is missing a parameter"; - return ipa::LscPolynomial(*cx, *cy, *k0, *k1, *k2, *k3, *k4); + return ipa::Polynomial(*cx, *cy, *k0, *k1, *k2, *k3, *k4); } }; diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build index edf8eabd8b78..da6ea0c5e130 100644 --- a/src/ipa/libipa/meson.build +++ b/src/ipa/libipa/meson.build @@ -14,6 +14,9 @@ libipa_headers = files([ 'fixedpoint.h', 'histogram.h', 'interpolator.h', + 'lsc.h', + 'lsc_base.h', + 'lsc_grid.h', 'lsc_polynomial.h', 'lux.h', 'module.h', @@ -36,6 +39,8 @@ libipa_sources = files([ 'fixedpoint.cpp', 'histogram.cpp', 'interpolator.cpp', + 'lsc.cpp', + 'lsc_grid.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 25f7c512b35c..51076737ffa5 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021-2022, Ideas On Board * - * RkISP1 Lens Shading Correction control + * RkISP1 Lens Shading Correction algorithm */ #include "lsc.h" @@ -14,56 +14,17 @@ #include #include -#include "libcamera/internal/value_node.h" - -#include "libipa/lsc_polynomial.h" -#include "linux/rkisp1-config.h" - /** * \file lsc.h */ 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 rkisp1::algorithms::LensShadingCorrection::Components &a, - const rkisp1::algorithms::LensShadingCorrection::Components &b, - rkisp1::algorithms::LensShadingCorrection::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 { /** * \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 YAML tuning file. */ LOG_DEFINE_CATEGORY(RkISP1Lsc) @@ -72,265 +33,20 @@ namespace { constexpr int kColourTemperatureQuantization = 10; -class LscPolynomialShadingDescriptor : public LensShadingCorrection::ShadingDescriptor -{ -public: - LscPolynomialShadingDescriptor(const LscPolynomial &pr, const LscPolynomial &pgr, - const LscPolynomial &pgb, const LscPolynomial &pb) - : pr_(pr), pgr_(pgr), pgb_(pgb), pb_(pb) - { - } - - LensShadingCorrection::Components sampleForCrop(const Rectangle &cropRectangle, - Span xSizes, - Span ySizes) override; - -private: - std::vector samplePolynomial(const LscPolynomial &poly, - Span xPositions, - Span yPositions, - const Rectangle &cropRectangle); - - std::vector sizesListToPositions(Span sizes); - - LscPolynomial pr_; - LscPolynomial pgr_; - LscPolynomial pgb_; - LscPolynomial pb_; -}; - -LensShadingCorrection::Components -LscPolynomialShadingDescriptor::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) - }; -} - -std::vector -LscPolynomialShadingDescriptor::samplePolynomial(const LscPolynomial &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 -LscPolynomialShadingDescriptor::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 LscPolynomialLoader -{ -public: - LscPolynomialLoader(const Size &sensorSize) - : sensorSize_(sensorSize) - { - } - - int parseLscData(const ValueNode &yamlSets, - LensShadingCorrection::ShadingDescriptorMap &lscData); - -private: - Size sensorSize_; -}; - -int LscPolynomialLoader::parseLscData(const ValueNode &yamlSets, - LensShadingCorrection::ShadingDescriptorMap &lscData) +unsigned int quantize(unsigned int value, unsigned int step) { - const auto &sets = yamlSets.asList(); - for (const auto &yamlSet : sets) { - std::optional pr, pgr, pgb, pb; - uint32_t ct = yamlSet["ct"].get(0); - - if (lscData.count(ct)) { - LOG(RkISP1Lsc, Error) - << "Multiple sets found for " - << "color temperature " << ct; - return -EINVAL; - } - - pr = yamlSet["r"].get(); - pgr = yamlSet["gr"].get(); - pgb = yamlSet["gb"].get(); - pb = yamlSet["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; + return std::lround(value / static_cast(step)) * step; } -class LscTableShadingDescriptor : public LensShadingCorrection::ShadingDescriptor -{ -public: - LscTableShadingDescriptor(LensShadingCorrection::Components components) - : lscData_(std::move(components)) - { - } - - LensShadingCorrection::Components - 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: - LensShadingCorrection::Components lscData_; -}; - -class LscTableLoader -{ -public: - int parseLscData(const ValueNode &yamlSets, - LensShadingCorrection::ShadingDescriptorMap &lscData); - -private: - std::vector parseTable(const ValueNode &tuningData, - const char *prop); -}; - -int LscTableLoader::parseLscData(const ValueNode &yamlSets, - LensShadingCorrection::ShadingDescriptorMap &lscData) -{ - const auto &sets = yamlSets.asList(); - - for (const auto &yamlSet : sets) { - uint32_t ct = yamlSet["ct"].get(0); - - if (lscData.count(ct)) { - LOG(RkISP1Lsc, Error) - << "Multiple sets found for color temperature " - << ct; - return -EINVAL; - } - - LensShadingCorrection::Components set; - set.r = parseTable(yamlSet, "r"); - set.gr = parseTable(yamlSet, "gr"); - set.gb = parseTable(yamlSet, "gb"); - set.b = parseTable(yamlSet, "b"); - - if (set.r.empty() || set.gr.empty() || - set.gb.empty() || set.b.empty()) { - LOG(RkISP1Lsc, Error) - << "Set for color temperature " << ct - << " is missing tables"; - return -EINVAL; - } - - lscData.emplace( - ct, std::make_unique(std::move(set))); - } - - if (lscData.empty()) { - LOG(RkISP1Lsc, Error) << "Failed to load any sets"; - return -EINVAL; - } - - return 0; -} +} /* namespace */ -std::vector LscTableLoader::parseTable(const ValueNode &tuningData, - const char *prop) +LensShadingCorrection::LensShadingCorrection() + : lastAppliedCt_(0), lastAppliedQuantizedCt_(0) { - 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) +std::vector LensShadingCorrection::parseSizes(const ValueNode &tuningData, + const char *prop) { std::vector sizes = tuningData[prop].get>().value_or(utils::defopt); @@ -359,22 +75,36 @@ std::vector parseSizes(const ValueNode &tuningData, return sizes; } -unsigned int quantize(unsigned int value, unsigned int step) +/* + * 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::sizesToPositions(Span sizes) { - return std::lround(value / static_cast(step)) * step; -} + const int half = sizes.size(); + std::vector positions(half * 2 + 1); + double x = 0.0; -} /* namespace */ + 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; + } -LensShadingCorrection::LensShadingCorrection() - : lastAppliedCt_(0), lastAppliedQuantizedCt_(0) -{ + return positions; } /** * \copydoc libcamera::ipa::Algorithm::init */ -int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, +int LensShadingCorrection::init(IPAContext &context, const ValueNode &tuningData) { xSize_ = parseSizes(tuningData, "x-size"); @@ -383,56 +113,27 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, if (xSize_.empty() || ySize_.empty()) return -EINVAL; - /* Get all defined sets to apply. */ - const ValueNode &yamlSets = tuningData["sets"]; - if (!yamlSets.isList()) { - LOG(RkISP1Lsc, Error) - << "'sets' parameter not found in tuning file"; - return -EINVAL; - } - - ShadingDescriptorMap lscData; - int ret = 0; - - std::string type = tuningData["type"].get("table"); - if (type == "table") { - LOG(RkISP1Lsc, Debug) << "Loading tabular LSC data."; - auto loader = LscTableLoader(); - ret = loader.parseLscData(yamlSets, lscData); - } 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(yamlSets, lscData); - } else { - LOG(RkISP1Lsc, Error) << "Unsupported LSC data type '" - << type << "'"; - ret = -EINVAL; - } - - if (ret) - return ret; + xPos_ = sizesToPositions(xSize_); + yPos_ = sizesToPositions(ySize_); - context.ctrlMap[&controls::LensShadingCorrectionEnable] = - ControlInfo(false, true, true); - - shadingDescriptors_ = std::move(lscData); - - return 0; + 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 + }); } /** * \copydoc libcamera::ipa::Algorithm::configure */ int LensShadingCorrection::configure(IPAContext &context, - [[maybe_unused]] const IPACameraSensorInfo &configInfo) + const IPACameraSensorInfo &configInfo) { const Size &size = context.configuration.sensor.size; Size totalSize{}; + /* Calculate gradients. */ for (unsigned int i = 0; i < RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE; ++i) { xSizes_[i] = xSize_[i] * size.width; ySizes_[i] = ySize_[i] * size.height; @@ -455,16 +156,8 @@ int LensShadingCorrection::configure(IPAContext &context, yGrad_[i] = std::round(32768 / ySizes_[i]); } - 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_); - - 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) @@ -476,12 +169,16 @@ void LensShadingCorrection::setParameters(rkisp1_cif_isp_lsc_config &config) } void LensShadingCorrection::copyTable(rkisp1_cif_isp_lsc_config &config, - const Components &set) + const ipa::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]); } /** @@ -492,19 +189,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); } /** @@ -542,7 +228,7 @@ void LensShadingCorrection::prepare([[maybe_unused]] IPAContext &context, setParameters(*config); - const Components &set = sets_.getInterpolated(quantizedCt); + const auto &set = lscAlgo_.interpolateComponents(quantizedCt); copyTable(*config, set); lastAppliedCt_ = ct; @@ -562,7 +248,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 0a256e225327..7c656d95acfb 100644 --- a/src/ipa/rkisp1/algorithms/lsc.h +++ b/src/ipa/rkisp1/algorithms/lsc.h @@ -2,17 +2,23 @@ /* * Copyright (C) 2021-2022, Ideas On Board * - * RkISP1 Lens Shading Correction control + * RkISP1 Lens Shading Correction algorithm */ #pragma once -#include -#include +#include -#include "libipa/interpolator.h" +#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 { @@ -37,39 +43,28 @@ public: 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: + std::vector parseSizes(const ValueNode &tuningData, + const char *prop); + std::vector sizesToPositions(Span sizes); + 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 ipa::lsc::Components &set0); - ShadingDescriptorMap shadingDescriptors_; - ipa::Interpolator sets_; std::vector xSize_; std::vector ySize_; 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]; uint16_t ySizes_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE]; + std::vector xPos_; + std::vector yPos_; unsigned int lastAppliedCt_; unsigned int lastAppliedQuantizedCt_; + + LscAlgorithm> lscAlgo_; }; } /* namespace ipa::rkisp1::algorithms */ diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h index cd97e10bcf2b..005f4102b4f6 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 { @@ -138,9 +139,7 @@ struct IPAActiveState { double strength; } wdr; - struct { - bool enabled; - } lsc; + ipa::lsc::ActiveState lsc; }; struct IPAFrameContext : public FrameContext { @@ -213,10 +212,7 @@ struct IPAFrameContext : public FrameContext { double gain; } wdr; - struct { - bool enabled; - bool update; - } lsc; + ipa::lsc::FrameContext lsc; }; struct IPAContext {