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