new file mode 100644
@@ -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 <libcamera/base/log.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 */
+
+#ifndef __DOXYGEN__
+template<>
+void Interpolator<lsc::Components<uint16_t>>::
+ interpolate(const lsc::Components<uint16_t> &a,
+ const lsc::Components<uint16_t> &b,
+ lsc::Components<uint16_t> &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 */
new file mode 100644
@@ -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 <memory>
+
+#include <libcamera/control_ids.h>
+
+#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<typename T>
+void interpolateVector(const std::vector<T> &a, const std::vector<T> &b,
+ std::vector<T> &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<lsc::Components<uint16_t>>::
+ interpolate(const lsc::Components<uint16_t> &a,
+ const lsc::Components<uint16_t> &b,
+ lsc::Components<uint16_t> &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<typename T, typename U>
+class LscAlgorithm : public LscAlgorithmBase
+{
+public:
+ int init(const ValueNode &tuningData, ControlInfoMap::Map &controls,
+ LscDescriptor descriptor)
+ {
+ polynomial_ = false;
+
+ std::string type = tuningData["type"].get<std::string>("table");
+ if (type == "table") {
+ impl_ = std::make_unique<LscGrid<T, U>>();
+ LOG(Lsc, Debug) << "Using grid-based Lsc";
+ } else if (type == "polynomial") {
+ impl_ = std::make_unique<LscPolynomial<T, U>>();
+ 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<double> &xPos,
+ const std::vector<double> &yPos)
+ {
+ LOG(Lsc, Debug) << "Sample Lsc data for " << analogCrop;
+ lsc::ComponentsMap<T> 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<T> interpolateComponents(unsigned int ct)
+ {
+ return sets_.getInterpolated(ct);
+ }
+
+ const lsc::ComponentsMap<T> getComponents()
+ {
+ return lscData_;
+ }
+
+private:
+ std::unique_ptr<LscImplementation<T, U>> impl_;
+ Interpolator<lsc::Components<T>> sets_;
+ lsc::ComponentsMap<T> lscData_;
+ bool polynomial_;
+};
+
+} /* namespace ipa */
+
+} /* namespace libcamera */
new file mode 100644
@@ -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 */
new file mode 100644
@@ -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 <map>
+#include <string>
+#include <vector>
+
+#include <libcamera/geometry.h>
+
+#include "libcamera/internal/value_node.h"
+
+namespace libcamera {
+
+namespace ipa {
+
+namespace lsc {
+
+template<typename T>
+class Components : public std::map<std::string, std::vector<T>>
+{
+};
+
+template<typename T>
+class ComponentsMap : public std::map<unsigned int, Components<T>>
+{
+};
+
+} /* namespace lsc */
+
+struct LscDescriptor {
+ std::vector<std::string> keys;
+ unsigned int numHCells;
+ unsigned int numVCells;
+ Size sensorSize;
+};
+
+template<typename T, typename U>
+class LscImplementation
+{
+public:
+ virtual ~LscImplementation() = default;
+
+ virtual int parseLscData(const ValueNode &tuningData,
+ const LscDescriptor &descriptor) = 0;
+
+ virtual lsc::ComponentsMap<T> resampleLscData(const Rectangle &cropRectangle,
+ const std::vector<double> &xPos,
+ const std::vector<double> &yPos) = 0;
+};
+
+} /* namespace ipa */
+
+} /* namespace libcamera */
new file mode 100644
@@ -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 */
new file mode 100644
@@ -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 <vector>
+
+#include <libcamera/base/log.h>
+
+#include "lsc_base.h"
+
+namespace libcamera {
+
+LOG_DECLARE_CATEGORY(LscGrid)
+
+namespace ipa {
+
+template<typename T, typename U>
+class LscGrid : public LscImplementation<T, U>
+{
+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<uint32_t>(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<T> resampleLscData([[maybe_unused]] const Rectangle &cropRectangle,
+ [[maybe_unused]] const std::vector<double> &xPos,
+ [[maybe_unused]] const std::vector<double> &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<T> 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<T> parseTable(const ValueNode &tuningData,
+ const char *prop, unsigned int numHCells,
+ unsigned int numVCells)
+ {
+ unsigned int kLscNumSamples = numHCells * numVCells;
+
+ std::vector<T> table =
+ tuningData[prop].get<std::vector<T>>().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<T> lscData_;
+};
+
+} /* namespace ipa */
+
+} /* namespace libcamera */
@@ -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 <assert.h>
+
#include <libcamera/base/log.h>
/**
@@ -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<uint32_t>(0);
+
+ Components components;
+ for (auto &k : descriptor.keys) {
+ auto polynomial = yamlSet[k.c_str()].get<Polynomial>();
+ 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 */
@@ -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 <algorithm>
#include <array>
-#include <assert.h>
#include <cmath>
+#include <optional>
+#include <tuple>
#include <libcamera/base/log.h>
#include <libcamera/base/span.h>
@@ -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<double, 5> coefficients_;
+ Size imageSize_;
+};
+
+class LscPolynomialBase
+{
+private:
+ using Components = std::map<std::string, Polynomial>;
+ using ComponentsMap = std::map<unsigned int, Components>;
+
+public:
+ int parseLscData(const ValueNode &yamlSets,
+ const LscDescriptor &descriptor);
+
+protected:
+ ComponentsMap lscData_;
+};
+
+template<typename T, typename U>
+class LscPolynomial : public LscPolynomialBase, public LscImplementation<T, U>
+{
+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<T> resampleLscData(const Rectangle &cropRectangle,
+ const std::vector<double> &xPos,
+ const std::vector<double> &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<T> components;
- return sqrt(mx * mx + my * my);
- }
+ for (auto &[t, c] : lscData_) {
+ lsc::Components<T> 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<double, 5> coefficients_;
+ std::vector<T> samplePolynomial(const Polynomial &poly,
+ Span<const double> xPositions,
+ Span<const double> 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<T> 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<float>
+ (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<ipa::LscPolynomial> {
- std::optional<ipa::LscPolynomial> get(const ValueNode &obj) const
+struct ValueNode::Accessor<ipa::Polynomial> {
+ std::optional<ipa::Polynomial> get(const ValueNode &obj) const
{
std::optional<double> cx = obj["cx"].get<double>();
std::optional<double> cy = obj["cy"].get<double>();
@@ -98,7 +153,7 @@ struct ValueNode::Accessor<ipa::LscPolynomial> {
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);
}
};
@@ -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',
@@ -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 <libcamera/base/log.h>
#include <libcamera/base/utils.h>
-#include "libcamera/internal/value_node.h"
-
-#include "libipa/lsc_polynomial.h"
-#include "linux/rkisp1-config.h"
-
/**
* \file lsc.h
*/
namespace libcamera {
-namespace ipa {
-
-template<typename T>
-void interpolateVector(const std::vector<T> &a, const std::vector<T> &b,
- std::vector<T> &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<rkisp1::algorithms::LensShadingCorrection::Components>::
- 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<const double> xSizes,
- Span<const double> ySizes) override;
-
-private:
- std::vector<uint16_t> samplePolynomial(const LscPolynomial &poly,
- Span<const double> xPositions,
- Span<const double> yPositions,
- const Rectangle &cropRectangle);
-
- std::vector<double> sizesListToPositions(Span<const double> sizes);
-
- LscPolynomial pr_;
- LscPolynomial pgr_;
- LscPolynomial pgb_;
- LscPolynomial pb_;
-};
-
-LensShadingCorrection::Components
-LscPolynomialShadingDescriptor::sampleForCrop(const Rectangle &cropRectangle,
- Span<const double> xSizes,
- Span<const double> ySizes)
-{
- std::vector<double> xPos = sizesListToPositions(xSizes);
- std::vector<double> 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<uint16_t>
-LscPolynomialShadingDescriptor::samplePolynomial(const LscPolynomial &poly,
- Span<const double> xPositions,
- Span<const double> 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<uint16_t> 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<int>(
- 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<double>
-LscPolynomialShadingDescriptor::sizesListToPositions(Span<const double> sizes)
-{
- const int half = sizes.size();
- std::vector<double> 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<LscPolynomial> pr, pgr, pgb, pb;
- uint32_t ct = yamlSet["ct"].get<uint32_t>(0);
-
- if (lscData.count(ct)) {
- LOG(RkISP1Lsc, Error)
- << "Multiple sets found for "
- << "color temperature " << ct;
- return -EINVAL;
- }
-
- pr = yamlSet["r"].get<LscPolynomial>();
- pgr = yamlSet["gr"].get<LscPolynomial>();
- pgb = yamlSet["gb"].get<LscPolynomial>();
- pb = yamlSet["b"].get<LscPolynomial>();
-
- 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<LscPolynomialShadingDescriptor>(
- *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<double>(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<const double> xSizes,
- [[maybe_unused]] Span<const double> 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<uint16_t> 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<uint32_t>(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<LscTableShadingDescriptor>(std::move(set)));
- }
-
- if (lscData.empty()) {
- LOG(RkISP1Lsc, Error) << "Failed to load any sets";
- return -EINVAL;
- }
-
- return 0;
-}
+} /* namespace */
-std::vector<uint16_t> 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<uint16_t> table =
- tuningData[prop].get<std::vector<uint16_t>>().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<double> parseSizes(const ValueNode &tuningData,
- const char *prop)
+std::vector<double> LensShadingCorrection::parseSizes(const ValueNode &tuningData,
+ const char *prop)
{
std::vector<double> sizes =
tuningData[prop].get<std::vector<double>>().value_or(utils::defopt);
@@ -359,22 +75,36 @@ std::vector<double> 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<double> LensShadingCorrection::sizesToPositions(Span<const double> sizes)
{
- return std::lround(value / static_cast<double>(step)) * step;
-}
+ const int half = sizes.size();
+ std::vector<double> 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<std::string>("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<unsigned int, LensShadingCorrection::Components> 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<uint16_t> &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")
@@ -2,17 +2,23 @@
/*
* Copyright (C) 2021-2022, Ideas On Board
*
- * RkISP1 Lens Shading Correction control
+ * RkISP1 Lens Shading Correction algorithm
*/
#pragma once
-#include <map>
-#include <memory>
+#include <vector>
-#include "libipa/interpolator.h"
+#include <linux/rkisp1-config.h>
+
+#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<uint16_t> r;
- std::vector<uint16_t> gr;
- std::vector<uint16_t> gb;
- std::vector<uint16_t> b;
- };
-
- class ShadingDescriptor
- {
- public:
- virtual ~ShadingDescriptor() = default;
- virtual Components sampleForCrop(const Rectangle &cropRectangle,
- Span<const double> xSizes,
- Span<const double> ySizes) = 0;
- };
-
- using ShadingDescriptorMap = std::map<unsigned int, std::unique_ptr<ShadingDescriptor>>;
-
private:
+ std::vector<double> parseSizes(const ValueNode &tuningData,
+ const char *prop);
+ std::vector<double> sizesToPositions(Span<const double> 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<uint16_t> &set0);
- ShadingDescriptorMap shadingDescriptors_;
- ipa::Interpolator<Components> sets_;
std::vector<double> xSize_;
std::vector<double> 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<double> xPos_;
+ std::vector<double> yPos_;
unsigned int lastAppliedCt_;
unsigned int lastAppliedQuantizedCt_;
+
+ LscAlgorithm<uint16_t, UQ<2, 10>> lscAlgo_;
};
} /* namespace ipa::rkisp1::algorithms */
@@ -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 {
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 <jacopo.mondi@ideasonboard.com> --- 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(-)